diff --git a/changes/internal/1665-arm-d-b5.md b/changes/internal/1665-arm-d-b5.md new file mode 100644 index 00000000..e88b9c7d --- /dev/null +++ b/changes/internal/1665-arm-d-b5.md @@ -0,0 +1 @@ +- Migrate tensor and model container reads and writes to the slot API. diff --git a/src/builtins_tensor.c b/src/builtins_tensor.c index 520b902b..937e1cda 100644 --- a/src/builtins_tensor.c +++ b/src/builtins_tensor.c @@ -226,14 +226,14 @@ static int tensor_dims(Value *v, int *rows, int *cols) { *rows = 1; *cols = v->data.buffer.count; return 1; } if (!v || v->type != VAL_LIST || v->data.list.count == 0) return 0; - Value *first = list_get_borrow(v, 0); - if (first->type == VAL_NUM) { + if (list_elem_type(v, 0) == VAL_NUM) { /* 1D tensor */ *rows = 1; *cols = v->data.list.count; return 1; } - if (first->type == VAL_LIST) { + Value *first = list_get_ref(v, 0); + if (first && first->type == VAL_LIST) { /* 2D tensor */ *rows = v->data.list.count; *cols = first->data.list.count; @@ -254,18 +254,20 @@ static double* tensor_to_flat(Value *v, int *rows, int *cols, * explicit EIGS_STRICT=0 path deliberately retains the old stand-in. * Check even when the first element or row gives no usable shape. */ /* #1637: a bool element is refused in every strict mode. */ -#define FLAT_BAD(e) ((e)->type != VAL_NUM && (g_strict || (e)->type == VAL_BOOL)) +#define FLAT_BAD(t) ((t) != VAL_NUM && (g_strict || (t) == VAL_BOOL)) if (v && v->type == VAL_LIST) { int bad = 0; if (ndim == 1) { for (int i = 0; i < v->data.list.count; i++) - if (FLAT_BAD(list_get_borrow(v, i))) { bad = 1; break; } + if (FLAT_BAD(list_elem_type(v, i))) { bad = 1; break; } } else { for (int r = 0; r < v->data.list.count && !bad; r++) { - Value *row = list_get_borrow(v, r); - if (row->type != VAL_LIST) { bad = g_strict || row->type == VAL_BOOL; if (bad) break; continue; } + /* list_get_ref is NULL for a number: test the type first. */ + ValType rt = list_elem_type(v, r); + if (rt != VAL_LIST) { bad = g_strict || rt == VAL_BOOL; if (bad) break; continue; } + Value *row = list_get_ref(v, r); for (int c = 0; c < row->data.list.count; c++) - if (FLAT_BAD(list_get_borrow(row, c))) { bad = 1; break; } + if (FLAT_BAD(list_elem_type(row, c))) { bad = 1; break; } } } #undef FLAT_BAD @@ -287,13 +289,14 @@ static double* tensor_to_flat(Value *v, int *rows, int *cols, } if (ndim == 1) { for (int i = 0; i < *cols; i++) - out[i] = (list_get_borrow(v, i)->type == VAL_NUM) ? eigs_num_arg(list_get_borrow(v, i), __func__) : 0.0; + out[i] = (list_elem_type(v, i) == VAL_NUM) ? eigs_slot_num(list_slot(v, i), __func__) : 0.0; } else { for (int r = 0; r < *rows; r++) { - Value *row = list_get_borrow(v, r); - int rc = (row->type == VAL_LIST) ? row->data.list.count : 0; + /* list_get_ref is NULL for a number: test the type first. */ + Value *row = (list_elem_type(v, r) == VAL_LIST) ? list_get_ref(v, r) : NULL; + int rc = row ? row->data.list.count : 0; for (int c = 0; c < *cols && c < rc; c++) - out[r * (*cols) + c] = (list_get_borrow(row, c)->type == VAL_NUM) ? eigs_num_arg(list_get_borrow(row, c), __func__) : 0.0; + out[r * (*cols) + c] = (list_elem_type(row, c) == VAL_NUM) ? eigs_slot_num(list_slot(row, c), __func__) : 0.0; } } return out; @@ -363,8 +366,10 @@ static int tensor_total(Value *v) { if (v->type == VAL_BUFFER) return v->data.buffer.count; /* #1093 */ if (v->type != VAL_LIST) return 0; int total = 0; - for (int i = 0; i < v->data.list.count; i++) - total += tensor_total(list_get_borrow(v, i)); + for (int i = 0; i < v->data.list.count; i++) { + EIGS_VIEW(child_view); + total += tensor_total(list_get_view(v, i, &child_view)); + } return total; } @@ -382,8 +387,10 @@ static int tensor_flatten_recursive(Value *v, double *out, int *idx) { return 1; } if (v->type != VAL_LIST) return 1; - for (int i = 0; i < v->data.list.count; i++) - if (!tensor_flatten_recursive(list_get_borrow(v, i), out, idx)) return 0; + for (int i = 0; i < v->data.list.count; i++) { + EIGS_VIEW(child_view); + if (!tensor_flatten_recursive(list_get_view(v, i, &child_view), out, idx)) return 0; + } return 1; } @@ -471,6 +478,7 @@ static Value* buf_scalar_elementwise(Value *buf, double sc, BinOpFn fn, int buf_ return out; } +static Value* tensor_elementwise_slots(EigsSlot a, EigsSlot b, BinOpFn fn); static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { /* Shared by add/subtract/multiply/divide/pow (and by its own recursion on * nested elements), so the guard names that surface rather than one call. @@ -512,7 +520,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { if (a->type == VAL_NUM && b->type == VAL_LIST) { Value *out = make_list(b->data.list.count); for (int i = 0; i < b->data.list.count; i++) { - Value *item = tensor_elementwise(a, list_get_borrow(b, i), fn); + Value *item = tensor_elementwise_slots(slot_from_heap(a), list_slot(b, i), fn); if (g_has_error) { val_decref(item); val_decref(out); return make_null(); } @@ -523,7 +531,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { if (a->type == VAL_LIST && b->type == VAL_NUM) { Value *out = make_list(a->data.list.count); for (int i = 0; i < a->data.list.count; i++) { - Value *item = tensor_elementwise(list_get_borrow(a, i), b, fn); + Value *item = tensor_elementwise_slots(list_slot(a, i), slot_from_heap(b), fn); if (g_has_error) { val_decref(item); val_decref(out); return make_null(); } @@ -538,16 +546,17 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { * Row-vector bias takes priority for square matrices, matching neural * layer convention: add of [batch @ weights, bias]. */ if (a->type == VAL_LIST && b->type == VAL_LIST) { - int a_is_matrix = a->data.list.count > 0 && list_get_borrow(a, 0)->type == VAL_LIST; - int b_is_matrix = b->data.list.count > 0 && list_get_borrow(b, 0)->type == VAL_LIST; + int a_is_matrix = a->data.list.count > 0 && list_elem_type(a, 0) == VAL_LIST; + int b_is_matrix = b->data.list.count > 0 && list_elem_type(b, 0) == VAL_LIST; if (a_is_matrix && !b_is_matrix) { int rows = a->data.list.count; - int cols = list_get_borrow(a, 0)->data.list.count; + int cols = list_get_ref(a, 0)->data.list.count; if (b->data.list.count == cols) { Value *out = make_list(rows); for (int i = 0; i < rows; i++) { - Value *row = tensor_elementwise(list_get_borrow(a, i), b, fn); + EigsView ai_view = {{0}}; /* a ragged row may be a number: view, not get_ref (NULL) */ + Value *row = tensor_elementwise(list_get_view(a, i, &ai_view), b, fn); if (g_has_error) { val_decref(row); val_decref(out); return make_null(); } @@ -559,7 +568,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { if (b->data.list.count == rows) { Value *out = make_list(rows); for (int i = 0; i < rows; i++) { - Value *row = tensor_elementwise(list_get_borrow(a, i), list_get_borrow(b, i), fn); + Value *row = tensor_elementwise_slots(list_slot(a, i), list_slot(b, i), fn); if (g_has_error) { val_decref(row); val_decref(out); return make_null(); } @@ -572,11 +581,12 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { if (!a_is_matrix && b_is_matrix) { int rows = b->data.list.count; - int cols = list_get_borrow(b, 0)->data.list.count; + int cols = list_get_ref(b, 0)->data.list.count; if (a->data.list.count == cols) { Value *out = make_list(rows); for (int i = 0; i < rows; i++) { - Value *row = tensor_elementwise(a, list_get_borrow(b, i), fn); + EigsView bi_view = {{0}}; /* a ragged row may be a number: view, not get_ref (NULL) */ + Value *row = tensor_elementwise(a, list_get_view(b, i, &bi_view), fn); if (g_has_error) { val_decref(row); val_decref(out); return make_null(); } @@ -588,7 +598,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { if (a->data.list.count == rows) { Value *out = make_list(rows); for (int i = 0; i < rows; i++) { - Value *row = tensor_elementwise(list_get_borrow(a, i), list_get_borrow(b, i), fn); + Value *row = tensor_elementwise_slots(list_slot(a, i), list_slot(b, i), fn); if (g_has_error) { val_decref(row); val_decref(out); return make_null(); } @@ -603,7 +613,7 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { int n = a->data.list.count < b->data.list.count ? a->data.list.count : b->data.list.count; Value *out = make_list(n); for (int i = 0; i < n; i++) { - Value *item = tensor_elementwise(list_get_borrow(a, i), list_get_borrow(b, i), fn); + Value *item = tensor_elementwise_slots(list_slot(a, i), list_slot(b, i), fn); if (g_has_error) { val_decref(item); val_decref(out); return make_null(); } @@ -619,6 +629,12 @@ static Value* tensor_elementwise(Value *a, Value *b, BinOpFn fn) { "numbers, lists or buffers as operands", make_num(0.0)); } +static Value* tensor_elementwise_slots(EigsSlot a, EigsSlot b, BinOpFn fn) { + EIGS_VIEW(av); + EIGS_VIEW(bv); + return tensor_elementwise(slot_view(a, &av), slot_view(b, &bv), fn); +} + /* ==== BUILTIN: add ==== */ Value* builtin_tensor_add(Value *arg) { STRICT_LIST_MAX(arg, 2, "add"); @@ -629,35 +645,35 @@ Value* builtin_tensor_add(Value *arg) { * integration by keeping THIS one — a builtin must not have two buffer * paths, and this is the one whose shape rules are the list path's, * container for container. */ - return tensor_elementwise(list_get_borrow(arg, 0), list_get_borrow(arg, 1), op_add); + return tensor_elementwise_slots(list_slot(arg, 0), list_slot(arg, 1), op_add); } /* ==== BUILTIN: subtract ==== */ Value* builtin_tensor_subtract(Value *arg) { STRICT_LIST_MAX(arg, 2, "subtract"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) return make_null(); - return tensor_elementwise(list_get_borrow(arg, 0), list_get_borrow(arg, 1), op_sub); + return tensor_elementwise_slots(list_slot(arg, 0), list_slot(arg, 1), op_sub); } /* ==== BUILTIN: multiply ==== */ Value* builtin_tensor_multiply(Value *arg) { STRICT_LIST_MAX(arg, 2, "multiply"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) return make_null(); - return tensor_elementwise(list_get_borrow(arg, 0), list_get_borrow(arg, 1), op_mul); + return tensor_elementwise_slots(list_slot(arg, 0), list_slot(arg, 1), op_mul); } /* ==== BUILTIN: divide ==== */ Value* builtin_tensor_divide(Value *arg) { STRICT_LIST_MAX(arg, 2, "divide"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) return make_null(); - return tensor_elementwise(list_get_borrow(arg, 0), list_get_borrow(arg, 1), op_div); + return tensor_elementwise_slots(list_slot(arg, 0), list_slot(arg, 1), op_div); } /* ==== BUILTIN: pow ==== */ Value* builtin_tensor_pow(Value *arg) { STRICT_LIST_MAX(arg, 2, "pow"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) return make_null(); - return tensor_elementwise(list_get_borrow(arg, 0), list_get_borrow(arg, 1), op_pow); + return tensor_elementwise_slots(list_slot(arg, 0), list_slot(arg, 1), op_pow); } /* ---- Element-wise unary op ---- */ @@ -675,8 +691,10 @@ static Value* tensor_unary(Value *v, UnaryOpFn fn) { } if (v->type == VAL_LIST) { Value *out = make_list(v->data.list.count); - for (int i = 0; i < v->data.list.count; i++) - list_append_owned(out, tensor_unary(list_get_borrow(v, i), fn)); + for (int i = 0; i < v->data.list.count; i++) { + EIGS_VIEW(child_view); + list_append_owned(out, tensor_unary(list_get_view(v, i, &child_view), fn)); + } return out; } /* Both handled types exit above, so `v` is neither a number nor a @@ -740,8 +758,10 @@ Value* builtin_tensor_matmul(Value *arg) { rt_error(EK_TYPE, 0, "matmul requires [A, B]"); return make_null(); } - Value *a = list_get_borrow(arg, 0); - Value *b = list_get_borrow(arg, 1); + EIGS_VIEW(a_view); + Value *a = list_get_view(arg, 0, &a_view); + EIGS_VIEW(b_view); + Value *b = list_get_view(arg, 1, &b_view); /* flat-buffer fast path: compute in place, no flatten/rebuild */ if (a->type == VAL_BUFFER && b->type == VAL_BUFFER) { int ar, ac, br, bc; @@ -832,8 +852,10 @@ Value* builtin_tensor_matmul_at(Value *arg) { rt_error(EK_TYPE, 0, "matmul_at requires [A, B]"); return make_null(); } - Value *a = list_get_borrow(arg, 0); - Value *b = list_get_borrow(arg, 1); + EIGS_VIEW(a_view); + Value *a = list_get_view(arg, 0, &a_view); + EIGS_VIEW(b_view); + Value *b = list_get_view(arg, 1, &b_view); if (a->type == VAL_BUFFER && b->type == VAL_BUFFER) { int ar, ac, br, bc; buf_dims(a, &ar, &ac); buf_dims(b, &br, &bc); @@ -890,8 +912,10 @@ Value* builtin_tensor_matmul_bt(Value *arg) { rt_error(EK_TYPE, 0, "matmul_bt requires [A, B]"); return make_null(); } - Value *a = list_get_borrow(arg, 0); - Value *b = list_get_borrow(arg, 1); + EIGS_VIEW(a_view); + Value *a = list_get_view(arg, 0, &a_view); + EIGS_VIEW(b_view); + Value *b = list_get_view(arg, 1, &b_view); if (a->type == VAL_BUFFER && b->type == VAL_BUFFER) { int ar, ac, br, bc; buf_dims(a, &ar, &ac); buf_dims(b, &br, &bc); @@ -954,9 +978,12 @@ Value* builtin_tensor_scatter_add(Value *arg) { rt_error(EK_TYPE, 0, "scatter_add requires [dst, indices, values]"); return make_null(); } - Value *dst = list_get_borrow(arg, 0); - Value *indices = list_get_borrow(arg, 1); - Value *values = list_get_borrow(arg, 2); + EIGS_VIEW(dst_view); + Value *dst = list_get_view(arg, 0, &dst_view); + EIGS_VIEW(indices_view); + Value *indices = list_get_view(arg, 1, &indices_view); + EIGS_VIEW(values_view); + Value *values = list_get_view(arg, 2, &values_view); if (dst->type != VAL_BUFFER) { rt_error(EK_TYPE, 0, "scatter_add: dst must be a buffer, got %s", val_type_name(dst->type)); return make_null(); @@ -995,12 +1022,12 @@ Value* builtin_tensor_scatter_add(Value *arg) { for (int i = 0; i < n; i++) { double di, v; if (indices->type == VAL_LIST) { - Value *iv = list_get_borrow(indices, i); - if (iv->type != VAL_NUM) { - rt_error(EK_TYPE, 0, "scatter_add: index %d is %s (expected a number)", i, val_type_name(iv->type)); + ValType it = list_elem_type(indices, i); + if (it != VAL_NUM) { + rt_error(EK_TYPE, 0, "scatter_add: index %d is %s (expected a number)", i, val_type_name(it)); return make_null(); } - di = eigs_num_arg(iv, __func__); + di = eigs_slot_num(list_slot(indices, i), __func__); } else { /* #1417: an index is a scalar read too. Guard before the * double-to-int conversion: casting a stored NaN is undefined @@ -1010,12 +1037,12 @@ Value* builtin_tensor_scatter_add(Value *arg) { if (g_has_error) return make_null(); } if (values->type == VAL_LIST) { - Value *vv = list_get_borrow(values, i); - if (vv->type != VAL_NUM) { - rt_error(EK_TYPE, 0, "scatter_add: value %d is %s (expected a number)", i, val_type_name(vv->type)); + ValType vt = list_elem_type(values, i); + if (vt != VAL_NUM) { + rt_error(EK_TYPE, 0, "scatter_add: value %d is %s (expected a number)", i, val_type_name(vt)); return make_null(); } - v = eigs_num_arg(vv, __func__); + v = eigs_slot_num(list_slot(values, i), __func__); } else if (values->type == VAL_BUFFER) { v = values->data.buffer.data[i]; } else { @@ -1054,7 +1081,7 @@ Value* builtin_tensor_scatter_add(Value *arg) { } } } - return dst; /* borrowed, like copy_into — the VM's borrow scan compensates */ + return list_get_return(arg, 0); /* borrowed, like copy_into — the VM's borrow scan compensates */ } /* ==== BUILTIN: softmax ==== */ @@ -1094,9 +1121,9 @@ Value* builtin_tensor_log_softmax(Value *arg) { * a row (a list), never a number, so the two forms no longer collide. */ Value *tensor = arg; if (arg && arg->type == VAL_LIST && arg->data.list.count == 2 && - list_get_borrow(arg, 0)->type == VAL_LIST && - list_get_borrow(arg, 1)->type == VAL_NUM) - tensor = list_get_borrow(arg, 0); /* [tensor, dim] form */ + list_elem_type(arg, 0) == VAL_LIST && + list_elem_type(arg, 1) == VAL_NUM) + tensor = list_get_ref(arg, 0); /* [tensor, dim] form */ /* #632: log(softmax(scalar)) = log(1) = 0. */ /* fs:ANSWER softmax of a single element is 1 and log(1) is 0, so 0.0 is * the arithmetic result for a scalar argument — a NUMBER is a valid @@ -1335,8 +1362,8 @@ Value* builtin_tensor_zeros(Value *arg) { } /* zeros of [rows, cols] → 2D */ if (arg->type == VAL_LIST && arg->data.list.count >= 2 - && list_get_borrow(arg, 0)->type == VAL_NUM - && list_get_borrow(arg, 1)->type == VAL_NUM) { + && list_elem_type(arg, 0) == VAL_NUM + && list_elem_type(arg, 1) == VAL_NUM) { int64_t rows64 = (int64_t)eigs_list_num(arg, 0, __func__); int64_t cols64 = (int64_t)eigs_list_num(arg, 1, __func__); if (rows64 < 0) rows64 = 0; @@ -1369,8 +1396,10 @@ Value* builtin_tensor_zeros_like(Value *arg) { if (arg->type == VAL_NUM) return make_num(0.0); if (arg->type == VAL_LIST) { Value *out = make_list(arg->data.list.count); - for (int i = 0; i < arg->data.list.count; i++) - list_append_owned(out, builtin_tensor_zeros_like(list_get_borrow(arg, i))); + for (int i = 0; i < arg->data.list.count; i++) { + EIGS_VIEW(child_view); + list_append_owned(out, builtin_tensor_zeros_like(list_get_view(arg, i, &child_view))); + } return out; } /* #1093: a buffer's zero is a zero BUFFER of the same shape, not the @@ -1398,8 +1427,8 @@ static int flat_is_vector(Value *v) { } static int flat_index_at(Value *v, int i) { if (v->type == VAL_LIST) - return (list_get_borrow(v, i)->type == VAL_NUM) - ? (int)eigs_num_arg(list_get_borrow(v, i), __func__) : -1; + return (list_elem_type(v, i) == VAL_NUM) + ? (int)eigs_slot_num(list_slot(v, i), __func__) : -1; /* #1417: normalize before conversion, and let every caller propagate a * raised read before using its index or touching an output. Saturated * infinity is still outside the int domain and uses the existing invalid @@ -1413,7 +1442,7 @@ static int flat_index_at(Value *v, int i) { * as "index -1 out of range" would name the wrong fault. Buffers hold doubles, * so every element is a number by construction. */ static int flat_index_is_num(Value *v, int i) { - return v->type != VAL_LIST || list_get_borrow(v, i)->type == VAL_NUM; + return v->type != VAL_LIST || list_elem_type(v, i) == VAL_NUM; } /* ==== BUILTIN: gather ==== */ @@ -1448,8 +1477,10 @@ static int flat_index_is_num(Value *v, int i) { Value* builtin_tensor_gather(Value *arg) { STRICT_LIST_MAX(arg, 2, "gather"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 2) return make_null(); - Value *tensor = list_get_borrow(arg, 0); - Value *indices = list_get_borrow(arg, 1); + EIGS_VIEW(tensor_view); + Value *tensor = list_get_view(arg, 0, &tensor_view); + EIGS_VIEW(indices_view); + Value *indices = list_get_view(arg, 1, &indices_view); /* #1093 + #973: a buffer tensor. A shaped (2-D) buffer with one index per * row selects one element per row and yields a buffer; an unshaped (1-D) * buffer with a scalar index yields that element. */ @@ -1475,7 +1506,7 @@ Value* builtin_tensor_gather(Value *arg) { if (!flat_index_is_num(indices, i)) { val_decref(out); rt_error(EK_TYPE, 0, "gather: index %d is %s (expected a number)", - i, val_type_name(list_get_borrow(indices, i)->type)); + i, val_type_name(list_elem_type(indices, i))); return make_null(); } int idx = flat_index_at(indices, i); @@ -1498,24 +1529,26 @@ Value* builtin_tensor_gather(Value *arg) { ? tensor->data.list.count : indices->data.list.count; Value *out = make_list(n); for (int i = 0; i < n; i++) { - Value *row = list_get_borrow(tensor, i); - if (row->type != VAL_LIST) { /* not a matrix row — shape, not index */ + /* list_get_ref is NULL for a number: test the type first. */ + ValType row_t = list_elem_type(tensor, i); + if (row_t != VAL_LIST) { /* not a matrix row — shape, not index */ /* #1637: a bool where a row belongs raises, in every mode. */ - if (row->type == VAL_BOOL) { + if (row_t == VAL_BOOL) { val_decref(out); - eigs_num_arg_slow(row, "gather"); + eigs_num_arg_slow(list_get_ref(tensor, i), "gather"); return make_null(); } list_append_owned(out, make_num(0.0)); continue; } - if (list_get_borrow(indices, i)->type != VAL_NUM) { + Value *row = list_get_ref(tensor, i); + if (list_elem_type(indices, i) != VAL_NUM) { val_decref(out); rt_error(EK_TYPE, 0, "gather: index %d is %s (expected a number)", - i, val_type_name(list_get_borrow(indices, i)->type)); + i, val_type_name(list_elem_type(indices, i))); return make_null(); } - int idx = (int)eigs_num_arg(list_get_borrow(indices, i), __func__); + int idx = (int)eigs_slot_num(list_slot(indices, i), __func__); if (idx < 0 || idx >= row->data.list.count) { val_decref(out); rt_error(EK_INDEX, 0, @@ -1525,7 +1558,7 @@ Value* builtin_tensor_gather(Value *arg) { } /* #1637 round 4: the selected cell is read as a number -- a * bool there raised nothing and read as 0 (critic r3). */ - double cell = eigs_elem_num(list_get_borrow(row, idx), "gather"); + double cell = eigs_slot_elem_num(list_slot(row, idx), "gather"); if (g_has_error) { val_decref(out); return make_null(); } list_append_owned(out, make_num(cell)); } @@ -1539,7 +1572,7 @@ Value* builtin_tensor_gather(Value *arg) { idx, tensor->data.list.count); return make_null(); } - double cell = eigs_elem_num(list_get_borrow(tensor, idx), "gather"); /* #1637 round 4 */ + double cell = eigs_slot_elem_num(list_slot(tensor, idx), "gather"); /* #1637 round 4 */ if (g_has_error) return make_null(); return make_num(cell); } @@ -1592,7 +1625,7 @@ Value* call_eigs_fn(Value *fn, Value *arg) { if (pc > 1 && arg && arg->type == VAL_LIST) { int n = arg->data.list.count; for (int pi = 0; pi < pc && pi < n; pi++) - env_set_local(call_env, fn->data.fn.params[pi], list_get_borrow(arg, pi)); + env_set_local_owned(call_env, fn->data.fn.params[pi], list_get_owned(arg, pi)); /* Under-arity null-fill, matching the VM. Leaving the tail unbound * is not the same thing: an unbound name resolves through the * CLOSURE, so a 2-param key over 1-wide elements could silently read @@ -1710,8 +1743,9 @@ Value* builtin_tensor_shape(Value *arg) { list_append_owned(out, make_num(0)); return out; } - Value *first = list_get_borrow(arg, 0); - if (first->type == VAL_LIST) { + if (list_elem_type(arg, 0) == VAL_LIST) { + EIGS_VIEW(first_view); + Value *first = list_get_view(arg, 0, &first_view); /* 2D */ Value *out = make_list(2); list_append_owned(out, make_num(arg->data.list.count)); @@ -1761,15 +1795,16 @@ static double numerical_loss(Value *loss_fn, Value *arg, const char *who, static int tensor_cells_numeric(const Value *v, const char *who) { if (!v || v->type != VAL_LIST) return 1; for (int i = 0; i < v->data.list.count; i++) { - const Value *e = list_get_borrow(v, i); - if (e && e->type == VAL_LIST) { + ValType et = list_elem_type(v, i); + const Value *e = et == VAL_LIST ? list_get_ref(v, i) : NULL; + if (et == VAL_LIST) { for (int c = 0; c < e->data.list.count; c++) - if (!list_get_borrow(e, c) || list_get_borrow(e, c)->type != VAL_NUM) { - eigs_num_arg(list_get_borrow(e, c), who); + if (list_elem_type(e, c) != VAL_NUM) { + eigs_slot_num(list_slot(e, c), who); return 0; } - } else if (!e || e->type != VAL_NUM) { - eigs_num_arg(e, who); + } else if (et != VAL_NUM) { + eigs_slot_num(list_slot(v, i), who); return 0; } } @@ -1779,12 +1814,14 @@ static int tensor_cells_numeric(const Value *v, const char *who) { Value* builtin_numerical_grad(Value *arg) { STRICT_LIST_MAX(arg, 3, "numerical_grad"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 3) return make_null(); - Value *loss_fn = list_get_borrow(arg, 0); - Value *param = list_get_borrow(arg, 1); + EIGS_VIEW(loss_fn_view); + Value *loss_fn = list_get_view(arg, 0, &loss_fn_view); + EIGS_VIEW(param_view); + Value *param = list_get_view(arg, 1, ¶m_view); /* #1637: the optional eps: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double eps = 0.001; - eigs_opt_num(list_get_borrow(arg, 2), &eps, __func__); + eigs_slot_opt_num(list_slot(arg, 2), &eps, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(param, "numerical_grad")) return make_null(); if (eps <= 0) eps = 0.001; @@ -1813,26 +1850,19 @@ Value* builtin_numerical_grad(Value *arg) { Value *nul = make_null(); /* shared arg for loss_fn calls */ /* Check if 1D or 2D */ - int is_2d = (param->data.list.count > 0 && list_get_borrow(param, 0)->type == VAL_LIST); + int is_2d = (param->data.list.count > 0 && list_elem_type(param, 0) == VAL_LIST); if (!is_2d) { /* 1D param */ int len = param->data.list.count; Value *grad = make_list(len); for (int i = 0; i < len; i++) { - Value *orig = list_get_borrow(param, i); - double old_val = eigs_num_arg(orig, __func__); - val_incref(orig); /* guard while displaced from its slot */ - Value *pp = make_num(old_val + eps); /* birth ref doubles as slot ref */ - list_set_owned(param, i, pp); + double old_val = eigs_slot_num(list_slot(param, i), __func__); + list_set_num(param, i, old_val + eps); double lp = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); - Value *pm = make_num(old_val - eps); - list_set_owned(param, i, pm); - val_decref(pp); + list_set_num(param, i, old_val - eps); double lm = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); - list_set_owned(param, i, orig); - val_decref(orig); /* drop the guard */ - val_decref(pm); + list_set_num(param, i, old_val); /* Central difference */ list_append_owned(grad, make_num((lp - lm) / (2.0 * eps))); } @@ -1845,24 +1875,17 @@ Value* builtin_numerical_grad(Value *arg) { int rows = param->data.list.count; Value *grad = make_list(rows); for (int r = 0; r < rows; r++) { - Value *row = list_get_borrow(param, r); + Value *row = list_get_ref(param, r); if (!row || row->type != VAL_LIST) { list_append_owned(grad, make_list(0)); continue; } int cols = row->data.list.count; Value *grad_row = make_list(cols); for (int c = 0; c < cols; c++) { - Value *orig = list_get_borrow(row, c); - double old_val = eigs_num_arg(orig, __func__); - val_incref(orig); /* guard while displaced from its slot */ - Value *pp = make_num(old_val + eps); /* birth ref doubles as slot ref */ - list_set_owned(row, c, pp); + double old_val = eigs_slot_num(list_slot(row, c), __func__); + list_set_num(row, c, old_val + eps); double lp = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); - Value *pm = make_num(old_val - eps); - list_set_owned(row, c, pm); - val_decref(pp); + list_set_num(row, c, old_val - eps); double lm = numerical_loss(loss_fn, nul, "numerical_grad", &loss_valid); - list_set_owned(row, c, orig); - val_decref(orig); /* drop the guard */ - val_decref(pm); + list_set_num(row, c, old_val); list_append_owned(grad_row, make_num((lp - lm) / (2.0 * eps))); } list_append_owned(grad, grad_row); @@ -1877,12 +1900,14 @@ Value* builtin_numerical_grad(Value *arg) { Value* builtin_sgd_update(Value *arg) { STRICT_LIST_MAX(arg, 3, "sgd_update"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 3) return make_null(); - Value *param = list_get_borrow(arg, 0); - Value *grad = list_get_borrow(arg, 1); + EIGS_VIEW(param_view); + Value *param = list_get_view(arg, 0, ¶m_view); + EIGS_VIEW(grad_view); + Value *grad = list_get_view(arg, 1, &grad_view); /* #1637: the optional lr: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double lr = 0.01; - eigs_opt_num(list_get_borrow(arg, 2), &lr, __func__); + eigs_slot_opt_num(list_slot(arg, 2), &lr, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(param, "sgd_update") || !tensor_cells_numeric(grad, "sgd_update")) return make_null(); @@ -1892,43 +1917,39 @@ Value* builtin_sgd_update(Value *arg) { ? param->data.buffer.count : grad->data.buffer.count; for (int i = 0; i < len; i++) param->data.buffer.data[i] -= lr * grad->data.buffer.data[i]; - return param; + return list_get_return(arg, 0); } - if (param->type != VAL_LIST || grad->type != VAL_LIST) return param; + if (param->type != VAL_LIST || grad->type != VAL_LIST) return list_get_return(arg, 0); - int is_2d = (param->data.list.count > 0 && list_get_borrow(param, 0)->type == VAL_LIST); + int is_2d = (param->data.list.count > 0 && list_elem_type(param, 0) == VAL_LIST); if (!is_2d) { /* 1D */ int len = param->data.list.count < grad->data.list.count ? param->data.list.count : grad->data.list.count; for (int i = 0; i < len; i++) { - Value *old = list_get_borrow(param, i); - double pv = eigs_num_arg(old, __func__); - double gv = eigs_num_arg(list_get_borrow(grad, i), __func__); - list_set_owned(param, i, make_num(pv - lr * gv)); - val_decref(old); + double pv = eigs_slot_num(list_slot(param, i), __func__); + double gv = eigs_slot_num(list_slot(grad, i), __func__); + list_set_num(param, i, pv - lr * gv); } } else { /* 2D */ int rows = param->data.list.count < grad->data.list.count ? param->data.list.count : grad->data.list.count; for (int r = 0; r < rows; r++) { - Value *pr = list_get_borrow(param, r); - Value *gr = list_get_borrow(grad, r); + Value *pr = list_get_ref(param, r); + Value *gr = list_get_ref(grad, r); if (!pr || pr->type != VAL_LIST || !gr || gr->type != VAL_LIST) continue; int cols = pr->data.list.count < gr->data.list.count ? pr->data.list.count : gr->data.list.count; for (int c = 0; c < cols; c++) { - Value *old = list_get_borrow(pr, c); - double pv = eigs_num_arg(old, __func__); - double gv = eigs_num_arg(list_get_borrow(gr, c), __func__); - list_set_owned(pr, c, make_num(pv - lr * gv)); - val_decref(old); + double pv = eigs_slot_num(list_slot(pr, c), __func__); + double gv = eigs_slot_num(list_slot(gr, c), __func__); + list_set_num(pr, c, pv - lr * gv); } } } - return param; + return list_get_return(arg, 0); } /* ==== BUILTIN: numerical_grad_rows ==== */ @@ -1939,13 +1960,16 @@ Value* builtin_sgd_update(Value *arg) { Value* builtin_numerical_grad_rows(Value *arg) { STRICT_LIST_MAX(arg, 4, "numerical_grad_rows"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 4) return make_null(); - Value *loss_fn = list_get_borrow(arg, 0); - Value *matrix = list_get_borrow(arg, 1); - Value *row_indices = list_get_borrow(arg, 2); + EIGS_VIEW(loss_fn_view); + Value *loss_fn = list_get_view(arg, 0, &loss_fn_view); + EIGS_VIEW(matrix_view); + Value *matrix = list_get_view(arg, 1, &matrix_view); + EIGS_VIEW(row_indices_view); + Value *row_indices = list_get_view(arg, 2, &row_indices_view); /* #1637: the optional eps: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double eps = 0.001; - eigs_opt_num(list_get_borrow(arg, 3), &eps, __func__); + eigs_slot_opt_num(list_slot(arg, 3), &eps, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "numerical_grad_rows") || !tensor_cells_numeric(row_indices, "numerical_grad_rows")) return make_null(); if (eps <= 0) eps = 0.001; @@ -1987,8 +2011,8 @@ Value* builtin_numerical_grad_rows(Value *arg) { if (matrix->type != VAL_LIST || !flat_is_vector(row_indices)) return make_null(); int rows = matrix->data.list.count; - if (rows == 0 || list_get_borrow(matrix, 0)->type != VAL_LIST) return make_null(); - int cols = list_get_borrow(matrix, 0)->data.list.count; + if (rows == 0 || list_elem_type(matrix, 0) != VAL_LIST) return make_null(); + int cols = list_get_ref(matrix, 0)->data.list.count; Value *nul = make_null(); /* shared arg for loss_fn calls */ /* Build zero gradient matrix */ @@ -2010,21 +2034,19 @@ Value* builtin_numerical_grad_rows(Value *arg) { } if (r < 0 || r >= rows) continue; - Value *row = list_get_borrow(matrix, r); + Value *row = list_get_ref(matrix, r); if (!row || row->type != VAL_LIST) continue; - Value *grad_row = list_get_borrow(grad, r); + Value *grad_row = list_get_ref(grad, r); for (int c = 0; c < cols && c < row->data.list.count; c++) { - Value *cell = list_get_borrow(row, c); - double old_val = eigs_num_arg(cell, __func__); - VAL_NUM_RAW(cell) = old_val + eps; + double old_val = eigs_slot_num(list_slot(row, c), __func__); + list_set_num(row, c, old_val + eps); double loss_plus = numerical_loss(loss_fn, nul, "numerical_grad_rows", &loss_valid); - VAL_NUM_RAW(cell) = old_val - eps; + list_set_num(row, c, old_val - eps); double loss_minus = numerical_loss(loss_fn, nul, "numerical_grad_rows", &loss_valid); - VAL_NUM_RAW(cell) = old_val; + list_set_num(row, c, old_val); /* gradient — release the zero placeholder this slot held */ - val_decref(list_get_borrow(grad_row, c)); - list_set_owned(grad_row, c, make_num((loss_plus - loss_minus) / (2.0 * eps))); + list_set_num(grad_row, c, (loss_plus - loss_minus) / (2.0 * eps)); } } val_decref(nul); @@ -2038,13 +2060,16 @@ Value* builtin_numerical_grad_rows(Value *arg) { Value* builtin_sgd_update_rows(Value *arg) { STRICT_LIST_MAX(arg, 4, "sgd_update_rows"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 4) return make_null(); - Value *matrix = list_get_borrow(arg, 0); - Value *grad = list_get_borrow(arg, 1); - Value *row_indices = list_get_borrow(arg, 2); + EIGS_VIEW(matrix_view); + Value *matrix = list_get_view(arg, 0, &matrix_view); + EIGS_VIEW(grad_view); + Value *grad = list_get_view(arg, 1, &grad_view); + EIGS_VIEW(row_indices_view); + Value *row_indices = list_get_view(arg, 2, &row_indices_view); /* #1637: the optional lr: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double lr = 0.01; - eigs_opt_num(list_get_borrow(arg, 3), &lr, __func__); + eigs_slot_opt_num(list_slot(arg, 3), &lr, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "sgd_update_rows") || !tensor_cells_numeric(grad, "sgd_update_rows") || !tensor_cells_numeric(row_indices, "sgd_update_rows")) return make_null(); @@ -2064,31 +2089,29 @@ Value* builtin_sgd_update_rows(Value *arg) { matrix->data.buffer.data[(int64_t)r * matrix->data.buffer.cols + c] -= lr * grad->data.buffer.data[(int64_t)r * grad->data.buffer.cols + c]; } - return matrix; + return list_get_return(arg, 0); } if (matrix->type != VAL_LIST || grad->type != VAL_LIST || !flat_is_vector(row_indices)) - return matrix; + return list_get_return(arg, 0); for (int ri = 0; ri < flat_count(row_indices); ri++) { int r = flat_index_at(row_indices, ri); if (g_has_error) return make_null(); if (r < 0 || r >= matrix->data.list.count || r >= grad->data.list.count) continue; - Value *mrow = list_get_borrow(matrix, r); - Value *grow = list_get_borrow(grad, r); + Value *mrow = list_get_ref(matrix, r); + Value *grow = list_get_ref(grad, r); if (!mrow || mrow->type != VAL_LIST || !grow || grow->type != VAL_LIST) continue; int cols = mrow->data.list.count < grow->data.list.count ? mrow->data.list.count : grow->data.list.count; for (int c = 0; c < cols; c++) { - Value *old = list_get_borrow(mrow, c); - double pv = eigs_num_arg(old, __func__); - double gv = eigs_num_arg(list_get_borrow(grow, c), __func__); - list_set_owned(mrow, c, make_num(pv - lr * gv)); - val_decref(old); + double pv = eigs_slot_num(list_slot(mrow, c), __func__); + double gv = eigs_slot_num(list_slot(grow, c), __func__); + list_set_num(mrow, c, pv - lr * gv); } } - return matrix; + return list_get_return(arg, 0); } /* ==== BUILTIN: numerical_grad_cols ==== */ @@ -2099,13 +2122,16 @@ Value* builtin_sgd_update_rows(Value *arg) { Value* builtin_numerical_grad_cols(Value *arg) { STRICT_LIST_MAX(arg, 4, "numerical_grad_cols"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 4) return make_null(); - Value *loss_fn = list_get_borrow(arg, 0); - Value *matrix = list_get_borrow(arg, 1); - Value *col_indices = list_get_borrow(arg, 2); + EIGS_VIEW(loss_fn_view); + Value *loss_fn = list_get_view(arg, 0, &loss_fn_view); + EIGS_VIEW(matrix_view); + Value *matrix = list_get_view(arg, 1, &matrix_view); + EIGS_VIEW(col_indices_view); + Value *col_indices = list_get_view(arg, 2, &col_indices_view); /* #1637: the optional eps: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double eps = 0.001; - eigs_opt_num(list_get_borrow(arg, 3), &eps, __func__); + eigs_slot_opt_num(list_slot(arg, 3), &eps, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "numerical_grad_cols") || !tensor_cells_numeric(col_indices, "numerical_grad_cols")) return make_null(); if (eps <= 0) eps = 0.001; @@ -2145,8 +2171,8 @@ Value* builtin_numerical_grad_cols(Value *arg) { if (matrix->type != VAL_LIST || !flat_is_vector(col_indices)) return make_null(); int rows = matrix->data.list.count; - if (rows == 0 || list_get_borrow(matrix, 0)->type != VAL_LIST) return make_null(); - int cols = list_get_borrow(matrix, 0)->data.list.count; + if (rows == 0 || list_elem_type(matrix, 0) != VAL_LIST) return make_null(); + int cols = list_get_ref(matrix, 0)->data.list.count; Value *nul = make_null(); /* shared arg for loss_fn calls */ /* Build zero gradient matrix */ @@ -2169,28 +2195,21 @@ Value* builtin_numerical_grad_cols(Value *arg) { if (col < 0 || col >= cols) continue; for (int r = 0; r < rows; r++) { - Value *row = list_get_borrow(matrix, r); + Value *row = list_get_ref(matrix, r); if (!row || row->type != VAL_LIST || col >= row->data.list.count) continue; - Value *orig = list_get_borrow(row, col); - double old_val = eigs_num_arg(orig, __func__); - val_incref(orig); /* guard while displaced from its slot */ + double old_val = eigs_slot_num(list_slot(row, col), __func__); /* +eps */ - Value *pp = make_num(old_val + eps); /* birth ref doubles as slot ref */ - list_set_owned(row, col, pp); + list_set_num(row, col, old_val + eps); double loss_plus = numerical_loss(loss_fn, nul, "numerical_grad_cols", &loss_valid); /* -eps */ - Value *pm = make_num(old_val - eps); - list_set_owned(row, col, pm); - val_decref(pp); + list_set_num(row, col, old_val - eps); double loss_minus = numerical_loss(loss_fn, nul, "numerical_grad_cols", &loss_valid); /* restore */ - list_set_owned(row, col, orig); - val_decref(orig); /* drop the guard */ - val_decref(pm); + list_set_num(row, col, old_val); /* gradient — release the zero placeholder this slot held */ - val_decref(list_get_borrow(list_get_borrow(grad, r), col)); - list_set_owned(list_get_borrow(grad, r), col, make_num((loss_plus - loss_minus) / (2.0 * eps))); + Value *grad_row = list_get_ref(grad, r); + list_set_num(grad_row, col, (loss_plus - loss_minus) / (2.0 * eps)); } } val_decref(nul); @@ -2204,13 +2223,16 @@ Value* builtin_numerical_grad_cols(Value *arg) { Value* builtin_sgd_update_cols(Value *arg) { STRICT_LIST_MAX(arg, 4, "sgd_update_cols"); if (!arg || arg->type != VAL_LIST || arg->data.list.count < 4) return make_null(); - Value *matrix = list_get_borrow(arg, 0); - Value *grad = list_get_borrow(arg, 1); - Value *col_indices = list_get_borrow(arg, 2); + EIGS_VIEW(matrix_view); + Value *matrix = list_get_view(arg, 0, &matrix_view); + EIGS_VIEW(grad_view); + Value *grad = list_get_view(arg, 1, &grad_view); + EIGS_VIEW(col_indices_view); + Value *col_indices = list_get_view(arg, 2, &col_indices_view); /* #1637: the optional lr: null keeps the default, a number sets it, * anything else (a bool) raises instead of silently meaning the default. */ double lr = 0.01; - eigs_opt_num(list_get_borrow(arg, 3), &lr, __func__); + eigs_slot_opt_num(list_slot(arg, 3), &lr, __func__); if (g_has_error) return make_null(); if (!tensor_cells_numeric(matrix, "sgd_update_cols") || !tensor_cells_numeric(grad, "sgd_update_cols") || !tensor_cells_numeric(col_indices, "sgd_update_cols")) return make_null(); @@ -2229,10 +2251,10 @@ Value* builtin_sgd_update_cols(Value *arg) { matrix->data.buffer.data[(int64_t)r * matrix->data.buffer.cols + col] -= lr * grad->data.buffer.data[(int64_t)r * grad->data.buffer.cols + col]; } - return matrix; + return list_get_return(arg, 0); } if (matrix->type != VAL_LIST || grad->type != VAL_LIST || !flat_is_vector(col_indices)) - return matrix; + return list_get_return(arg, 0); int rows = matrix->data.list.count < grad->data.list.count ? matrix->data.list.count : grad->data.list.count; @@ -2243,27 +2265,27 @@ Value* builtin_sgd_update_cols(Value *arg) { if (col < 0) continue; for (int r = 0; r < rows; r++) { - Value *mrow = list_get_borrow(matrix, r); - Value *grow = list_get_borrow(grad, r); + Value *mrow = list_get_ref(matrix, r); + Value *grow = list_get_ref(grad, r); if (!mrow || mrow->type != VAL_LIST || col >= mrow->data.list.count) continue; if (!grow || grow->type != VAL_LIST || col >= grow->data.list.count) continue; - Value *old = list_get_borrow(mrow, col); - double pv = eigs_num_arg(old, __func__); - double gv = eigs_num_arg(list_get_borrow(grow, col), __func__); - list_set_owned(mrow, col, make_num(pv - lr * gv)); - val_decref(old); + double pv = eigs_slot_num(list_slot(mrow, col), __func__); + double gv = eigs_slot_num(list_slot(grow, col), __func__); + list_set_num(mrow, col, pv - lr * gv); } } - return matrix; + return list_get_return(arg, 0); } #if !EIGENSCRIPT_FREESTANDING Value* builtin_tensor_save(Value *arg) { STRICT_LIST_MAX(arg, 2, "tensor_save"); ARG_GUARD(!arg || arg->type != VAL_LIST || arg->data.list.count < 2, "tensor_save", "[tensor, path]", make_bool(0)); - Value *tensor = list_get_borrow(arg, 0); - Value *path_val = list_get_borrow(arg, 1); + EIGS_VIEW(tensor_view); + Value *tensor = list_get_view(arg, 0, &tensor_view); + EIGS_VIEW(path_val_view); + Value *path_val = list_get_view(arg, 1, &path_val_view); ARG_GUARD(!tensor || (tensor->type != VAL_LIST && tensor->type != VAL_BUFFER) || !path_val || path_val->type != VAL_STR, /* #1093 */ "tensor_save", "[a list or buffer tensor, a string path]", make_bool(0)); @@ -2378,8 +2400,8 @@ Value* builtin_tensor_load(Value *arg) { replayed_verdict = 1; if (verdict && verdict->type == VAL_LIST && verdict->data.list.count == 2 && - list_get_borrow(verdict, 0)->type == VAL_NUM && - list_get_borrow(verdict, 1)->type == VAL_NUM) { + list_elem_type(verdict, 0) == VAL_NUM && + list_elem_type(verdict, 1) == VAL_NUM) { uint32_t rows = (uint32_t)eigs_list_num(verdict, 0, __func__); uint32_t cols = (uint32_t)eigs_list_num(verdict, 1, __func__); val_decref(verdict); diff --git a/src/model_infer.c b/src/model_infer.c index 3a37d662..95cbafa9 100644 --- a/src/model_infer.c +++ b/src/model_infer.c @@ -555,7 +555,8 @@ Value* builtin_eigen_generate(Value *arg) { fprintf(stderr, "eigen_generate: requires [prompt_ids, temperature, max_tokens]\n"); TRACE_NONDET_RECORD("eigen_generate", make_list(0)); } - Value *prompt_list = list_get_borrow(arg, 0); + EIGS_VIEW(prompt_list_view); + Value *prompt_list = list_get_view(arg, 0, &prompt_list_view); if (prompt_list->type != VAL_LIST) { fprintf(stderr, "eigen_generate: prompt_ids must be a list\n"); TRACE_NONDET_RECORD("eigen_generate", make_list(0)); @@ -569,9 +570,9 @@ Value* builtin_eigen_generate(Value *arg) { double top_p = 0.0; /* #1637: optional numbers -- null keeps the default, a bool raises. */ double mt = max_tokens; - eigs_opt_num(list_get_borrow(arg, 1), &temperature, __func__); - eigs_opt_num(list_get_borrow(arg, 2), &mt, __func__); - if (arg->data.list.count >= 4) eigs_opt_num(list_get_borrow(arg, 3), &top_p, __func__); + eigs_slot_opt_num(list_slot(arg, 1), &temperature, __func__); + eigs_slot_opt_num(list_slot(arg, 2), &mt, __func__); + if (arg->data.list.count >= 4) eigs_slot_opt_num(list_slot(arg, 3), &top_p, __func__); if (g_has_error) return make_null(); max_tokens = (int)mt; @@ -607,8 +608,7 @@ Value* builtin_eigen_generate(Value *arg) { int *prompt_ids = xcalloc(prompt_len, sizeof(int)); for (int i = 0; i < prompt_len; i++) { - Value *v = list_get_borrow(prompt_list, i); - prompt_ids[i] = (int)eigs_num_arg(v, "eigen_generate"); /* #1637: a bool raises */ + prompt_ids[i] = (int)eigs_slot_num(list_slot(prompt_list, i), "eigen_generate"); /* #1637: a bool raises */ } if (g_has_error) { free(prompt_ids); return make_null(); } @@ -651,12 +651,13 @@ Value* builtin_eigen_eval_loss(Value *arg) { * a real score. */ ARG_GUARD(1, "eigen_eval_loss", "[prompt_ids, target_id]", make_num(-1.0)); } - Value *prompt_list = list_get_borrow(arg, 0); + EIGS_VIEW(prompt_list_view); + Value *prompt_list = list_get_view(arg, 0, &prompt_list_view); if (prompt_list->type != VAL_LIST) { fprintf(stderr, "eigen_eval_loss: prompt_ids must be a list\n"); ARG_GUARD(1, "eigen_eval_loss", "a list of prompt ids", make_num(-1.0)); } - if (list_get_borrow(arg, 1)->type != VAL_NUM) { + if (list_elem_type(arg, 1) != VAL_NUM) { fprintf(stderr, "eigen_eval_loss: target_id must be a number\n"); ARG_GUARD(1, "eigen_eval_loss", "a numeric target_id", make_num(-1.0)); } @@ -692,8 +693,7 @@ Value* builtin_eigen_eval_loss(Value *arg) { int *prompt_ids = xcalloc(prompt_len, sizeof(int)); for (int i = 0; i < prompt_len; i++) { - Value *v = list_get_borrow(prompt_list, i); - int t = (int)eigs_num_arg(v, "eigen_eval_loss"); /* #1637: a bool raises */ + int t = (int)eigs_slot_num(list_slot(prompt_list, i), "eigen_eval_loss"); /* #1637: a bool raises */ if (t < 0 || t >= vocab_size) t = 0; prompt_ids[i] = t; } diff --git a/src/model_io.c b/src/model_io.c index 28cd2c91..65b80eab 100644 --- a/src/model_io.c +++ b/src/model_io.c @@ -600,12 +600,15 @@ Value* eigs_json_parse_value(const char *s, int *pos); Value* json_obj_get(Value *obj, const char *key) { if (!obj) return NULL; - if (obj->type == VAL_DICT) return dict_get(obj, key); + if (obj->type == VAL_DICT) { + int i = env_hash_find_dict(obj, key, env_hash_name(key)); + return i < 0 ? NULL : dict_value_get_return(obj, i); + } if (obj->type != VAL_LIST) return NULL; for (int i = 0; i + 1 < obj->data.list.count; i += 2) { - Value *k = list_get_borrow(obj, i); + Value *k = list_get_ref(obj, i); if (k && k->type == VAL_STR && strcmp(k->data.str, key) == 0) { - return list_get_borrow(obj, i + 1); + return list_get_return(obj, i + 1); } } return NULL; diff --git a/src/model_train.c b/src/model_train.c index 4692e006..a4495aef 100644 --- a/src/model_train.c +++ b/src/model_train.c @@ -1444,16 +1444,19 @@ Value* builtin_native_train_step(Value *arg) { return make_str("{\"status\": \"error\", \"error\": \"Model not loaded\"}"); } - Value *in_list = list_get_borrow(arg, 0); - Value *out_list = list_get_borrow(arg, 1); + EIGS_VIEW(in_list_view); + + Value *in_list = list_get_view(arg, 0, &in_list_view); + EIGS_VIEW(out_list_view); + Value *out_list = list_get_view(arg, 1, &out_list_view); if (in_list->type != VAL_LIST || out_list->type != VAL_LIST) { return make_str("{\"status\": \"error\", \"error\": \"input_ids and output_ids must be lists\"}"); } float lr = 0.001f; - if (list_get_borrow(arg, 2)->type == VAL_BOOL) { eigs_num_arg(list_get_borrow(arg, 2), __func__); return make_null(); } /* #1637 */ - if (list_get_borrow(arg, 2)->type == VAL_NUM) lr = eigs_list_num(arg, 2, __func__); - else if (list_get_borrow(arg, 2)->type == VAL_STR) lr = strtod(list_get_borrow(arg, 2)->data.str, NULL); + if (list_elem_type(arg, 2) == VAL_BOOL) { eigs_slot_num(list_slot(arg, 2), __func__); return make_null(); } /* #1637 */ + if (list_elem_type(arg, 2) == VAL_NUM) lr = eigs_list_num(arg, 2, __func__); + else if (list_elem_type(arg, 2) == VAL_STR) lr = strtod(list_get_ref(arg, 2)->data.str, NULL); if (lr <= 0 || lr > 1) lr = 0.001f; int input_len = in_list->data.list.count; @@ -1468,12 +1471,10 @@ Value* builtin_native_train_step(Value *arg) { int *input_ids = xcalloc(input_len > 0 ? input_len : 1, sizeof(int)); int *output_ids = xcalloc(output_len > 0 ? output_len : 1, sizeof(int)); for (int i = 0; i < input_len; i++) { - Value *v = list_get_borrow(in_list, i); - input_ids[i] = (int)eigs_num_arg(v, "native_train_step"); /* #1637: a bool raises */ + input_ids[i] = (int)eigs_slot_num(list_slot(in_list, i), "native_train_step"); /* #1637: a bool raises */ } for (int i = 0; i < output_len; i++) { - Value *v = list_get_borrow(out_list, i); - output_ids[i] = (int)eigs_num_arg(v, "native_train_step"); + output_ids[i] = (int)eigs_slot_num(list_slot(out_list, i), "native_train_step"); } if (g_has_error) { free(input_ids); free(output_ids); return make_null(); } diff --git a/tests/test_slot_escape_regress.eigs b/tests/test_slot_escape_regress.eigs index 4af12b2d..19e308d3 100644 --- a/tests/test_slot_escape_regress.eigs +++ b/tests/test_slot_escape_regress.eigs @@ -31,4 +31,14 @@ for i in range of 70: deep is [deep] assert_eq of [deep == deep, true, "a deep nest equals itself"] +# A ragged tensor holding a bare number where a row belongs: B5 read it with +# list_get_ref (NULL for a number) and crashed; the type must be tested first. +assert_eq of [add of [[[1, 2], 3], [1, 1]], [[2, 3], [4, 4]], "ragged broadcast add treats a number row as a scalar"] +assert_eq of [gather of [[[1, 2], 5], [0, 0]], [1, 0], "gather on a ragged tensor"] +try: + r is relu of ([[7, 1], 1]) + assert_eq of ["no raise", "raise", "relu on a ragged tensor raises"] +catch e: + assert_eq of [e.kind, "type_mismatch", "relu on a ragged tensor raises a type error"] + test_summary of null diff --git a/tools/container_migrated_files.txt b/tools/container_migrated_files.txt index da0ccfef..b9e76cd1 100644 --- a/tools/container_migrated_files.txt +++ b/tools/container_migrated_files.txt @@ -4,3 +4,7 @@ src/eigenscript.c src/trace.c src/vm.c src/builtins.c +src/builtins_tensor.c +src/model_infer.c +src/model_train.c +src/model_io.c diff --git a/tools/num_read_allowlist.txt b/tools/num_read_allowlist.txt index d1e60b90..40566a6d 100644 --- a/tools/num_read_allowlist.txt +++ b/tools/num_read_allowlist.txt @@ -5,7 +5,6 @@ # count), when a function with raw tokens is missing here, or a row is stale. # Builtins never belong here: they read through eigs_num_arg / eigs_list_num / # eigs_opt_num / eigs_elem_num, which raise on a bool. -builtins_tensor.c|builtin_numerical_grad_rows|3|in-place writes of a matrix cell that tensor_cells_numeric proved a number before any mutation builtins_tensor.c|builtin_tensor_matmul|1|writes the NaN-boxed null sentinel into a result buffer cell (EIGS_STRICT=0 path); a write, no Value is read container.h|dict_value_get_num|1|accessor numeric fast path: reads the number only behind its own type == VAL_NUM test (#1665 arm D) container.h|dict_value_get_owned|1|owned numeric read copies a proven VAL_NUM box for flip-safe ownership diff --git a/tools/strict_shape_contracts.json b/tools/strict_shape_contracts.json index c58b7a28..de2707b4 100644 --- a/tools/strict_shape_contracts.json +++ b/tools/strict_shape_contracts.json @@ -1455,7 +1455,7 @@ "b38ee26c123f3a682f61cfacc3cd75d7dd6423aa2eafbceb49e64b2e63ee47bf" ], "call_eigs_fn": [ - "123bbc53a201557a82d9c75caf6dc231d9c2485e9d2ccce3a296ccca5c9f95d6" + "7b0b1ef9ee65309c4c965af3c4167aa1e832da6185c80f16f3b05bb42d2b860c" ], "env_set_local": [ "bde886fc51cd3b927e7c8c487a7a0d270453133c4e598506927a2d549788afbf" @@ -2268,7 +2268,7 @@ "f6bb04ca6134dd41d1a67ee91ea2b9587e13477f13ed51afaa3834de1d4f2b78" ], "tensor_unary": [ - "4abceb6bf32d03e72bf0b7245447987b78885d079328ae6504e48bf8afa170f9" + "6cd5986e901ac953b121ba49c8b8d0eac1c53a1cf27cb8889b7f8f9a7e1b9a11" ] } }, @@ -2309,7 +2309,7 @@ "40100cc6cf4d92300cf1d3fbae23cb21adf4241bd01edecc9a683a07fb30bc01" ], "tensor_to_flat": [ - "935bb2e66301b7e41ac4d999bb1e5bfee0b4b4ab8afd54bb73d0369f71c67898" + "d6c74fa5dc6c5d4d702b6f2eb1746dbe61271fd05f9bae972bdefa05d607cedc" ] } }, @@ -2325,11 +2325,11 @@ ], "disposition": "exempt", "body_sha256": [ - "6923863238d3db9fb71ddeb0c0b56d92e9436253f886350d6b89560b5f55e963" + "a247feeafdfc05d844eb1bdd0f3eed2be3cf37a4801ca34add1628d8f141b8a5" ], "source_sha256": { "builtin_tensor_load": [ - "6923863238d3db9fb71ddeb0c0b56d92e9436253f886350d6b89560b5f55e963" + "a247feeafdfc05d844eb1bdd0f3eed2be3cf37a4801ca34add1628d8f141b8a5" ] } }, @@ -2352,7 +2352,7 @@ "ff086ad360320823ab29cb9cfbeed6ec340ae78f959cb18c187c227db55f947f" ], "tensor_unary": [ - "4abceb6bf32d03e72bf0b7245447987b78885d079328ae6504e48bf8afa170f9" + "6cd5986e901ac953b121ba49c8b8d0eac1c53a1cf27cb8889b7f8f9a7e1b9a11" ] } }, @@ -2368,11 +2368,11 @@ ], "disposition": "exempt", "body_sha256": [ - "28d209db0561173b9426149c963f32bbb8a1d4bd15ded1aad450e3e84e4b986e" + "868db4f26fa6b315a63c25e1474c2ba2bbea74c1ff294876bfef6c715906a0e3" ], "source_sha256": { "builtin_tensor_log_softmax": [ - "28d209db0561173b9426149c963f32bbb8a1d4bd15ded1aad450e3e84e4b986e" + "868db4f26fa6b315a63c25e1474c2ba2bbea74c1ff294876bfef6c715906a0e3" ] } }, @@ -2431,10 +2431,10 @@ "f6eec99bd0153900403028372a294cf84be9485ce98b7ad8843ce4e2a5d390ee" ], "tensor_flatten_recursive": [ - "3844bd92ee4238bec0b3bd2645eb4bd17d18a6d681a7ad7a8e21291282e33d14" + "be389e2e858e13f1dc09f309722e7e6ccebf4bb2bb829b2b1ad176401d2e86ae" ], "tensor_total": [ - "a0a594c0e05d41ef7ef4a8c262d56646d628f3b3f5c4f94b40e9cf1637d8ca03" + "e08db1676199c3179be8948bba78f43af63fecc404ef75b8d0b55ac59be72484" ] } }, @@ -2469,7 +2469,7 @@ "a345f0c30c4b1b80943b9c718c0002fc19981d0336a9a1ea0aca3b5966bf8462" ], "tensor_unary": [ - "4abceb6bf32d03e72bf0b7245447987b78885d079328ae6504e48bf8afa170f9" + "6cd5986e901ac953b121ba49c8b8d0eac1c53a1cf27cb8889b7f8f9a7e1b9a11" ] } }, @@ -2492,10 +2492,10 @@ "817e544211920e1216ff102393d66f3cb7a8def4a9ea05611872433b5ad8f87c" ], "tensor_flatten_recursive": [ - "3844bd92ee4238bec0b3bd2645eb4bd17d18a6d681a7ad7a8e21291282e33d14" + "be389e2e858e13f1dc09f309722e7e6ccebf4bb2bb829b2b1ad176401d2e86ae" ], "tensor_total": [ - "a0a594c0e05d41ef7ef4a8c262d56646d628f3b3f5c4f94b40e9cf1637d8ca03" + "e08db1676199c3179be8948bba78f43af63fecc404ef75b8d0b55ac59be72484" ] } }, @@ -2533,7 +2533,7 @@ "9829b048a1af1233bfeae6329573e79d83cd7acb01e548d3737011a625fbdc02" ], "tensor_to_flat": [ - "935bb2e66301b7e41ac4d999bb1e5bfee0b4b4ab8afd54bb73d0369f71c67898" + "d6c74fa5dc6c5d4d702b6f2eb1746dbe61271fd05f9bae972bdefa05d607cedc" ] } }, @@ -2573,11 +2573,11 @@ ], "disposition": "exempt", "body_sha256": [ - "d5ab1a467607560bfce18d36abd18fa5b25d7fe43179da3c305e29f379b027e7" + "f78ba93eb0b22a35b1c8a328a1f557b7096bf05bdbf38fe3259876cc7d1b8bb9" ], "source_sha256": { "builtin_tensor_shape": [ - "d5ab1a467607560bfce18d36abd18fa5b25d7fe43179da3c305e29f379b027e7" + "f78ba93eb0b22a35b1c8a328a1f557b7096bf05bdbf38fe3259876cc7d1b8bb9" ] } }, @@ -2609,7 +2609,7 @@ "40100cc6cf4d92300cf1d3fbae23cb21adf4241bd01edecc9a683a07fb30bc01" ], "tensor_to_flat": [ - "935bb2e66301b7e41ac4d999bb1e5bfee0b4b4ab8afd54bb73d0369f71c67898" + "d6c74fa5dc6c5d4d702b6f2eb1746dbe61271fd05f9bae972bdefa05d607cedc" ] } }, @@ -2632,7 +2632,7 @@ "f38d9057e3c1e2a049e7cd4d4f38df6aace72af77d1a718b357b4f2a5bf4f54d" ], "tensor_unary": [ - "4abceb6bf32d03e72bf0b7245447987b78885d079328ae6504e48bf8afa170f9" + "6cd5986e901ac953b121ba49c8b8d0eac1c53a1cf27cb8889b7f8f9a7e1b9a11" ] } }, @@ -2667,10 +2667,10 @@ "196480c8b98743ea57d8bf3bc4bdf0b0fdc3ca70c56ef6cd1289342e15fcd544" ], "tensor_flatten_recursive": [ - "3844bd92ee4238bec0b3bd2645eb4bd17d18a6d681a7ad7a8e21291282e33d14" + "be389e2e858e13f1dc09f309722e7e6ccebf4bb2bb829b2b1ad176401d2e86ae" ], "tensor_total": [ - "a0a594c0e05d41ef7ef4a8c262d56646d628f3b3f5c4f94b40e9cf1637d8ca03" + "e08db1676199c3179be8948bba78f43af63fecc404ef75b8d0b55ac59be72484" ] } }, @@ -2698,11 +2698,11 @@ ], "disposition": "exempt", "body_sha256": [ - "aad531cea0cb998c5ab4bcfbc19ca755f163227267af03b6ad6f3ae66a8c751f" + "0de72aaa352907156e4244579f587cadc755557642f4e297485a5c4a019233eb" ], "source_sha256": { "builtin_tensor_zeros_like": [ - "aad531cea0cb998c5ab4bcfbc19ca755f163227267af03b6ad6f3ae66a8c751f" + "0de72aaa352907156e4244579f587cadc755557642f4e297485a5c4a019233eb" ], "make_buffer_like": [ "40100cc6cf4d92300cf1d3fbae23cb21adf4241bd01edecc9a683a07fb30bc01"