Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
840da3b
test(driver): raise RFL_THUNK_CAPACITY 256 → 320
ser-vasilich May 21, 2026
39d3337
test(agg): +9.29pp regions via parted F64/I64 + list med/var paths
ser-vasilich May 21, 2026
19f0492
test(temporal): +4.73pp regions via pre-epoch & DAG extract/trunc
ser-vasilich May 21, 2026
44c0c33
test(eval/lang): +1.28pp regions via try/raise, VM, gather W8/W16
ser-vasilich May 21, 2026
7b37b26
test(expr): narrow binary, null compare, cast, affine-sum cache
ser-vasilich May 21, 2026
61d428a
test(graph): 61 assertions across 15 algorithm scenarios
ser-vasilich May 21, 2026
79234ef
test(journal/splay): RAY_JREPLAY arms + mkdir failure path
ser-vasilich May 21, 2026
c7fd908
test(strop): null propagation + parted LIKE dispatch
ser-vasilich May 21, 2026
fdc143f
fix(eval): user-raised value reaches trap handler
ser-vasilich May 21, 2026
f21a897
fix(expr): narrowing CAST in DAG (I64/F64 → I32/I16/U8/BOOL)
ser-vasilich May 21, 2026
8de8147
fix(pivot): exec_if reads sym ID from SYM atom and 1-elem SYM vec
ser-vasilich May 21, 2026
75ca766
test(csv): +3.18pp via fast-path nulls, sentinels, parted save
ser-vasilich May 21, 2026
bd61c2c
test(opt): +3.49pp via fold_unary_const + I32 min2/max2 swaps
ser-vasilich May 21, 2026
ffa0f75
test(sym): RFL + C coverage for table/sym.c interning
ser-vasilich May 21, 2026
7247a98
test(query): coverage push for query.c — 8 new RFL files
ser-vasilich May 21, 2026
53fd4a6
test(query/sym): post-commit refinements from query/sym agents
ser-vasilich May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/lang/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2255,9 +2255,19 @@ vm_error_cleanup: {
if (v) ray_release(v);
}

/* Get error value — prefer vm_err_obj (VM-detected errors like
* arity mismatch) over __raise_val (user raise expressions) */
ray_t *err_val = vm_err_obj ? vm_err_obj : __raise_val;
/* Get error value. If __raise_val is set, a user (raise x)
* just ran — its value is the real payload the handler must
* see. vm_err_obj is the generic error-sentinel returned
* from ray_raise_fn (or a VM-detected error like arity);
* release it if we picked __raise_val. For VM-only errors,
* __raise_val stays NULL and vm_err_obj is used. */
ray_t *err_val;
if (__raise_val) {
if (vm_err_obj) ray_release(vm_err_obj);
err_val = __raise_val;
} else {
err_val = vm_err_obj;
}
vm_err_obj = NULL;
__raise_val = NULL;
if (!err_val) err_val = make_i64(0);
Expand Down
109 changes: 105 additions & 4 deletions src/ops/expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -852,10 +852,50 @@ static void expr_exec_unary(uint8_t opcode, int8_t dt, void* dp,
}
} else if (dt == RAY_BOOL) {
uint8_t* d = (uint8_t*)dp;
const uint8_t* a = (const uint8_t*)ap;
switch (opcode) {
case OP_NOT: for (int64_t j = 0; j < n; j++) d[j] = !a[j]; break;
default: break;
if (opcode == OP_CAST) {
/* (as 'BOOL ...) — truthy semantics, not truncation. */
if (t1 == RAY_F64) {
const double* a = (const double*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (a[j] != 0.0) ? 1 : 0;
} else {
const int64_t* a = (const int64_t*)ap;
for (int64_t j = 0; j < n; j++) d[j] = a[j] ? 1 : 0;
}
} else {
const uint8_t* a = (const uint8_t*)ap;
switch (opcode) {
case OP_NOT: for (int64_t j = 0; j < n; j++) d[j] = !a[j]; break;
default: break;
}
}
} else if (dt == RAY_I32) {
/* OP_CAST narrow output — src came from I64/F64 scratch (filled
* by REG_CONST or REG_SCAN widening); truncate to int32_t. */
int32_t* d = (int32_t*)dp;
if (t1 == RAY_F64) {
const double* a = (const double*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (int32_t)a[j];
} else {
const int64_t* a = (const int64_t*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (int32_t)a[j];
}
} else if (dt == RAY_I16) {
int16_t* d = (int16_t*)dp;
if (t1 == RAY_F64) {
const double* a = (const double*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (int16_t)a[j];
} else {
const int64_t* a = (const int64_t*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (int16_t)a[j];
}
} else if (dt == RAY_U8) {
uint8_t* d = (uint8_t*)dp;
if (t1 == RAY_F64) {
const double* a = (const double*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (uint8_t)a[j];
} else {
const int64_t* a = (const int64_t*)ap;
for (int64_t j = 0; j < n; j++) d[j] = (uint8_t)a[j];
}
}
}
Expand Down Expand Up @@ -1390,6 +1430,67 @@ ray_t* exec_elementwise_unary(ray_graph_t* g, ray_op_t* op, ray_t* input) {
out_off += n;
}
}
} else if (in_type == RAY_I64) {
/* Narrowing I64 → I32/I16/U8/BOOL: truncate. */
if (out_type == RAY_I32) {
while (ray_morsel_next(&m)) {
int64_t n = m.morsel_len;
int64_t* src = (int64_t*)m.morsel_ptr;
int32_t* dst = (int32_t*)((char*)ray_data(result) + out_off * sizeof(int32_t));
for (int64_t i = 0; i < n; i++) dst[i] = (int32_t)src[i];
out_off += n;
}
} else if (out_type == RAY_I16) {
while (ray_morsel_next(&m)) {
int64_t n = m.morsel_len;
int64_t* src = (int64_t*)m.morsel_ptr;
int16_t* dst = (int16_t*)((char*)ray_data(result) + out_off * sizeof(int16_t));
for (int64_t i = 0; i < n; i++) dst[i] = (int16_t)src[i];
out_off += n;
}
} else if (out_type == RAY_U8 || out_type == RAY_BOOL) {
while (ray_morsel_next(&m)) {
int64_t n = m.morsel_len;
int64_t* src = (int64_t*)m.morsel_ptr;
uint8_t* dst = (uint8_t*)((char*)ray_data(result) + out_off);
/* BOOL: collapse non-zero to 1; U8: low byte. */
if (out_type == RAY_BOOL)
for (int64_t i = 0; i < n; i++) dst[i] = src[i] ? 1 : 0;
else
for (int64_t i = 0; i < n; i++) dst[i] = (uint8_t)src[i];
out_off += n;
}
}
} else if (in_type == RAY_F64) {
/* Narrowing F64 → I32/I16/U8/BOOL: float truncation. */
if (out_type == RAY_I32) {
while (ray_morsel_next(&m)) {
int64_t n = m.morsel_len;
double* src = (double*)m.morsel_ptr;
int32_t* dst = (int32_t*)((char*)ray_data(result) + out_off * sizeof(int32_t));
for (int64_t i = 0; i < n; i++) dst[i] = (int32_t)src[i];
out_off += n;
}
} else if (out_type == RAY_I16) {
while (ray_morsel_next(&m)) {
int64_t n = m.morsel_len;
double* src = (double*)m.morsel_ptr;
int16_t* dst = (int16_t*)((char*)ray_data(result) + out_off * sizeof(int16_t));
for (int64_t i = 0; i < n; i++) dst[i] = (int16_t)src[i];
out_off += n;
}
} else if (out_type == RAY_U8 || out_type == RAY_BOOL) {
while (ray_morsel_next(&m)) {
int64_t n = m.morsel_len;
double* src = (double*)m.morsel_ptr;
uint8_t* dst = (uint8_t*)((char*)ray_data(result) + out_off);
if (out_type == RAY_BOOL)
for (int64_t i = 0; i < n; i++) dst[i] = (src[i] != 0.0) ? 1 : 0;
else
for (int64_t i = 0; i < n; i++) dst[i] = (uint8_t)src[i];
out_off += n;
}
}
}
}

Expand Down
24 changes: 18 additions & 6 deletions src/ops/pivot.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@

#include "ops/internal.h"

/* For a SYM-scalar broadcast input (atom -RAY_SYM, or a 1-elem
* RAY_SYM_W{8,16,32,64} vec used as scalar), return the sym ID.
* Hides the atom/vec dispatch: ray_t->i64 aliases ray_t->len, so
* reading `v->i64` on a vec silently yields `len` (= 1) instead of
* the element value. Always use this helper to read the sym ID
* from a then_v/else_v that may be either atom or 1-elem vec. */
static inline int64_t sym_scalar_id(ray_t* v) {
return ray_is_atom(v)
? v->i64
: ray_read_sym(ray_data(v), 0, v->type, v->attrs);
}

/* ============================================================================
* OP_IF: ternary select result[i] = cond[i] ? then[i] : else[i]
* ============================================================================ */
Expand Down Expand Up @@ -109,8 +121,8 @@ ray_t* exec_if(ray_graph_t* g, ray_op_t* op) {
} else if (then_v->type == RAY_STR) {
sp = ray_str_vec_get(then_v, 0, &sl);
if (!sp) { sp = ""; sl = 0; }
} else if (RAY_IS_SYM(then_v->type)) {
ray_t* s = ray_sym_str(then_v->i64);
} else if (RAY_IS_SYM(then_v->type) || then_v->type == -RAY_SYM) {
ray_t* s = ray_sym_str(sym_scalar_id(then_v));
sp = s ? ray_str_ptr(s) : "";
sl = s ? ray_str_len(s) : 0;
} else { sp = ""; sl = 0; }
Expand All @@ -132,8 +144,8 @@ ray_t* exec_if(ray_graph_t* g, ray_op_t* op) {
} else if (else_v->type == RAY_STR) {
sp = ray_str_vec_get(else_v, 0, &sl);
if (!sp) { sp = ""; sl = 0; }
} else if (RAY_IS_SYM(else_v->type)) {
ray_t* s = ray_sym_str(else_v->i64);
} else if (RAY_IS_SYM(else_v->type) || else_v->type == -RAY_SYM) {
ray_t* s = ray_sym_str(sym_scalar_id(else_v));
sp = s ? ray_str_ptr(s) : "";
sl = s ? ray_str_len(s) : 0;
} else { sp = ""; sl = 0; }
Expand All @@ -159,14 +171,14 @@ ray_t* exec_if(ray_graph_t* g, ray_op_t* op) {
if (then_v->type == -RAY_STR) {
t_scalar = ray_sym_intern(ray_str_ptr(then_v), ray_str_len(then_v));
} else {
t_scalar = then_v->i64;
t_scalar = sym_scalar_id(then_v);
}
}
if (else_scalar) {
if (else_v->type == -RAY_STR) {
e_scalar = ray_sym_intern(ray_str_ptr(else_v), ray_str_len(else_v));
} else {
e_scalar = else_v->i64;
e_scalar = sym_scalar_id(else_v);
}
}
int64_t* dst = (int64_t*)ray_data(result);
Expand Down
12 changes: 10 additions & 2 deletions test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ static const test_entry_t* const compiled_groups[] = {
* evaluates it under a fresh runtime (via rfl_setup/rfl_teardown).
*/

#define RFL_THUNK_CAPACITY 256
#define RFL_THUNK_CAPACITY 320

static char g_rfl_paths[RFL_THUNK_CAPACITY][512];
static char g_rfl_names[RFL_THUNK_CAPACITY][256];
Expand Down Expand Up @@ -454,7 +454,15 @@ static void rfl_teardown(void) { ray_runtime_destroy(__RUNTIME); }
X(224) X(225) X(226) X(227) X(228) X(229) X(230) X(231) \
X(232) X(233) X(234) X(235) X(236) X(237) X(238) X(239) \
X(240) X(241) X(242) X(243) X(244) X(245) X(246) X(247) \
X(248) X(249) X(250) X(251) X(252) X(253) X(254) X(255)
X(248) X(249) X(250) X(251) X(252) X(253) X(254) X(255) \
X(256) X(257) X(258) X(259) X(260) X(261) X(262) X(263) \
X(264) X(265) X(266) X(267) X(268) X(269) X(270) X(271) \
X(272) X(273) X(274) X(275) X(276) X(277) X(278) X(279) \
X(280) X(281) X(282) X(283) X(284) X(285) X(286) X(287) \
X(288) X(289) X(290) X(291) X(292) X(293) X(294) X(295) \
X(296) X(297) X(298) X(299) X(300) X(301) X(302) X(303) \
X(304) X(305) X(306) X(307) X(308) X(309) X(310) X(311) \
X(312) X(313) X(314) X(315) X(316) X(317) X(318) X(319)

#define X(N) static test_result_t rfl_thunk_##N(void) { return run_rfl_at(N); }
RFL_THUNKS(X)
Expand Down
98 changes: 98 additions & 0 deletions test/rfl/agg/list_med_var.rfl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
;; list_med_var.rfl — coverage for ray_med_fn and var_stddev_core
;; when invoked on a heterogeneous list rather than a vec.
;;
;; Lines targeted in src/ops/agg.c:
;; ray_med_fn list branch : 503-519 (is_list path with scratch alloc)
;; ray_med_fn type error : 519 (else: return type error)
;; var_stddev_core list : 593-607 (is_list path with ray_vec_new scratch)
;; var_stddev_core type err: 606 (else: return type error)
;;
;; Also covers:
;; ray_avg_fn non-numeric atom (line 309): ray_retain(x); return x
;; vec_to_f64_scratch type error (475-476): non-numeric vec type
;; ray_sum_fn TIMESTAMP vec path (257-261): else branch of RAY_TIME
;; ray_sum_fn TIME vec path (251-255): sum on TIME vec
;;
;; High density: each assertion exercises ≥3 regions:
;; 1. is_list / ray_is_atom dispatch
;; 2. element iteration + value accumulation
;; 3. null-skip / type check + return

;; ─── med on list (is_list branch, lines 503-519) ─────────────────────
;; Odd count: median = middle element after sort.
(med (list 5 3 1 4 2)) -- 3.0
;; Even count: average of two middle elements.
(med (list 1.0 4.0 2.0 3.0)) -- 2.5
;; Single non-null element.
(med (list 7)) -- 7.0
;; Null-skip in list: 0Ni is a typed null for integer.
(med (list 1 0Ni 5)) -- 3.0
;; All-null list → typed-null F64 (cnt==0 branch, line 524).
(nil? (med (list 0Ni 0Ni))) -- true
;; Empty list → typed-null F64.
(nil? (med (list))) -- true
;; Mixed integer + float in list.
(med (list 1 2.0 3)) -- 2.0
;; Return type always f64.
(type (med (list 10 20 30))) -- 'f64

;; ─── med type error on list with non-numeric (line 514) ──────────────
(med (list 1.0 "hello" 3.0)) !- type
(med (list 1 'foo 3)) !- type

;; ─── med type error on non-list non-vec (line 519) ───────────────────
(med 'some_sym) !- type

;; ─── var/stddev on list (var_stddev_core list branch: lines 593-607) ──
;; Basic: list [1 2 3 4 5], pop_var = 2.0, sample_var = 2.5
(var_pop (list 1 2 3 4 5)) -- 2.0
(stddev_pop (list 1 2 3 4 5)) -- 1.4142135623730951
;; Sample variance on the same list.
(< (abs (- (var (list 2 4 4 4 5 5 7 9)) 4.571428571428571)) 0.000001) -- true
;; Null skipping in list: 0Ni is skipped.
(var_pop (list 1 0Ni 3 0Ni 5)) -- 2.6666666666666665
;; verify with tolerance
(< (abs (- (var_pop (list 1 0Ni 3 0Ni 5)) 2.6666666666666665)) 0.000001) -- true
;; Empty list → typed null (cnt==0).
(nil? (var (list))) -- true
(nil? (var_pop (list))) -- true
;; Single-element list for sample: → null (sample & cnt<=1).
(nil? (var (list 42.0))) -- true
(nil? (stddev (list 42.0))) -- true
;; Single-element list for pop: → 0.0.
(var_pop (list 42.0)) -- 0.0
(stddev_pop (list 42.0)) -- 0.0
;; Return type f64.
(type (var (list 1.0 2.0 3.0))) -- 'f64

;; ─── var type error on list with non-numeric (line 601) ──────────────
(var (list 1.0 'bad 3.0)) !- type
(stddev (list 1.0 "bad" 3.0)) !- type

;; ─── var type error on non-list non-vec (line 606) ───────────────────
(var_pop 'sym_input) !- type
(dev 'sym_input) !- type

;; ─── vec_to_f64_scratch type error path (lines 475-476) ─────────────
;; SYM vec is not numeric → error from vec_to_f64_scratch.
(med (as 'SYM ['a 'b 'c])) !- type
(dev (as 'SYM ['a 'b 'c])) !- type
(var (as 'SYM ['a 'b 'c])) !- type

;; ─── avg non-numeric atom return path (line 309) ─────────────────────
;; When ray_is_atom(x) and !is_numeric and !is_null: ray_retain(x); return x
;; SYM atom is non-numeric → passes through unchanged.
(type (avg 'hello)) -- 'sym
(type (avg 'world)) -- 'sym

;; ─── sum TIMESTAMP vec (lines 257-261 in ray_sum_fn) ─────────────────
;; TIMESTAMP scalars as vec via `as`; sum returns a TIMESTAMP atom.
(type (as 'TIMESTAMP [1000 2000 3000])) -- 'TIMESTAMP
(sum (as 'TIMESTAMP [1000 2000 3000])) -- (as 'TIMESTAMP 6000)
;; Null-aware: null is skipped; result = 1000 + 3000 = 4000.
(sum (as 'TIMESTAMP [1000 0N 3000])) -- (as 'TIMESTAMP 4000)

;; ─── sum TIME vec (lines 251-255 in ray_sum_fn) ──────────────────────
;; TIME vec; result type TIME.
(type (sum (as 'TIME [100 200 300]))) -- 'time
(sum (as 'TIME [100 200 300])) -- (as 'TIME 600)
Loading
Loading