diff --git a/CLAUDE.md b/CLAUDE.md index 0e3d28a7..09615ebd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,7 +57,7 @@ The creator functions are **polymorphic** — behavior depends on argument types - `record(record, another)` — merge - `record(array_of_keys)` — create record from keys -Other key intrinsics: `length()`, `stone()`, `is_stone()`, `print()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()` +Other key intrinsics: `object()`, `length()`, `stone()`, `is_stone()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()` Sensory functions: `is_array()`, `is_text()`, `is_number()`, `is_object()`, `is_function()`, `is_null()`, `is_logical()`, `is_integer()`, `is_stone()`, etc. diff --git a/mcode.cm b/mcode.cm index 79a79996..62d587e6 100644 --- a/mcode.cm +++ b/mcode.cm @@ -878,17 +878,47 @@ var mcode = function(ast) { var inline_every = true var inline_some = true var inline_reduce = true - var inline_map = true + var inline_map = false var inline_find = true // --- Helper: emit arity-dispatched callback invocation --- - // ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix} + // ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix, + // known_arity (optional — compile-time arity of callback literal)} // args = [slot_for_arg1, slot_for_arg2] — data args (not this) // max_args = 1 or 2 — how many data args to support var emit_arity_call = function(ctx, args, max_args) { - var call_one = gen_label(ctx.prefix + "_c1") - var call_two = gen_label(ctx.prefix + "_c2") - var call_done = gen_label(ctx.prefix + "_cd") + var call_one = null + var call_two = null + var call_done = null + var ka = ctx.known_arity + // When callback arity is known at compile time, emit only the matching + // call path. This avoids dead branches where parameters are nulled, + // which confuse the type checker after inlining (e.g. push on null). + if (ka != null) { + if (ka >= max_args) { + ka = max_args + } + if (ka == 0) { + emit_3("frame", ctx.frame, ctx.fn, 0) + emit_3("setarg", ctx.frame, 0, ctx.null_s) + emit_2("invoke", ctx.frame, ctx.result) + } else if (ka == 1 || max_args < 2) { + emit_3("frame", ctx.frame, ctx.fn, 1) + emit_3("setarg", ctx.frame, 0, ctx.null_s) + emit_3("setarg", ctx.frame, 1, args[0]) + emit_2("invoke", ctx.frame, ctx.result) + } else { + emit_3("frame", ctx.frame, ctx.fn, 2) + emit_3("setarg", ctx.frame, 0, ctx.null_s) + emit_3("setarg", ctx.frame, 1, args[0]) + emit_3("setarg", ctx.frame, 2, args[1]) + emit_2("invoke", ctx.frame, ctx.result) + } + return null + } + call_one = gen_label(ctx.prefix + "_c1") + call_two = gen_label(ctx.prefix + "_c2") + call_done = gen_label(ctx.prefix + "_cd") emit_3("eq", ctx.az, ctx.fn_arity, ctx.zero) emit_jump_cond("jump_false", ctx.az, call_one) emit_3("frame", ctx.frame, ctx.fn, 0) @@ -952,7 +982,7 @@ var mcode = function(ast) { } // --- Helper: emit a reduce loop body --- - // r = {acc, i, arr, fn, len, fn_arity}; emits loop updating acc in-place. + // r = {acc, i, arr, fn, len, fn_arity, known_arity}; emits loop updating acc in-place. // Caller must emit the done_label after calling this. var emit_reduce_loop = function(r, forward, done_label) { var acc = r.acc @@ -971,7 +1001,8 @@ var mcode = function(ast) { var f = alloc_slot() var loop_label = gen_label("reduce_loop") var ctx = {fn: fn_slot, fn_arity: fn_arity, result: acc, null_s: null_s, - frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "reduce"} + frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "reduce", + known_arity: r.known_arity} emit_2("int", one, 1) emit_2("int", zero, 0) emit_1("null", null_s) @@ -1428,7 +1459,8 @@ var mcode = function(ast) { emit_2("length", fn_arity, fn_slot) emit_2("int", zero, 0) emit_2("int", one, 1) - r = {acc: acc, i: i, arr: arr_slot, fn: fn_slot, len: len, fn_arity: fn_arity} + r = {acc: acc, i: i, arr: arr_slot, fn: fn_slot, len: len, fn_arity: fn_arity, + known_arity: args.fn_known_arity} if (nargs == 2) { null_label = gen_label("reduce_null") d1 = gen_label("reduce_d1") @@ -1877,6 +1909,8 @@ var mcode = function(ast) { var guard_t = 0 var guard_err = null var guard_done = null + var cb_known = null + var cb_p = null if (expr == null) { return -1 @@ -2184,27 +2218,16 @@ var mcode = function(ast) { a2 = nargs >= 3 ? gen_expr(args_list[2], -1) : -1 a3 = nargs >= 4 ? gen_expr(args_list[3], -1) : -1 d = alloc_slot() - return expand_inline_reduce(d, {arr: a0, fn: a1, init: a2, rev: a3}, nargs) - } - // array(arr, fn) → inline map expansion - // Skip when first arg is a number literal (that's array(N, fn) — creation, not map) - if (nargs == 2 && fname == "array" && inline_map - && args_list[0].kind != "number") { - // Specialized: array(arr, known_sensory_intrinsic) → direct opcode loop - if (args_list[1].kind == "name" && args_list[1].intrinsic == true - && sensory_ops[args_list[1].name] != null) { - a0 = gen_expr(args_list[0], -1) - d = alloc_slot() - return expand_inline_map_intrinsic(d, a0, sensory_ops[args_list[1].name]) - } - // General: array(arr, fn_literal) → map loop with arity dispatch + cb_known = null if (args_list[1].kind == "function") { - a0 = gen_expr(args_list[0], -1) - a1 = gen_expr(args_list[1], -1) - d = alloc_slot() - return expand_inline_map(d, a0, a1) + cb_p = args_list[1].list + if (cb_p == null) cb_p = args_list[1].parameters + cb_known = cb_p != null ? length(cb_p) : 0 } + return expand_inline_reduce(d, {arr: a0, fn: a1, init: a2, rev: a3, + fn_known_arity: cb_known}, nargs) } + // array(arr, fn) inline expansion removed — array() is too complex to inline } // Collect arg slots diff --git a/source/runtime.c b/source/runtime.c index 225aa691..94df3904 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -8550,6 +8550,8 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu /* array(object) - keys */ if (JS_IsRecord (arg)) { + if (argc > 1 && JS_IsFunction (argv[1])) + return JS_RaiseDisrupt (ctx, "array(record, fn) is not valid — use array(array(record), fn) to map over keys"); /* Return object keys */ return JS_GetOwnPropertyNames (ctx, arg); } diff --git a/streamline.ce b/streamline.ce index aafd74aa..a15d5e66 100644 --- a/streamline.ce +++ b/streamline.ce @@ -17,6 +17,7 @@ var show_ir = false var show_check = false var show_types = false var show_diagnose = false +var no_inline = false var filename = null var i = 0 var di = 0 @@ -33,6 +34,8 @@ for (i = 0; i < length(args); i++) { show_types = true } else if (args[i] == '--diagnose') { show_diagnose = true + } else if (args[i] == '--no-inline') { + no_inline = true } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] ") $stop() @@ -58,6 +61,11 @@ var compiled = null if (show_diagnose) { compiled = shop.mcode_file(filename) compiled._warn = true + if (no_inline) compiled._no_inline = true + optimized = use('streamline')(compiled) +} else if (no_inline) { + compiled = shop.mcode_file(filename) + compiled._no_inline = true optimized = use('streamline')(compiled) } else { optimized = shop.compile_file(filename) @@ -363,12 +371,12 @@ if (show_diagnose) { di = 0 while (di < length(optimized._diagnostics)) { diag = optimized._diagnostics[di] - print(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`) + log.compile(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`) di = di + 1 } - print(`\n${text(length(optimized._diagnostics))} diagnostic(s)`) + log.compile(`\n${text(length(optimized._diagnostics))} diagnostic(s)`) } else { - print("No diagnostics.") + log.compile("No diagnostics.") } } diff --git a/streamline.cm b/streamline.cm index 5c37279a..44555154 100644 --- a/streamline.cm +++ b/streamline.cm @@ -271,7 +271,7 @@ var streamline = function(ir, log) { shr: [2, T_INT, 3, T_INT], ushr: [2, T_INT, 3, T_INT], bitnot: [2, T_INT], concat: [2, T_TEXT, 3, T_TEXT], - not: [2, T_BOOL], and: [2, T_BOOL, 3, T_BOOL], or: [2, T_BOOL, 3, T_BOOL], + and: [2, T_BOOL, 3, T_BOOL], or: [2, T_BOOL, 3, T_BOOL], store_index: [1, T_ARRAY, 2, T_INT], store_field: [1, T_RECORD], push: [1, T_ARRAY], load_index: [2, T_ARRAY, 3, T_INT], load_field: [2, T_RECORD], @@ -3213,6 +3213,9 @@ var streamline = function(ir, log) { } // Phase 2: Inline pass + if (ir._no_inline) { + return ir + } var changed_main = false var changed_fns = null if (ir.main != null) { diff --git a/vm_suite.ce b/vm_suite.ce index 0e081d41..6774f4ce 100644 --- a/vm_suite.ce +++ b/vm_suite.ce @@ -6957,6 +6957,17 @@ run("reduce counting with predicate", function() { assert_eq(result, 3, "reduce count evens") }) +run("reduce building array", function() { + var result = reduce([1, 2, 3], function(acc, x) { + acc[] = x * 2 + return acc + }, []) + assert_eq(length(result), 3, "reduce array build length") + assert_eq(result[0], 2, "reduce array [0]") + assert_eq(result[1], 4, "reduce array [1]") + assert_eq(result[2], 6, "reduce array [2]") +}) + // ============================================================================ // SOME - COMPLETE COVERAGE // ============================================================================