Skip to content

Commit 9e7a940

Browse files
committed
Merge branch 'upstream' into concedo_experimental
# Conflicts: # ggml/src/ggml-opencl/ggml-opencl.cpp # ggml/src/ggml-opencl/kernels/softmax_4_f16.cl # ggml/src/ggml-opencl/kernels/softmax_4_f32.cl # ggml/src/ggml-opencl/kernels/softmax_f16.cl # ggml/src/ggml-opencl/kernels/softmax_f32.cl # ggml/src/ggml-rpc/ggml-rpc.cpp # ggml/src/ggml-sycl/ggml-sycl.cpp
2 parents 7087aeb + cd6983d commit 9e7a940

File tree

9 files changed

+134
-56
lines changed

9 files changed

+134
-56
lines changed

ggml/src/ggml-blas/ggml-blas.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ ggml_backend_t ggml_backend_blas_init(void) {
281281
ggml_backend_blas_context * ctx = new ggml_backend_blas_context;
282282

283283
ggml_backend_t backend = new ggml_backend {
284-
/* .guid = */ ggml_backend_blas_guid(),
285-
/* .interface = */ blas_backend_i,
286-
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_blas_reg(), 0),
287-
/* .context = */ ctx,
284+
/* .guid = */ ggml_backend_blas_guid(),
285+
/* .iface = */ blas_backend_i,
286+
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_blas_reg(), 0),
287+
/* .context = */ ctx,
288288
};
289289

290290
#if defined(OPENBLAS_VERSION) && defined(GGML_USE_OPENMP)

ggml/src/ggml-cpu/ggml-cpu.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,10 @@ ggml_backend_t ggml_backend_cpu_init(void) {
214214
ctx->abort_callback_data = NULL;
215215

216216
ggml_backend_t cpu_backend = new ggml_backend {
217-
/* .guid = */ ggml_backend_cpu_guid(),
218-
/* .interface = */ ggml_backend_cpu_i,
219-
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cpu_reg(), 0),
220-
/* .context = */ ctx,
217+
/* .guid = */ ggml_backend_cpu_guid(),
218+
/* .iface = */ ggml_backend_cpu_i,
219+
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cpu_reg(), 0),
220+
/* .context = */ ctx,
221221
};
222222

223223
if (cpu_backend == NULL) {

ggml/src/ggml-cuda/fattn-mma-f16.cuh

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ static __device__ __forceinline__ void flash_attn_ext_f16_process_tile(
785785
const half2 * const __restrict__ K_h2,
786786
const half2 * const __restrict__ V_h2,
787787
const half2 * const __restrict__ mask_h2,
788+
const float * const __restrict__ sinks_f,
788789
float2 * const __restrict__ dstk,
789790
float2 * const __restrict__ dstk_fixup,
790791
const float scale,
@@ -957,6 +958,52 @@ static __device__ __forceinline__ void flash_attn_ext_f16_process_tile(
957958
}
958959
}
959960

961+
// If attention sinks are used, potentially re-scale if KQ_max is small.
962+
// Also add the sink as a value to KQ_rowsum, this is done after synchonization of KQ_rowsum
963+
// so it's being done unconditionally for every thread.
964+
if (!is_fixup && (np == 1 || threadIdx.y % np == 0) && sinks_f) {
965+
float KQ_max_scale[cols_per_thread];
966+
#pragma unroll
967+
for (int col = 0; col < cols_per_thread; ++col) {
968+
static_assert(ntiles == 1 || ntiles == 2, "ntiles > 2 not implemented");
969+
const int jc = ntiles == 1 ? 2*tile_C_VKQ::get_j(col/2) + col % 2 : tile_C_VKQ_16::get_i(col);
970+
const float sink = sinks_f[jc % ncols2];
971+
972+
const float KQ_max_new = fmaxf(KQ_max[col], sink);
973+
const float KQ_max_diff = KQ_max[col] - KQ_max_new;
974+
KQ_max_scale[col] = expf(KQ_max_diff);
975+
KQ_max[col] = KQ_max_new;
976+
977+
*((uint32_t *) &KQ_max_scale[col]) *= KQ_max_diff >= SOFTMAX_FTZ_THRESHOLD;
978+
979+
const float KQ_max_add = expf(sink - KQ_max_new);
980+
KQ_rowsum[col] = KQ_max_scale[col]*KQ_rowsum[col] + KQ_max_add;
981+
}
982+
983+
if (ntiles == 1) {
984+
const half2 KQ_max_scale_h2 = make_half2(KQ_max_scale[0], KQ_max_scale[1]);
985+
#pragma unroll
986+
for (int i = 0; i < DV/tile_C_VKQ::I; ++i) {
987+
#pragma unroll
988+
for (int l = 0; l < tile_C_VKQ::ne; ++l) {
989+
VKQ_C[i].x[l] *= KQ_max_scale_h2;
990+
}
991+
}
992+
} else {
993+
#pragma unroll
994+
for (int col = 0; col < cols_per_thread; ++col) {
995+
const half2 KQ_max_scale_h2 = make_half2(KQ_max_scale[col], KQ_max_scale[col]);
996+
#pragma unroll
997+
for (int i = 0; i < DV/tile_C_VKQ_16::J; ++i) {
998+
#pragma unroll
999+
for (int l0 = 0; l0 < tile_C_VKQ_16::ne; l0 += 2) {
1000+
VKQ_C_16[i*ntiles/2 + col/2].x[l0 + col % 2] *= KQ_max_scale_h2;
1001+
}
1002+
}
1003+
}
1004+
}
1005+
}
1006+
9601007
// Combine VKQ accumulator values if np > 1.
9611008
// It's also faster to do small writes to shared memory, then large write to VRAM than to do small writes to VRAM.
9621009
// So also write VKQ accumulators to shared memory in column-major format if np == 1.
@@ -1271,18 +1318,21 @@ static __global__ void flash_attn_ext_f16(
12711318

12721319
while (kbc < kbc_stop && kb0_stop == iter_k) {
12731320
const int sequence = kbc / (iter_k*iter_j*(ne02/ncols2));
1274-
const int head = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j);
1275-
const int jt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*head) / iter_k; // j index of current tile.
1321+
const int zt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j); // head in units of ncols2
1322+
const int jt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*zt) / iter_k; // j index of current tile.
12761323

1277-
const float2 * Q_f2 = (const float2 *) (Q + nb03*sequence + nb02*(head*ncols2));
1278-
const half2 * K_h2 = (const half2 *) (K + nb13*sequence + nb12*(head*ncols2 / gqa_ratio));
1324+
const int head0 = zt * ncols2;
1325+
1326+
const float2 * Q_f2 = (const float2 *) (Q + nb03*sequence + nb02* head0);
1327+
const half2 * K_h2 = (const half2 *) (K + nb13*sequence + nb12*(head0 / gqa_ratio));
12791328
const half2 * mask_h2 = ncols2 == 1 && !mask ? nullptr :
12801329
(const half2 *) (mask + nb33*(sequence % ne33) + nb31*jt*ncols1);
1281-
float2 * dstk = ((float2 *) dst) + (sequence*ne01*ne02 + head*ncols2) * (DV/2);
1330+
float2 * dstk = ((float2 *) dst) + (sequence*ne01*ne02 + head0) * (DV/2);
12821331

1283-
const half2 * V_h2 = mla ? K_h2 + (DKQ/2 - DV/2) : (const half2 *) (V + nb23*sequence + nb22*(head*ncols2 / gqa_ratio));
1332+
const half2 * V_h2 = mla ? K_h2 + (DKQ/2 - DV/2) : (const half2 *) (V + nb23*sequence + nb22*(head0 / gqa_ratio));
1333+
const float * sinks_f = sinks ? (const float *) sinks + head0 : nullptr;
12841334

1285-
const float slope = ncols2 == 1 ? get_alibi_slope(max_bias, head, n_head_log2, m0, m1) : 1.0f;
1335+
const float slope = ncols2 == 1 ? get_alibi_slope(max_bias, head0, n_head_log2, m0, m1) : 1.0f;
12861336

12871337
const int kb0_start_kernel = kb0_start * kb_niter;
12881338
int kb0_stop_kernel = kb0_stop * kb_niter;
@@ -1295,12 +1345,12 @@ static __global__ void flash_attn_ext_f16(
12951345
if (kb0_start == 0) {
12961346
constexpr bool needs_fixup = false; // CUDA block is working on an entire tile.
12971347
flash_attn_ext_f16_process_tile<DKQ, DV, ncols1, ncols2, nwarps, ntiles, use_logit_softcap, mla, needs_fixup, is_fixup>
1298-
(Q_f2, K_h2, V_h2, mask_h2, dstk, dst_meta, scale, slope, logit_softcap,
1348+
(Q_f2, K_h2, V_h2, mask_h2, sinks_f, dstk, dst_meta, scale, slope, logit_softcap,
12991349
ne01, ne02, stride_Q1, stride_Q2, stride_K, stride_V, stride_mask, jt, kb0_start_kernel, kb0_stop_kernel);
13001350
} else {
13011351
constexpr bool needs_fixup = true; // CUDA block is working on the beginning of a tile.
13021352
flash_attn_ext_f16_process_tile<DKQ, DV, ncols1, ncols2, nwarps, ntiles, use_logit_softcap, mla, needs_fixup, is_fixup>
1303-
(Q_f2, K_h2, V_h2, mask_h2, dstk, dst_meta, scale, slope, logit_softcap,
1353+
(Q_f2, K_h2, V_h2, mask_h2, sinks_f, dstk, dst_meta, scale, slope, logit_softcap,
13041354
ne01, ne02, stride_Q1, stride_Q2, stride_K, stride_V, stride_mask, jt, kb0_start_kernel, kb0_stop_kernel);
13051355
}
13061356

@@ -1316,18 +1366,21 @@ static __global__ void flash_attn_ext_f16(
13161366
}
13171367

13181368
const int sequence = kbc / (iter_k*iter_j*(ne02/ncols2));
1319-
const int head = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j);
1320-
const int jt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*head) / iter_k; // j index of current tile.
1369+
const int zt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j); // head in units of ncols2
1370+
const int jt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*zt) / iter_k; // j index of current tile.
1371+
1372+
const int head0 = zt * ncols2;
13211373

1322-
const float2 * Q_f2 = (const float2 *) (Q + nb03*sequence + nb02*(head*ncols2));
1323-
const half2 * K_h2 = (const half2 *) (K + nb13*sequence + nb12*(head*ncols2 / gqa_ratio));
1374+
const float2 * Q_f2 = (const float2 *) (Q + nb03*sequence + nb02* head0);
1375+
const half2 * K_h2 = (const half2 *) (K + nb13*sequence + nb12*(head0 / gqa_ratio));
13241376
const half2 * mask_h2 = ncols2 == 1 && !mask ? nullptr :
13251377
(const half2 *) (mask + nb33*(sequence % ne33) + nb31*jt*ncols1);
1326-
float2 * dstk = ((float2 *) dst) + (sequence*ne01*ne02 + head*ncols2) * (DV/2);
1378+
float2 * dstk = ((float2 *) dst) + (sequence*ne01*ne02 + head0) * (DV/2);
13271379

1328-
const half2 * V_h2 = mla ? K_h2 + (DKQ/2 - DV/2) : (const half2 *) (V + nb23*sequence + nb22*(head*ncols2 / gqa_ratio));
1380+
const half2 * V_h2 = mla ? K_h2 + (DKQ/2 - DV/2) : (const half2 *) (V + nb23*sequence + nb22*(head0 / gqa_ratio));
1381+
const float * sinks_f = sinks ? (const float *) sinks + head0 : nullptr;
13291382

1330-
const float slope = ncols2 == 1 ? get_alibi_slope(max_bias, head, n_head_log2, m0, m1) : 1.0f;
1383+
const float slope = ncols2 == 1 ? get_alibi_slope(max_bias, head0, n_head_log2, m0, m1) : 1.0f;
13311384

13321385
const int kb0_start_kernel = kb0_start * kb_niter;
13331386
int kb0_stop_kernel = kb0_stop * kb_niter;
@@ -1339,7 +1392,7 @@ static __global__ void flash_attn_ext_f16(
13391392
constexpr bool is_fixup = true; // Last index writes its data to fixup buffer to avoid data races with other blocks.
13401393
constexpr bool needs_fixup = false;
13411394
flash_attn_ext_f16_process_tile<DKQ, DV, ncols1, ncols2, nwarps, ntiles, use_logit_softcap, mla, needs_fixup, is_fixup>
1342-
(Q_f2, K_h2, V_h2, mask_h2, dstk, dst_meta, scale, slope, logit_softcap,
1395+
(Q_f2, K_h2, V_h2, mask_h2, sinks_f, dstk, dst_meta, scale, slope, logit_softcap,
13431396
ne01, ne02, stride_Q1, stride_Q2, stride_K, stride_V, stride_mask, jt, kb0_start_kernel, kb0_stop_kernel);
13441397
#else
13451398
GGML_UNUSED(Q); GGML_UNUSED(K); GGML_UNUSED(V); GGML_UNUSED(mask); GGML_UNUSED(sinks);

ggml/src/ggml-cuda/fattn.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ void ggml_cuda_flash_attn_ext(ggml_backend_cuda_context & ctx, ggml_tensor * dst
282282
const enum ggml_prec prec = ggml_flash_attn_ext_get_prec(KQV);
283283

284284
// TODO: currently only vec implementation for sinks is supported [TAG_ATTN_SINKS]
285-
if (sinks) {
285+
if (sinks && !fp16_mma_available(cc)) {
286286
if (prec == GGML_PREC_DEFAULT && fast_fp16_available(cc)) {
287287
ggml_cuda_flash_attn_ext_vec_f16(ctx, dst);
288288
} else {

ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3537,7 +3537,8 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
35373537
return op->src[1]->ne[0] == 576 && op->src[2]->ne[0] == 512 && op->src[3] && gqa_ratio % 16 == 0;
35383538
}
35393539
// TODO: more general-purpose attention sink support [TAG_ATTN_SINKS]
3540-
if (op->src[4] && op->src[0]->ne[0] != 64 && op->src[0]->ne[0] != 128) { // currently only sinks for head_size 64 and 128 are supported
3540+
if (op->src[4] && !fp16_mma_available(ggml_cuda_info().devices[dev_ctx->device].cc)
3541+
&& op->src[0]->ne[0] != 64 && op->src[0]->ne[0] != 128) {
35413542
return false;
35423543
}
35433544
if (op->src[0]->ne[0] == 192) {
@@ -3804,10 +3805,10 @@ ggml_backend_t ggml_backend_cuda_init(int device) {
38043805
}
38053806

38063807
ggml_backend_t cuda_backend = new ggml_backend {
3807-
/* .guid = */ ggml_backend_cuda_guid(),
3808-
/* .interface = */ ggml_backend_cuda_interface,
3809-
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cuda_reg(), device),
3810-
/* .context = */ ctx,
3808+
/* .guid = */ ggml_backend_cuda_guid(),
3809+
/* .iface = */ ggml_backend_cuda_interface,
3810+
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cuda_reg(), device),
3811+
/* .context = */ ctx,
38113812
};
38123813

38133814
return cuda_backend;

ggml/src/ggml-vulkan/ggml-vulkan.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10797,10 +10797,10 @@ ggml_backend_t ggml_backend_vk_init(size_t dev_num) {
1079710797
ggml_vk_init(ctx, dev_num);
1079810798

1079910799
ggml_backend_t vk_backend = new ggml_backend {
10800-
/* .guid = */ ggml_backend_vk_guid(),
10801-
/* .interface = */ ggml_backend_vk_interface,
10802-
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_vk_reg(), dev_num),
10803-
/* .context = */ ctx,
10800+
/* .guid = */ ggml_backend_vk_guid(),
10801+
/* .iface = */ ggml_backend_vk_interface,
10802+
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_vk_reg(), dev_num),
10803+
/* .context = */ ctx,
1080410804
};
1080510805

1080610806
return vk_backend;

koboldcpp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
extra_images_max = 4
6464

6565
# global vars
66-
KcppVersion = "1.97.1"
66+
KcppVersion = "1.97.2"
6767
showdebug = True
6868
kcpp_instance = None #global running instance
6969
global_memory = {"tunnel_url": "", "restart_target":"", "input_to_exit":False, "load_complete":False, "restart_override_config_target":""}

vendor/minja/chat-template.hpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,15 @@ class chat_template {
162162
}), false);
163163
caps_.supports_tools = contains(out, "some_tool");
164164

165-
auto out_empty = try_raw_render(json::array({dummy_user_msg, {{"role", "assistant"}, {"content", ""}}}), {}, false);
166-
auto out_null = try_raw_render(json::array({dummy_user_msg, {{"role", "assistant"}, {"content", nullptr}}}), {}, false);
165+
const auto render_with_content = [&](const json & content) {
166+
const json assistant_msg {{"role", "assistant"}, {"content", content}};
167+
// Render two assistant messages as some templates like QwQ-32B are handling
168+
// the content differently depending on whether it's the last message or not
169+
// (to remove the <think> tag in all but the last message).
170+
return try_raw_render(json::array({dummy_user_msg, assistant_msg, dummy_user_msg, assistant_msg}), {}, false);
171+
};
172+
auto out_empty = render_with_content("");
173+
auto out_null = render_with_content(json());
167174
caps_.requires_non_null_content = contains(out_empty, user_needle) && !contains(out_null, user_needle);
168175

169176
json j_null;
@@ -191,12 +198,12 @@ class chat_template {
191198
dummy_user_msg,
192199
make_tool_calls_msg(json::array({make_tool_call("ipython", dummy_args_obj.dump())})),
193200
}), {}, false);
194-
auto tool_call_renders_str_arguments = contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':");
201+
auto tool_call_renders_str_arguments = contains(out, "<parameter=argument_needle>") || contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':");
195202
out = try_raw_render(json::array({
196203
dummy_user_msg,
197204
make_tool_calls_msg(json::array({make_tool_call("ipython", dummy_args_obj)})),
198205
}), {}, false);
199-
auto tool_call_renders_obj_arguments = contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':");
206+
auto tool_call_renders_obj_arguments = contains(out, "<parameter=argument_needle>") || contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':");
200207

201208
caps_.supports_tool_calls = tool_call_renders_str_arguments || tool_call_renders_obj_arguments;
202209
caps_.requires_object_arguments = !tool_call_renders_str_arguments && tool_call_renders_obj_arguments;

vendor/minja/minja.hpp

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,12 @@ class UnaryOpExpr : public Expression {
12911291
}
12921292
};
12931293

1294+
static bool in(const Value & value, const Value & container) {
1295+
return (((container.is_array() || container.is_object()) && container.contains(value)) ||
1296+
(value.is_string() && container.is_string() &&
1297+
container.to_str().find(value.to_str()) != std::string::npos));
1298+
}
1299+
12941300
class BinaryOpExpr : public Expression {
12951301
public:
12961302
enum class Op { StrConcat, Add, Sub, Mul, MulMul, Div, DivDiv, Mod, Eq, Ne, Lt, Gt, Le, Ge, And, Or, In, NotIn, Is, IsNot };
@@ -1355,13 +1361,8 @@ class BinaryOpExpr : public Expression {
13551361
case Op::Gt: return l > r;
13561362
case Op::Le: return l <= r;
13571363
case Op::Ge: return l >= r;
1358-
case Op::In: return (((r.is_array() || r.is_object()) && r.contains(l)) ||
1359-
(l.is_string() && r.is_string() &&
1360-
r.to_str().find(l.to_str()) != std::string::npos));
1361-
case Op::NotIn:
1362-
return !(((r.is_array() || r.is_object()) && r.contains(l)) ||
1363-
(l.is_string() && r.is_string() &&
1364-
r.to_str().find(l.to_str()) != std::string::npos));
1364+
case Op::In: return in(l, r);
1365+
case Op::NotIn: return !in(l, r);
13651366
default: break;
13661367
}
13671368
throw std::runtime_error("Unknown binary operator");
@@ -1500,6 +1501,13 @@ class MethodCallExpr : public Expression {
15001501
} else if (method->get_name() == "pop") {
15011502
vargs.expectArgs("pop method", {1, 1}, {0, 0});
15021503
return obj.pop(vargs.args[0]);
1504+
} else if (method->get_name() == "keys") {
1505+
vargs.expectArgs("keys method", {0, 0}, {0, 0});
1506+
auto result = Value::array();
1507+
for (const auto& key : obj.keys()) {
1508+
result.push_back(Value(key));
1509+
}
1510+
return result;
15031511
} else if (method->get_name() == "get") {
15041512
vargs.expectArgs("get method", {1, 2}, {0, 0});
15051513
auto key = vargs.args[0];
@@ -1541,6 +1549,16 @@ class MethodCallExpr : public Expression {
15411549
} else if (method->get_name() == "capitalize") {
15421550
vargs.expectArgs("capitalize method", {0, 0}, {0, 0});
15431551
return Value(capitalize(str));
1552+
} else if (method->get_name() == "upper") {
1553+
vargs.expectArgs("upper method", {0, 0}, {0, 0});
1554+
auto result = str;
1555+
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
1556+
return Value(result);
1557+
} else if (method->get_name() == "lower") {
1558+
vargs.expectArgs("lower method", {0, 0}, {0, 0});
1559+
auto result = str;
1560+
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
1561+
return Value(result);
15441562
} else if (method->get_name() == "endswith") {
15451563
vargs.expectArgs("endswith method", {1, 1}, {0, 0});
15461564
auto suffix = vargs.args[0].get<std::string>();
@@ -2646,15 +2664,11 @@ inline std::shared_ptr<Context> Context::builtins() {
26462664
auto items = Value::array();
26472665
if (args.contains("object")) {
26482666
auto & obj = args.at("object");
2649-
if (obj.is_string()) {
2650-
auto json_obj = json::parse(obj.get<std::string>());
2651-
for (const auto & kv : json_obj.items()) {
2652-
items.push_back(Value::array({kv.key(), kv.value()}));
2653-
}
2654-
} else if (!obj.is_null()) {
2655-
for (auto & key : obj.keys()) {
2656-
items.push_back(Value::array({key, obj.at(key)}));
2657-
}
2667+
if (!obj.is_object()) {
2668+
throw std::runtime_error("Can only get item pairs from a mapping");
2669+
}
2670+
for (auto & key : obj.keys()) {
2671+
items.push_back(Value::array({key, obj.at(key)}));
26582672
}
26592673
}
26602674
return items;
@@ -2782,6 +2796,9 @@ inline std::shared_ptr<Context> Context::builtins() {
27822796
if (!items.is_array()) throw std::runtime_error("object is not iterable");
27832797
return items;
27842798
}));
2799+
globals.set("in", simple_function("in", { "item", "items" }, [](const std::shared_ptr<Context> &, Value & args) -> Value {
2800+
return in(args.at("item"), args.at("items"));
2801+
}));
27852802
globals.set("unique", simple_function("unique", { "items" }, [](const std::shared_ptr<Context> &, Value & args) -> Value {
27862803
auto & items = args.at("items");
27872804
if (!items.is_array()) throw std::runtime_error("object is not iterable");

0 commit comments

Comments
 (0)