Merge branch 'optimize_mcode' into fix_aot

This commit is contained in:
2026-02-21 20:42:25 -06:00
12 changed files with 35102 additions and 36412 deletions

View File

@@ -95,22 +95,9 @@
"nr_close_slots": 0,
"instructions": [
["move", 2, 1, 14, 14],
[
"access",
3,
{
"name": "is_blob",
"kind": "name",
"make": "intrinsic"
},
15,
8
],
["frame", 4, 3, 1, 15, 8],
["setarg", 4, 1, 1, 15, 8],
["invoke", 4, 3, 15, 8],
["is_blob", 3, 1, 15, 16],
"_nop_bl_1",
["jump_true", 3, "if_else_6", 15, 8],
["jump_true", 3, "if_else_6", 15, 16],
[
"access",
3,
@@ -199,7 +186,7 @@
"_nop_ur_1",
"_nop_ur_2"
],
"_write_types": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "null", "text", "array", null, null, null, "text", null, null, null, null],
"_write_types": [null, null, null, "bool", null, null, null, null, null, null, null, null, null, null, null, null, null, "null", "text", "array", null, null, null, "text", null, null, null, null],
"name": "content_hash",
"filename": ".cell/packages/core/internal/bootstrap.cm",
"nr_args": 1
@@ -222,7 +209,7 @@
8
],
"_nop_bl_1",
["jump_true", 2, "if_else_10", 20, 8],
["wary_true", 2, "if_else_10", 20, 8],
["null", 2, 20, 26],
["return", 2, 20, 26],
"_nop_ur_1",
@@ -345,7 +332,7 @@
8
],
"_nop_bl_1",
["jump_true", 1, "if_else_18", 25, 8],
["wary_true", 1, "if_else_18", 25, 8],
["null", 1, 25, 26],
["return", 1, 25, 26],
"_nop_ur_1",
@@ -427,7 +414,7 @@
["invoke", 5, 3, 27, 8],
"call_done_26",
"_nop_bl_2",
["jump_true", 3, "if_else_23", 27, 8],
["wary_true", 3, "if_else_23", 27, 8],
["get", 2, 11, 1, 27, 24],
["is_proxy", 3, 2, 27, 24],
["jump_false", 3, "record_path_27", 27, 24],
@@ -628,7 +615,7 @@
["invoke", 8, 6, 36, 8],
"call_done_41",
"_nop_bl_1",
["jump_true", 6, "if_else_38", 36, 8],
["wary_true", 6, "if_else_38", 36, 8],
["access", 5, "error: missing seed: ", 37, 14],
"_nop_tc_7",
"_nop_tc_8",
@@ -1026,30 +1013,11 @@
"call_done_63",
"if_end_58",
["access", 3, 1, 66, 17],
"_nop_tc_1",
"_nop_tc_2",
"_nop_tc_3",
"_nop_tc_4",
["add", 5, 5, 3, 66, 17],
["jump", "num_done_65", 66, 17],
"num_err_64",
"_nop_ucfg_1",
"_nop_ucfg_2",
"_nop_ucfg_3",
"_nop_ucfg_4",
"_nop_ucfg_5",
"_nop_ucfg_6",
"_nop_ucfg_7",
"_nop_ucfg_8",
"_nop_ucfg_9",
"_nop_ucfg_10",
"_nop_ucfg_11",
"_nop_ucfg_12",
"num_done_65",
["jump", "while_start_55", 66, 17],
"while_end_56",
["disrupt", 68, 5],
"_nop_ucfg_13",
"_nop_ucfg_1",
"if_else_53",
"if_end_54",
["get", 3, 15, 1, 70, 10],
@@ -1060,7 +1028,7 @@
"_nop_ur_1",
"_nop_ur_2"
],
"_write_types": [null, null, null, "int", null, null, "bool", null, null, null, null, null, null, null, null, null, null, null, "null", "bool", "bool", null, "int", "int", "bool", null, "int", "bool", null, null, null, null, "null", "bool", "bool", null, "null", "bool", null, null, null, null, null, null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "int", null, null, null, null, null, null, null, null, null, null, null, null, null],
"_write_types": [null, null, null, "int", null, null, "bool", null, null, null, null, null, null, null, null, null, null, null, "null", "bool", "bool", null, "int", "int", "bool", null, "int", "bool", null, null, null, null, "null", "bool", "bool", null, "null", "bool", null, null, null, null, null, null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "int", null, null, null, null],
"name": "analyze",
"filename": ".cell/packages/core/internal/bootstrap.cm",
"nr_args": 2
@@ -1078,7 +1046,7 @@
"instructions": [
["get", 3, 11, 1, 74, 21],
["is_proxy", 4, 3, 74, 21],
["jump_false", 4, "record_path_66", 74, 21],
["jump_false", 4, "record_path_64", 74, 21],
["null", 4, 74, 21],
["access", 5, "slurp", 74, 21],
["array", 6, 0, 74, 21],
@@ -1089,14 +1057,14 @@
["setarg", 7, 1, 5, 74, 21],
["setarg", 7, 2, 6, 74, 21],
["invoke", 7, 4, 74, 21],
["jump", "call_done_67", 74, 21],
"record_path_66",
["jump", "call_done_65", 74, 21],
"record_path_64",
["load_field", 5, 3, "slurp", 74, 21],
["frame", 6, 5, 1, 74, 21],
["setarg", 6, 0, 3, 74, 21],
["setarg", 6, 1, 2, 74, 21],
["invoke", 6, 4, 74, 21],
"call_done_67",
"call_done_65",
["move", 3, 4, 74, 21],
["get", 5, 4, 1, 75, 14],
["frame", 6, 5, 1, 75, 14],
@@ -1113,10 +1081,10 @@
["null", 8, 79, 20],
["null", 9, 80, 19],
["move", 10, 4, 81, 7],
["jump_false", 4, "and_end_70", 81, 7],
["wary_false", 4, "and_end_68", 81, 7],
["get", 4, 11, 1, 81, 17],
["is_proxy", 11, 4, 81, 17],
["jump_false", 11, "record_path_71", 81, 17],
["jump_false", 11, "record_path_69", 81, 17],
["null", 11, 81, 17],
["access", 12, "is_file", 81, 17],
["array", 13, 0, 81, 17],
@@ -1127,22 +1095,22 @@
["setarg", 14, 1, 12, 81, 17],
["setarg", 14, 2, 13, 81, 17],
["invoke", 14, 11, 81, 17],
["jump", "call_done_72", 81, 17],
"record_path_71",
["jump", "call_done_70", 81, 17],
"record_path_69",
["load_field", 12, 4, "is_file", 81, 17],
["frame", 13, 12, 1, 81, 17],
["setarg", 13, 0, 4, 81, 17],
["setarg", 13, 1, 5, 81, 17],
["invoke", 13, 11, 81, 17],
"call_done_72",
"call_done_70",
["move", 10, 11, 81, 17],
"and_end_70",
["jump_false", 10, "if_else_68", 81, 17],
"and_end_68",
["wary_false", 10, "if_else_66", 81, 17],
["null", 4, 81, 37],
["return", 4, 81, 37],
"_nop_ur_1",
"if_else_68",
"if_end_69",
"if_else_66",
"if_end_67",
[
"access",
4,
@@ -1174,7 +1142,7 @@
["move", 7, 3, 83, 14],
["get", 3, 12, 1, 84, 16],
["is_proxy", 4, 3, 84, 16],
["jump_false", 4, "record_path_73", 84, 16],
["jump_false", 4, "record_path_71", 84, 16],
["null", 4, 84, 16],
["access", 6, "encode", 84, 16],
["array", 10, 0, 84, 16],
@@ -1185,14 +1153,14 @@
["setarg", 11, 1, 6, 84, 16],
["setarg", 11, 2, 10, 84, 16],
["invoke", 11, 4, 84, 16],
["jump", "call_done_74", 84, 16],
"record_path_73",
["jump", "call_done_72", 84, 16],
"record_path_71",
["load_field", 6, 3, "encode", 84, 16],
["frame", 10, 6, 1, 84, 16],
["setarg", 10, 0, 3, 84, 16],
["setarg", 10, 1, 7, 84, 16],
["invoke", 10, 4, 84, 16],
"call_done_74",
"call_done_72",
["move", 8, 4, 84, 16],
[
"access",
@@ -1210,13 +1178,13 @@
["setarg", 6, 2, 4, 85, 15],
["invoke", 6, 3, 85, 15],
["move", 9, 3, 85, 15],
["jump_false", 5, "if_else_75", 86, 7],
["wary_false", 5, "if_else_73", 86, 7],
["get", 3, 6, 1, 87, 5],
["frame", 4, 3, 0, 87, 5],
["invoke", 4, 3, 87, 5],
["get", 3, 11, 1, 88, 5],
["is_proxy", 4, 3, 88, 5],
["jump_false", 4, "record_path_77", 88, 5],
["jump_false", 4, "record_path_75", 88, 5],
["null", 4, 88, 5],
["access", 6, "slurpwrite", 88, 5],
["array", 7, 0, 88, 5],
@@ -1228,18 +1196,18 @@
["setarg", 8, 1, 6, 88, 5],
["setarg", 8, 2, 7, 88, 5],
["invoke", 8, 4, 88, 5],
["jump", "call_done_78", 88, 5],
"record_path_77",
["jump", "call_done_76", 88, 5],
"record_path_75",
["load_field", 6, 3, "slurpwrite", 88, 5],
["frame", 7, 6, 2, 88, 5],
["setarg", 7, 0, 3, 88, 5],
["setarg", 7, 1, 5, 88, 5],
["setarg", 7, 2, 9, 88, 5],
["invoke", 7, 4, 88, 5],
"call_done_78",
["jump", "if_end_76", 88, 5],
"if_else_75",
"if_end_76",
"call_done_76",
["jump", "if_end_74", 88, 5],
"if_else_73",
"if_end_74",
["null", 3, 88, 5],
["return", 3, 88, 5]
],
@@ -1369,10 +1337,10 @@
["move", 1, 22, 99, 26],
["access", 17, 0, 101, 10],
["null", 18, 102, 13],
"while_start_79",
"while_start_77",
["length", 19, 1, 103, 20],
["lt", 20, 17, 19, 103, 20],
["jump_false", 20, "while_end_80", 103, 20],
["jump_false", 20, "while_end_78", 103, 20],
["load_index", 19, 1, 17, 104, 22],
["move", 18, 19, 104, 22],
["load_field", 20, 19, "name", 105, 21],
@@ -1389,19 +1357,19 @@
],
["access", 21, "/", 105, 45],
["is_text", 22, 19, 105, 45],
["jump_false", 22, "add_cn_82", 105, 45],
["jump_false", 22, "add_cn_80", 105, 45],
"_nop_tc_1",
"_nop_tc_2",
["concat", 23, 19, 21, 105, 45],
["jump", "add_done_81", 105, 45],
"add_cn_82",
["jump", "add_done_79", 105, 45],
"add_cn_80",
["is_num", 22, 19, 105, 45],
["jump_false", 22, "add_err_83", 105, 45],
["jump_false", 22, "add_err_81", 105, 45],
"_nop_tc_3",
"_nop_dj_1",
"_nop_ucfg_1",
"_nop_ucfg_2",
"add_err_83",
"add_err_81",
[
"access",
19,
@@ -1426,22 +1394,22 @@
["setarg", 22, 2, 24, 105, 45],
["invoke", 22, 19, 105, 45],
["disrupt", 105, 45],
"add_done_81",
"add_done_79",
["load_field", 19, 18, "path", 105, 51],
"_nop_tc_1",
"_nop_tc_2",
["is_text", 21, 19, 105, 51],
["jump_false", 21, "add_cn_85", 105, 51],
["jump_false", 21, "add_cn_83", 105, 51],
["concat", 21, 23, 19, 105, 51],
["jump", "add_done_84", 105, 51],
"add_cn_85",
["jump", "add_done_82", 105, 51],
"add_cn_83",
"_nop_tc_3",
["jump", "add_err_86", 105, 51],
["jump", "add_err_84", 105, 51],
"_nop_ucfg_1",
"_nop_ucfg_2",
"_nop_ucfg_3",
"_nop_ucfg_4",
"add_err_86",
"add_err_84",
[
"access",
19,
@@ -1466,35 +1434,16 @@
["setarg", 23, 2, 24, 105, 51],
["invoke", 23, 19, 105, 51],
["disrupt", 105, 51],
"add_done_84",
"add_done_82",
["frame", 19, 9, 2, 105, 3],
["setarg", 19, 1, 20, 105, 3],
["stone_text", 21],
["setarg", 19, 2, 21, 105, 3],
["invoke", 19, 20, 105, 3],
["access", 19, 1, 106, 13],
"_nop_tc_4",
"_nop_tc_5",
"_nop_tc_6",
"_nop_tc_7",
["add", 17, 17, 19, 106, 13],
["jump", "num_done_88", 106, 13],
"num_err_87",
"_nop_ucfg_3",
"_nop_ucfg_4",
"_nop_ucfg_5",
"_nop_ucfg_6",
"_nop_ucfg_7",
"_nop_ucfg_8",
"_nop_ucfg_9",
"_nop_ucfg_10",
"_nop_ucfg_11",
"_nop_ucfg_12",
"_nop_ucfg_13",
"_nop_ucfg_14",
"num_done_88",
["jump", "while_start_79", 106, 13],
"while_end_80",
["jump", "while_start_77", 106, 13],
"while_end_78",
["access", 1, "bootstrap: cache seeded\n", 108, 10],
[
"access",
@@ -1508,7 +1457,7 @@
1
],
["is_proxy", 17, 9, 108, 1],
["jump_false", 17, "record_path_89", 108, 1],
["jump_false", 17, "record_path_85", 108, 1],
["null", 17, 108, 1],
["access", 18, "print", 108, 1],
["array", 19, 0, 108, 1],
@@ -1520,18 +1469,18 @@
["setarg", 20, 1, 18, 108, 1],
["setarg", 20, 2, 19, 108, 1],
["invoke", 20, 17, 108, 1],
["jump", "call_done_90", 108, 1],
"record_path_89",
["jump", "call_done_86", 108, 1],
"record_path_85",
["load_field", 18, 9, "print", 108, 1],
["frame", 19, 18, 1, 108, 1],
["setarg", 19, 0, 9, 108, 1],
["stone_text", 1],
["setarg", 19, 1, 1, 108, 1],
["invoke", 19, 17, 108, 1],
"call_done_90",
"call_done_86",
["return", 17, 108, 1]
],
"_write_types": [null, "function", "function", "function", null, "function", null, null, null, null, null, null, null, null, "function", "int", "function", "function", null, "array", "function", "function", "function", "function", "function", "function", "function", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", null, null, null, null, null, null, null, null, null, "text", null, null, null, "null", "text", "array", null, null, null],
"_write_types": [null, "function", "function", "function", null, "function", null, null, null, null, null, null, null, null, "function", "int", "function", "function", null, "array", "function", "function", "function", "function", "function", "function", "function", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", "text", null, null, null, "null", "text", "array", null, null, null],
"nr_args": 0
},
"name": ".cell/packages/core/internal/bootstrap.cm",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -13,14 +13,33 @@ var os = use('internal/os')
var link = use('link')
// These come from env (via core_extras in engine.cm):
// analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env,
// content_hash, cache_path, ensure_build_dir
// analyze, run_ast_fn, core_json, use_cache, core_path, shop_path, actor_api,
// runtime_env, content_hash, cache_path, ensure_build_dir
var shop_json = core_json
var global_shop_path = shop_path
var my$_ = actor_api
var core = "core"
// Compiler fingerprint: hash of all compiler source files so that any compiler
// change invalidates the entire build cache. Folded into hash_path().
var compiler_fingerprint = (function() {
var files = [
"tokenize", "parse", "fold", "mcode", "streamline",
"qbe", "qbe_emit", "ir_stats"
]
var combined = ""
var i = 0
var path = null
while (i < length(files)) {
path = core_path + '/' + files[i] + '.cm'
if (fd.is_file(path))
combined = combined + text(fd.slurp(path))
i = i + 1
}
return content_hash(stone(blob(combined)))
})()
// Make a package name safe for use in C identifiers.
// Replaces /, ., -, @ with _ so the result is a valid C identifier fragment.
function safe_c_name(name) {
@@ -43,7 +62,7 @@ function put_into_cache(content, obj)
function hash_path(content, salt)
{
var s = salt || 'mach'
return global_shop_path + '/build/' + content_hash(stone(blob(text(content) + '\n' + s)))
return global_shop_path + '/build/' + content_hash(stone(blob(text(content) + '\n' + s + '\n' + compiler_fingerprint)))
}
var Shop = {}

399
mcode.cm
View File

@@ -879,6 +879,77 @@ var mcode = function(ast) {
var inline_some = true
var inline_reduce = true
var inline_map = true
var inline_find = true
// --- Helper: emit arity-dispatched callback invocation ---
// ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix}
// 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")
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)
emit_3("setarg", ctx.frame, 0, ctx.null_s)
emit_2("invoke", ctx.frame, ctx.result)
emit_jump(call_done)
emit_label(call_one)
if (max_args >= 2) {
emit_3("eq", ctx.ao, ctx.fn_arity, ctx.one)
emit_jump_cond("jump_false", ctx.ao, call_two)
}
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)
if (max_args < 2) {
emit_label(call_done)
return null
}
emit_jump(call_done)
emit_label(call_two)
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)
emit_label(call_done)
return null
}
// --- Helper: forward loop scaffolding ---
// L = {arr, len, i, check, item, one, loop_label, done_label}
// body_fn(L) — called between element load and increment
var emit_forward_loop = function(L, body_fn) {
emit_2("int", L.i, 0)
emit_label(L.loop_label)
emit_3("lt", L.check, L.i, L.len)
emit_jump_cond("jump_false", L.check, L.done_label)
emit_3("load_index", L.item, L.arr, L.i)
body_fn(L)
emit_3("add", L.i, L.i, L.one)
emit_jump(L.loop_label)
emit_label(L.done_label)
return null
}
// --- Helper: reverse loop scaffolding ---
var emit_reverse_loop = function(L, body_fn) {
var zero = alloc_slot()
emit_2("int", zero, 0)
emit_3("subtract", L.i, L.len, L.one)
emit_label(L.loop_label)
emit_3("ge", L.check, L.i, zero)
emit_jump_cond("jump_false", L.check, L.done_label)
emit_3("load_index", L.item, L.arr, L.i)
body_fn(L)
emit_3("subtract", L.i, L.i, L.one)
emit_jump(L.loop_label)
emit_label(L.done_label)
return null
}
// --- Helper: emit a reduce loop body ---
// r = {acc, i, arr, fn, len, fn_arity}; emits loop updating acc in-place.
@@ -895,13 +966,12 @@ var mcode = function(ast) {
var null_s = alloc_slot()
var one = alloc_slot()
var zero = alloc_slot()
var arity_is_zero = alloc_slot()
var arity_is_one = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var f = alloc_slot()
var loop_label = gen_label("reduce_loop")
var call_one_label = gen_label("reduce_call_one")
var call_two_label = gen_label("reduce_call_two")
var call_done_label = gen_label("reduce_call_done")
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"}
emit_2("int", one, 1)
emit_2("int", zero, 0)
emit_1("null", null_s)
@@ -913,27 +983,7 @@ var mcode = function(ast) {
}
emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, acc)
emit_jump(call_done_label)
emit_label(call_one_label)
emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, acc)
emit_2("invoke", f, acc)
emit_jump(call_done_label)
emit_label(call_two_label)
emit_3("frame", f, fn_slot, 2)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, acc)
emit_3("setarg", f, 2, item)
emit_2("invoke", f, acc)
emit_label(call_done_label)
emit_arity_call(ctx, [acc, item], 2)
if (forward) {
emit_3("add", i, i, one)
} else {
@@ -942,60 +992,63 @@ var mcode = function(ast) {
emit_jump(loop_label)
}
// --- Inline expansion: arrfor(arr, fn) ---
var expand_inline_arrfor = function(dest, arr_slot, fn_slot) {
// --- Inline expansion: arrfor(arr, fn[, rev[, exit]]) ---
var expand_inline_arrfor = function(dest, args, nargs) {
var arr_slot = args.arr
var fn_slot = args.fn
var len = alloc_slot()
var i = alloc_slot()
var check = alloc_slot()
var item = alloc_slot()
var fn_arity = alloc_slot()
var arity_is_zero = alloc_slot()
var arity_is_one = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var null_s = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var f = alloc_slot()
var discard = alloc_slot()
var loop_label = gen_label("arrfor_loop")
var done_label = gen_label("arrfor_done")
var call_one_label = gen_label("arrfor_call_one")
var call_two_label = gen_label("arrfor_call_two")
var call_done_label = gen_label("arrfor_call_done")
var val = alloc_slot()
var eq_check = alloc_slot()
var early_exit = gen_label("arrfor_exit")
var done_final = gen_label("arrfor_final")
var rev_label = gen_label("arrfor_rev")
var final_label = gen_label("arrfor_fwd_done")
var fwd_L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("arrfor_fwd"), done_label: gen_label("arrfor_fwd_d")}
var rev_L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("arrfor_rev_l"), done_label: gen_label("arrfor_rev_d")}
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "arrfor"}
var body_fn = function(L) {
emit_arity_call(ctx, [L.item, L.i], 2)
if (nargs >= 4 && args.exit >= 0) {
emit_3("eq", eq_check, val, args.exit)
emit_jump_cond("jump_true", eq_check, early_exit)
}
return null
}
emit_2("length", len, arr_slot)
emit_2("int", i, 0)
emit_2("int", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot)
emit_label(loop_label)
emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, discard)
emit_jump(call_done_label)
emit_label(call_one_label)
emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_2("invoke", f, discard)
emit_jump(call_done_label)
emit_label(call_two_label)
emit_3("frame", f, fn_slot, 2)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_3("setarg", f, 2, i)
emit_2("invoke", f, discard)
emit_label(call_done_label)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(done_label)
if (nargs <= 2) {
emit_forward_loop(fwd_L, body_fn)
} else {
emit_jump_cond("wary_true", args.rev, rev_label)
emit_forward_loop(fwd_L, body_fn)
emit_jump(final_label)
emit_label(rev_label)
emit_reverse_loop(rev_L, body_fn)
emit_label(final_label)
}
emit_1("null", dest)
emit_jump(done_final)
if (nargs >= 4 && args.exit >= 0) {
emit_label(early_exit)
emit_2("move", dest, val)
}
emit_label(done_final)
return dest
}
@@ -1040,7 +1093,7 @@ var mcode = function(ast) {
emit_3("setarg", f, 1, item)
emit_2("invoke", f, val)
emit_label(call_done_label)
emit_jump_cond("jump_false", val, ret_false)
emit_jump_cond("wary_false", val, ret_false)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(ret_true)
@@ -1093,7 +1146,7 @@ var mcode = function(ast) {
emit_3("setarg", f, 1, item)
emit_2("invoke", f, val)
emit_label(call_done_label)
emit_jump_cond("jump_true", val, ret_true)
emit_jump_cond("wary_true", val, ret_true)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(ret_true)
@@ -1113,61 +1166,151 @@ var mcode = function(ast) {
var check = alloc_slot()
var item = alloc_slot()
var fn_arity = alloc_slot()
var arity_is_zero = alloc_slot()
var arity_is_one = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var null_s = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var f = alloc_slot()
var val = alloc_slot()
var loop_label = gen_label("filter_loop")
var call_one_label = gen_label("filter_call_one")
var call_two_label = gen_label("filter_call_two")
var call_done_label = gen_label("filter_call_done")
var skip_label = gen_label("filter_skip")
var done_label = gen_label("filter_done")
var skip = gen_label("filter_skip")
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "filter"}
var L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("filter_loop"), done_label: gen_label("filter_done")}
add_instr(["array", result, 0])
emit_2("length", len, arr_slot)
emit_2("int", i, 0)
emit_2("int", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot)
emit_label(loop_label)
emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, val)
emit_jump(call_done_label)
emit_label(call_one_label)
emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_2("invoke", f, val)
emit_jump(call_done_label)
emit_label(call_two_label)
emit_3("frame", f, fn_slot, 2)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_3("setarg", f, 2, i)
emit_2("invoke", f, val)
emit_label(call_done_label)
emit_jump_cond("jump_false", val, skip_label)
emit_2("push", result, item)
emit_label(skip_label)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(done_label)
emit_forward_loop(L, function(L) {
emit_arity_call(ctx, [L.item, L.i], 2)
emit_jump_cond("wary_false", val, skip)
emit_2("push", result, L.item)
emit_label(skip)
return null
})
emit_2("move", dest, result)
return dest
}
// --- Inline expansion: find(arr, target[, rev[, from]]) ---
var expand_inline_find = function(dest, args, nargs) {
var arr_slot = args.arr
var target = args.target
var len = alloc_slot()
var i = alloc_slot()
var check = alloc_slot()
var item = alloc_slot()
var fn_arity = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var null_s = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var f = alloc_slot()
var val = alloc_slot()
var is_fn = alloc_slot()
var eq_check = alloc_slot()
var fn_mode_label = gen_label("find_fn")
var found_label = gen_label("find_found")
var not_found_label = gen_label("find_nf")
var final_label = gen_label("find_final")
var vrev = gen_label("find_vrev")
var vdone = gen_label("find_vdone")
var frev = gen_label("find_frev")
var fdone = gen_label("find_fdone")
var vL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_vl"), done_label: gen_label("find_vd")}
var vrL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_vrl"), done_label: gen_label("find_vrd")}
var fL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_fl"), done_label: gen_label("find_fd")}
var ffL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_ffl"), done_label: gen_label("find_ffd")}
var frL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_frl"), done_label: gen_label("find_frd")}
var ctx = {fn: target, fn_arity: fn_arity, result: val, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "find"}
var val_body = function(L) {
emit_3("eq", eq_check, L.item, target)
emit_jump_cond("jump_true", eq_check, found_label)
return null
}
var fn_body = function(L) {
emit_arity_call(ctx, [L.item, L.i], 2)
emit_jump_cond("wary_true", val, found_label)
return null
}
emit_2("length", len, arr_slot)
emit_2("int", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("is_func", is_fn, target)
emit_jump_cond("jump_true", is_fn, fn_mode_label)
// === Value mode ===
if (nargs <= 2) {
emit_forward_loop(vL, val_body)
} else {
emit_jump_cond("wary_true", args.rev, vrev)
if (nargs >= 4 && args.from >= 0) {
emit_2("move", i, args.from)
}
if (nargs >= 4 && args.from >= 0) {
emit_label(vL.loop_label)
emit_3("lt", vL.check, vL.i, vL.len)
emit_jump_cond("jump_false", vL.check, vL.done_label)
emit_3("load_index", vL.item, vL.arr, vL.i)
val_body(vL)
emit_3("add", vL.i, vL.i, vL.one)
emit_jump(vL.loop_label)
emit_label(vL.done_label)
} else {
emit_forward_loop(vL, val_body)
}
emit_jump(vdone)
emit_label(vrev)
emit_reverse_loop(vrL, val_body)
emit_label(vdone)
}
emit_jump(not_found_label)
// === Function mode ===
emit_label(fn_mode_label)
emit_2("length", fn_arity, target)
if (nargs <= 2) {
emit_forward_loop(fL, fn_body)
} else {
emit_jump_cond("wary_true", args.rev, frev)
if (nargs >= 4 && args.from >= 0) {
emit_2("move", i, args.from)
}
if (nargs >= 4 && args.from >= 0) {
emit_label(ffL.loop_label)
emit_3("lt", ffL.check, ffL.i, ffL.len)
emit_jump_cond("jump_false", ffL.check, ffL.done_label)
emit_3("load_index", ffL.item, ffL.arr, ffL.i)
fn_body(ffL)
emit_3("add", ffL.i, ffL.i, ffL.one)
emit_jump(ffL.loop_label)
emit_label(ffL.done_label)
} else {
emit_forward_loop(ffL, fn_body)
}
emit_jump(fdone)
emit_label(frev)
emit_reverse_loop(frL, fn_body)
emit_label(fdone)
}
emit_label(not_found_label)
emit_1("null", dest)
emit_jump(final_label)
emit_label(found_label)
emit_2("move", dest, i)
emit_label(final_label)
return dest
}
// --- Inline expansion: array(arr, fn) → map ---
var expand_inline_map = function(dest, arr_slot, fn_slot) {
var result = alloc_slot()
@@ -1342,7 +1485,7 @@ var mcode = function(ast) {
// No initial
emit_3("lt", check, zero, len)
emit_jump_cond("jump_false", check, null_label)
emit_jump_cond("jump_true", rev_slot, no_init_rev)
emit_jump_cond("wary_true", rev_slot, no_init_rev)
// No initial, forward
emit_3("load_index", acc, arr_slot, zero)
emit_2("move", i, one)
@@ -1364,7 +1507,7 @@ var mcode = function(ast) {
emit_jump(final_label)
// Has initial
emit_label(has_init)
emit_jump_cond("jump_true", rev_slot, init_rev)
emit_jump_cond("wary_true", rev_slot, init_rev)
// Has initial, forward
emit_2("move", acc, init_slot)
emit_2("int", i, 0)
@@ -1411,7 +1554,7 @@ var mcode = function(ast) {
left_slot = gen_expr(left, -1)
dest = alloc_slot()
emit_2("move", dest, left_slot)
emit_jump_cond("jump_false", dest, end_label)
emit_jump_cond("wary_false", dest, end_label)
right_slot = gen_expr(right, -1)
emit_2("move", dest, right_slot)
emit_label(end_label)
@@ -1423,7 +1566,7 @@ var mcode = function(ast) {
left_slot = gen_expr(left, -1)
dest = alloc_slot()
emit_2("move", dest, left_slot)
emit_jump_cond("jump_true", dest, end_label)
emit_jump_cond("wary_true", dest, end_label)
right_slot = gen_expr(right, -1)
emit_2("move", dest, right_slot)
emit_label(end_label)
@@ -1992,12 +2135,22 @@ var mcode = function(ast) {
emit_label(guard_done)
return a1
}
// Callback intrinsics → inline mcode loops
if (nargs == 2 && fname == "arrfor" && inline_arrfor) {
// apply(fn, arr) → direct opcode
if (nargs == 2 && fname == "apply") {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
d = alloc_slot()
return expand_inline_arrfor(d, a0, a1)
emit_3("apply", d, a0, a1)
return d
}
// Callback intrinsics → inline mcode loops
if (fname == "arrfor" && nargs >= 2 && nargs <= 4 && inline_arrfor) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
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_arrfor(d, {arr: a0, fn: a1, rev: a2, exit: a3}, nargs)
}
if (nargs == 2 && fname == "every" && inline_every) {
a0 = gen_expr(args_list[0], -1)
@@ -2017,6 +2170,14 @@ var mcode = function(ast) {
d = alloc_slot()
return expand_inline_filter(d, a0, a1)
}
if (fname == "find" && nargs >= 2 && nargs <= 4 && inline_find) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
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_find(d, {arr: a0, target: a1, rev: a2, from: a3}, nargs)
}
if (fname == "reduce" && nargs >= 2 && nargs <= 4 && inline_reduce) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
@@ -2200,7 +2361,7 @@ var mcode = function(ast) {
else_label = gen_label("tern_else")
end_label = gen_label("tern_end")
cond_slot = gen_expr(cond, -1)
emit_jump_cond("jump_false", cond_slot, else_label)
emit_jump_cond("wary_false", cond_slot, else_label)
dest = alloc_slot()
then_slot = gen_expr(then_expr, -1)
emit_2("move", dest, then_slot)
@@ -2425,7 +2586,7 @@ var mcode = function(ast) {
else_label = gen_label("if_else")
end_label = gen_label("if_end")
cond_slot = gen_expr(cond, -1)
emit_jump_cond("jump_false", cond_slot, else_label)
emit_jump_cond("wary_false", cond_slot, else_label)
_i = 0
while (_i < length(then_stmts)) {
gen_statement(then_stmts[_i])
@@ -2466,7 +2627,7 @@ var mcode = function(ast) {
}
emit_label(start_label)
cond_slot = gen_expr(cond, -1)
emit_jump_cond("jump_false", cond_slot, end_label)
emit_jump_cond("wary_false", cond_slot, end_label)
_i = 0
while (_i < length(stmts)) {
gen_statement(stmts[_i])
@@ -2501,7 +2662,7 @@ var mcode = function(ast) {
}
emit_label(cond_label)
cond_slot = gen_expr(cond, -1)
emit_jump_cond("jump_true", cond_slot, start_label)
emit_jump_cond("wary_true", cond_slot, start_label)
emit_label(end_label)
s_loop_break = old_break
s_loop_continue = old_continue
@@ -2535,7 +2696,7 @@ var mcode = function(ast) {
emit_label(start_label)
if (test != null) {
test_slot = gen_expr(test, -1)
emit_jump_cond("jump_false", test_slot, end_label)
emit_jump_cond("wary_false", test_slot, end_label)
}
_i = 0
while (_i < length(stmts)) {

View File

@@ -89,7 +89,7 @@
[P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL, WARYTRUE, WARYFALSE, JMPEMPTY
[P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
@@ -282,6 +282,10 @@ typedef enum MachOpcode {
MACH_IS_UPPER, /* R(A) = is_upper(R(B)) */
MACH_IS_WS, /* R(A) = is_whitespace(R(B)) */
MACH_IS_ACTOR, /* R(A) = is_actor(R(B)) — has actor_sym property */
MACH_APPLY, /* R(A) = apply(R(B), R(C)) — call fn with args from array (ABC) */
MACH_WARYTRUE, /* if toBool(R(A)): pc += sBx — coercing (iAsBx) */
MACH_WARYFALSE, /* if !toBool(R(A)): pc += sBx — coercing (iAsBx) */
MACH_JMPEMPTY, /* if R(A)==empty_text: pc += sBx (iAsBx) */
MACH_OP_COUNT
} MachOpcode;
@@ -406,6 +410,10 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_IS_UPPER] = "is_upper",
[MACH_IS_WS] = "is_ws",
[MACH_IS_ACTOR] = "is_actor",
[MACH_APPLY] = "apply",
[MACH_WARYTRUE] = "wary_true",
[MACH_WARYFALSE] = "wary_false",
[MACH_JMPEMPTY] = "jump_empty",
};
/* ---- Compile-time constant pool entry ---- */
@@ -1427,6 +1435,8 @@ vm_dispatch:
DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER),
DT(MACH_IS_LOWER), DT(MACH_IS_UPPER),
DT(MACH_IS_WS), DT(MACH_IS_ACTOR),
DT(MACH_APPLY),
DT(MACH_WARYTRUE), DT(MACH_WARYFALSE), DT(MACH_JMPEMPTY),
};
#pragma GCC diagnostic pop
#undef DT
@@ -2069,12 +2079,7 @@ vm_dispatch:
}
VM_CASE(MACH_JMPTRUE): {
JSValue v = frame->slots[a];
int cond;
if (v == JS_TRUE) cond = 1;
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
else cond = JS_ToBool(ctx, v);
if (cond) {
if (frame->slots[a] == JS_TRUE) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
@@ -2095,12 +2100,7 @@ vm_dispatch:
}
VM_CASE(MACH_JMPFALSE): {
JSValue v = frame->slots[a];
int cond;
if (v == JS_TRUE) cond = 1;
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
else cond = JS_ToBool(ctx, v);
if (!cond) {
if (frame->slots[a] == JS_FALSE) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
@@ -2517,6 +2517,49 @@ vm_dispatch:
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_APPLY): {
/* A=dest, B=fn, C=arr_or_val */
JSValue fn_val = frame->slots[b];
JSValue arg_val = frame->slots[c];
if (!mist_is_function(fn_val)) {
frame->slots[a] = fn_val;
VM_BREAK();
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
JSValue ret;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
ctx->vm_call_depth++;
if (!mist_is_array(arg_val)) {
/* Non-array: use as single argument */
if (!mach_check_call_arity(ctx, fn, 1)) {
ctx->vm_call_depth--;
goto disrupt;
}
ret = JS_CallInternal(ctx, fn_val, JS_NULL, 1, &arg_val, 0);
} else {
JSArray *arr = JS_VALUE_GET_ARRAY(arg_val);
int len = arr->len;
if (!mach_check_call_arity(ctx, fn, len)) {
ctx->vm_call_depth--;
goto disrupt;
}
if (len == 0) {
ret = JS_CallInternal(ctx, fn_val, JS_NULL, 0, NULL, 0);
} else {
JSValue *args = alloca(sizeof(JSValue) * len);
for (int i = 0; i < len; i++)
args[i] = arr->values[i];
ret = JS_CallInternal(ctx, fn_val, JS_NULL, len, args, 0);
}
}
ctx->vm_call_depth--;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[a] = ret;
VM_BREAK();
}
/* Logical */
VM_CASE(MACH_NOT): {
int bval = JS_ToBool(ctx, frame->slots[b]);
@@ -2826,6 +2869,67 @@ vm_dispatch:
VM_BREAK();
}
/* Wary jumps — coerce via JS_ToBool (old JMPTRUE/JMPFALSE behavior) */
VM_CASE(MACH_WARYTRUE): {
JSValue v = frame->slots[a];
int cond;
if (v == JS_TRUE) cond = 1;
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
else cond = JS_ToBool(ctx, v);
if (cond) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf == 2) {
result = JS_RaiseDisrupt(ctx, "interrupted");
goto done;
}
if (pf == 1) {
if (ctx->vm_call_depth > 0)
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
else
goto suspend;
}
}
}
VM_BREAK();
}
VM_CASE(MACH_WARYFALSE): {
JSValue v = frame->slots[a];
int cond;
if (v == JS_TRUE) cond = 1;
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
else cond = JS_ToBool(ctx, v);
if (!cond) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf == 2) {
result = JS_RaiseDisrupt(ctx, "interrupted");
goto done;
}
if (pf == 1) {
if (ctx->vm_call_depth > 0)
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
else
goto suspend;
}
}
}
VM_BREAK();
}
VM_CASE(MACH_JMPEMPTY): {
if (frame->slots[a] == JS_EMPTY_TEXT) {
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
}
VM_BREAK();
}
/* Disrupt (mcode alias) */
VM_CASE(MACH_DISRUPT):
goto disrupt;
@@ -3174,6 +3278,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
else if (strcmp(op, "is_upper") == 0) { AB2(MACH_IS_UPPER); }
else if (strcmp(op, "is_ws") == 0) { AB2(MACH_IS_WS); }
else if (strcmp(op, "is_actor") == 0) { AB2(MACH_IS_ACTOR); }
else if (strcmp(op, "apply") == 0) { ABC3(MACH_APPLY); }
/* Logical */
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); }
@@ -3324,6 +3429,34 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "jump_null") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPNULL, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "wary_true") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_WARYTRUE, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "wary_false") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_WARYFALSE, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "jump_empty") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPEMPTY, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
/* Return / error */
else if (strcmp(op, "return") == 0) {
EM(MACH_ABC(MACH_RETURN, A1, 0, 0));

View File

@@ -9446,15 +9446,23 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV
if (argc < 1) return JS_NULL;
if (!JS_IsFunction (argv[0])) return argv[0];
JSFunction *fn = JS_VALUE_GET_FUNCTION (argv[0]);
if (argc < 2)
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);
if (!JS_IsArray (argv[1]))
if (!JS_IsArray (argv[1])) {
if (fn->length >= 0 && 1 > fn->length)
return JS_RaiseDisrupt (ctx, "too many arguments for apply: expected %d, got 1", fn->length);
return JS_CallInternal (ctx, argv[0], JS_NULL, 1, &argv[1], 0);
}
JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]);
int len = arr->len;
if (fn->length >= 0 && len > fn->length)
return JS_RaiseDisrupt (ctx, "too many arguments for apply: expected %d, got %d", fn->length, len);
if (len == 0)
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);

View File

@@ -69,12 +69,19 @@ var streamline = function(ir, log) {
var no_clear_ops = {
int: true, access: true, true: true, false: true, move: true, null: true,
jump: true, jump_true: true, jump_false: true, jump_not_null: true,
wary_true: true, wary_false: true, jump_null: true, jump_empty: true,
return: true, disrupt: true,
store_field: true, store_index: true, store_dynamic: true,
push: true, setarg: true, invoke: true, tail_invoke: true,
stone_text: true
}
var is_cond_jump = function(op) {
return op == "jump_true" || op == "jump_false" || op == "jump_not_null"
|| op == "wary_true" || op == "wary_false"
|| op == "jump_null" || op == "jump_empty"
}
// --- Logging support ---
var ir_stats = null
@@ -391,7 +398,12 @@ var streamline = function(ir, log) {
abs: T_NUM, floor: T_NUM, ceiling: T_NUM,
round: T_NUM, trunc: T_NUM, fraction: T_NUM,
integer: T_NUM, whole: T_NUM, sign: T_NUM,
max: T_NUM, min: T_NUM, remainder: T_NUM, modulo: T_NUM
max: T_NUM, min: T_NUM, remainder: T_NUM, modulo: T_NUM,
is_integer: T_BOOL, is_text: T_BOOL, is_number: T_BOOL,
is_null: T_BOOL, is_array: T_BOOL, is_function: T_BOOL,
is_object: T_BOOL, is_logical: T_BOOL, is_stone: T_BOOL,
is_blob: T_BOOL, starts_with: T_BOOL, ends_with: T_BOOL,
some: T_BOOL, every: T_BOOL
}
var narrow_arith_type = function(write_types, param_types, instr, typ) {
@@ -691,7 +703,49 @@ var streamline = function(ir, log) {
if (is_array(next)) {
next_op = next[0]
if (next_op == "jump_false" && next[1] == dest) {
// is_null + jump fusion: replace with jump_null / jump_not_null
if (op == "is_null" && (next_op == "jump_true" || next_op == "wary_true") && next[1] == dest) {
jlen = length(next)
nc = nc + 1
instructions[i] = "_nop_tc_" + text(nc)
instructions[i + 1] = ["jump_null", src, next[2], next[jlen - 2], next[jlen - 1]]
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "is_null_jump_fusion",
at: i,
before: [instr, next],
after: [instructions[i], instructions[i + 1]],
why: {slot: src, fused_to: "jump_null"}
}
}
slot_types[dest] = T_BOOL
i = i + 2
continue
}
if (op == "is_null" && (next_op == "jump_false" || next_op == "wary_false") && next[1] == dest) {
jlen = length(next)
nc = nc + 1
instructions[i] = "_nop_tc_" + text(nc)
instructions[i + 1] = ["jump_not_null", src, next[2], next[jlen - 2], next[jlen - 1]]
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "is_null_jump_fusion",
at: i,
before: [instr, next],
after: [instructions[i], instructions[i + 1]],
why: {slot: src, fused_to: "jump_not_null"}
}
}
slot_types[dest] = T_BOOL
i = i + 2
continue
}
if ((next_op == "jump_false" || next_op == "wary_false") && next[1] == dest) {
target_label = next[2]
if (slot_is(slot_types, src, checked_type)) {
nc = nc + 1
@@ -767,7 +821,7 @@ var streamline = function(ir, log) {
continue
}
if (next_op == "jump_true" && next[1] == dest) {
if ((next_op == "jump_true" || next_op == "wary_true") && next[1] == dest) {
target_label = next[2]
if (slot_is(slot_types, src, checked_type)) {
nc = nc + 1
@@ -918,6 +972,32 @@ var streamline = function(ir, log) {
continue
}
// Wary-to-certain: if slot type is T_BOOL, downgrade wary to certain jump
if (op == "wary_true" && slot_is(slot_types, instr[1], T_BOOL)) {
instr[0] = "jump_true"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "wary_to_certain",
at: i, before: "wary_true", after: "jump_true",
why: {slot: instr[1], known_type: T_BOOL}
}
}
}
if (op == "wary_false" && slot_is(slot_types, instr[1], T_BOOL)) {
instr[0] = "jump_false"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "wary_to_certain",
at: i, before: "wary_false", after: "jump_false",
why: {slot: instr[1], known_type: T_BOOL}
}
}
}
track_types(slot_types, instr)
i = i + 1
}
@@ -1075,11 +1155,12 @@ var streamline = function(ir, log) {
next_op = next[0]
nlen = length(next)
// not d, x; jump_false d, label → jump_true x, label
// not d, x; jump_false d, label → wary_true x, label
// (removing `not` removes coercion, so result must be wary)
if (next_op == "jump_false" && next[1] == instr[1]) {
nc = nc + 1
instructions[i] = "_nop_bl_" + text(nc)
instructions[i + 1] = ["jump_true", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
instructions[i + 1] = ["wary_true", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
if (events != null) {
events[] = {
event: "rewrite", pass: "simplify_booleans",
@@ -1092,11 +1173,11 @@ var streamline = function(ir, log) {
continue
}
// not d, x; jump_true d, label → jump_false x, label
// not d, x; jump_true d, label → wary_false x, label
if (next_op == "jump_true" && next[1] == instr[1]) {
nc = nc + 1
instructions[i] = "_nop_bl_" + text(nc)
instructions[i + 1] = ["jump_false", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
instructions[i + 1] = ["wary_false", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
if (events != null) {
events[] = {
event: "rewrite", pass: "simplify_booleans",
@@ -1109,6 +1190,40 @@ var streamline = function(ir, log) {
continue
}
// not d, x; wary_false d, label → wary_true x, label
if (next_op == "wary_false" && next[1] == instr[1]) {
nc = nc + 1
instructions[i] = "_nop_bl_" + text(nc)
instructions[i + 1] = ["wary_true", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
if (events != null) {
events[] = {
event: "rewrite", pass: "simplify_booleans",
rule: "not_wary_false_fusion", at: i,
before: [instr, next],
after: [instructions[i], instructions[i + 1]]
}
}
i = i + 2
continue
}
// not d, x; wary_true d, label → wary_false x, label
if (next_op == "wary_true" && next[1] == instr[1]) {
nc = nc + 1
instructions[i] = "_nop_bl_" + text(nc)
instructions[i + 1] = ["wary_false", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
if (events != null) {
events[] = {
event: "rewrite", pass: "simplify_booleans",
rule: "not_wary_true_fusion", at: i,
before: [instr, next],
after: [instructions[i], instructions[i + 1]]
}
}
i = i + 2
continue
}
// not d1, x; not d2, d1 → move d2, x
if (next_op == "not" && next[2] == instr[1]) {
nc = nc + 1
@@ -1196,7 +1311,7 @@ var streamline = function(ir, log) {
}
// Control flow with a read at position 1: substitute then clear
if (op == "return" || op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
if (op == "return" || is_cond_jump(op)) {
actual = copies[text(instr[1])]
if (actual != null) {
instr[1] = actual
@@ -1378,7 +1493,7 @@ var streamline = function(ir, log) {
op = instr[0]
if (op == "jump") {
target = instr[1]
} else if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
} else if (is_cond_jump(op)) {
target = instr[2]
}
if (target != null && is_text(target)) {
@@ -1585,7 +1700,7 @@ var streamline = function(ir, log) {
if (is_number(tgt)) stack[] = tgt
continue
}
if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
if (is_cond_jump(op)) {
tgt = label_map[instr[2]]
if (is_number(tgt)) stack[] = tgt
stack[] = idx + 1
@@ -1690,6 +1805,7 @@ var streamline = function(ir, log) {
frame: [1, 2], goframe: [1, 2],
jump: [], disrupt: [],
jump_true: [1], jump_false: [1], jump_not_null: [1],
wary_true: [1], wary_false: [1], jump_null: [1], jump_empty: [1],
return: [1],
stone_text: [1]
}
@@ -1720,6 +1836,7 @@ var streamline = function(ir, log) {
setarg: [], store_field: [], store_index: [], store_dynamic: [],
push: [], set_var: [], stone_text: [],
jump: [], jump_true: [], jump_false: [], jump_not_null: [],
wary_true: [], wary_false: [], jump_null: [], jump_empty: [],
return: [], disrupt: []
}
@@ -1733,6 +1850,7 @@ var streamline = function(ir, log) {
store_dynamic: [1, 2, 3],
push: [1, 2], set_var: [1], stone_text: [1],
jump: [], jump_true: [1], jump_false: [1], jump_not_null: [1],
wary_true: [1], wary_false: [1], jump_null: [1], jump_empty: [1],
return: [1], disrupt: []
}
@@ -1870,7 +1988,7 @@ var streamline = function(ir, log) {
target = null
if (op == "jump") {
target = instr[1]
} else if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
} else if (is_cond_jump(op)) {
target = instr[2]
}
if (target == null || !is_text(target)) {
@@ -2591,6 +2709,390 @@ var streamline = function(ir, log) {
return null
}
// =========================================================
// Sensory function IR synthesis for callback inlining
// =========================================================
var sensory_opcodes = {
is_array: "is_array", is_function: "is_func", is_object: "is_record",
is_stone: "is_stone", is_integer: "is_int", is_text: "is_text",
is_number: "is_num", is_logical: "is_bool", is_null: "is_null",
is_blob: "is_blob", is_data: "is_data",
is_true: "is_true", is_false: "is_false", is_fit: "is_fit",
is_character: "is_char", is_digit: "is_digit", is_letter: "is_letter",
is_lower: "is_lower", is_upper: "is_upper", is_whitespace: "is_ws",
is_actor: "is_actor", length: "length"
}
var make_sensory_ir = function(name) {
var opcode = sensory_opcodes[name]
if (opcode == null) return null
return {
name: name, nr_args: 1, nr_close_slots: 0, nr_slots: 3,
instructions: [[opcode, 2, 1, 0, 0], ["return", 2, 0, 0]]
}
}
// =========================================================
// Inline eligibility check
// =========================================================
var prefer_inline_set = {
filter: true, every: true, some: true, arrfor: true,
reduce: true, array: true
}
// Structural eligibility: closures, get/put, disruption, nested functions
var can_inline_structural = function(callee_func) {
var instrs = null
var i = 0
var instr = null
if (callee_func.nr_close_slots > 0) return false
instrs = callee_func.instructions
if (instrs == null) return false
i = 0
while (i < length(instrs)) {
instr = instrs[i]
if (is_array(instr)) {
if (instr[0] == "get" || instr[0] == "put") {
return false
}
// Reject if function creates child functions (closures may capture
// from the inlined frame, breaking get/put slot references)
if (instr[0] == "function") {
return false
}
}
i = i + 1
}
if (callee_func.disruption_pc != null && callee_func.disruption_pc > 0) {
return false
}
return true
}
// Size eligibility: instruction count check
var can_inline_size = function(callee_func, is_prefer) {
var instrs = callee_func.instructions
var count = 0
var i = 0
var limit = 0
if (instrs == null) return false
i = 0
while (i < length(instrs)) {
if (is_array(instrs[i])) count = count + 1
i = i + 1
}
limit = is_prefer ? 200 : 40
return count <= limit
}
var can_inline = function(callee_func, is_prefer) {
if (!can_inline_structural(callee_func)) return false
return can_inline_size(callee_func, is_prefer)
}
// =========================================================
// Pass: inline_calls — inline same-module + sensory functions
// =========================================================
var inline_counter = 0
var inline_calls = function(func, ir, log) {
var instructions = func.instructions
var num_instr = 0
var i = 0
var j = 0
var k = 0
var instr = null
var op = null
var changed = false
var inline_count = 0
var max_inlines = 20
var slot_to_func_idx = {}
var slot_to_intrinsic = {}
var callee_slot = 0
var frame_slot = 0
var argc = 0
var result_slot = 0
var call_start = 0
var call_end = 0
var arg_slots = null
var callee_func = null
var is_prefer = false
var base = 0
var remap = null
var cinstr = null
var cop = null
var new_instr = null
var refs = null
var label_prefix = null
var cont_label = null
var spliced = null
var before = null
var after = null
var inlined_body = null
var fi = null
var intrinsic_name = null
var is_single_use = false
var ref_count = 0
var ri = 0
if (instructions == null) return false
num_instr = length(instructions)
if (num_instr == 0) return false
// Build resolution maps
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr)) {
op = instr[0]
if (op == "function") {
slot_to_func_idx[text(instr[1])] = instr[2]
} else if (op == "access" && is_object(instr[2]) && instr[2].make == "intrinsic") {
slot_to_intrinsic[text(instr[1])] = instr[2].name
}
}
i = i + 1
}
// Scan for frame/setarg/invoke sequences and inline
i = 0
while (i < length(instructions)) {
instr = instructions[i]
if (!is_array(instr) || instr[0] != "frame") {
i = i + 1
continue
}
if (inline_count >= max_inlines) {
i = i + 1
continue
}
frame_slot = instr[1]
callee_slot = instr[2]
argc = instr[3]
call_start = i
// Collect setarg and find invoke
arg_slots = array(argc + 1, -1)
j = i + 1
call_end = -1
while (j < length(instructions)) {
instr = instructions[j]
if (!is_array(instr)) {
j = j + 1
continue
}
op = instr[0]
if (op == "setarg" && instr[1] == frame_slot) {
arg_slots[instr[2]] = instr[3]
} else if ((op == "invoke" || op == "tail_invoke") && instr[1] == frame_slot) {
result_slot = instr[2]
call_end = j
j = j + 1
break
} else if (op == "frame" || op == "goframe") {
// Another frame before invoke — abort this pattern
break
}
j = j + 1
}
if (call_end < 0) {
i = i + 1
continue
}
// Resolve callee
callee_func = null
is_prefer = false
fi = slot_to_func_idx[text(callee_slot)]
if (fi != null && ir.functions != null && fi >= 0 && fi < length(ir.functions)) {
callee_func = ir.functions[fi]
}
if (callee_func == null) {
intrinsic_name = slot_to_intrinsic[text(callee_slot)]
if (intrinsic_name != null) {
if (sensory_opcodes[intrinsic_name] != null) {
callee_func = make_sensory_ir(intrinsic_name)
}
if (callee_func != null) {
is_prefer = true
}
}
}
if (callee_func == null) {
i = i + 1
continue
}
// Check if callee is a single-use function literal — skip size limit
is_single_use = false
if (fi != null) {
ref_count = 0
ri = 0
while (ri < length(instructions)) {
if (is_array(instructions[ri])) {
// Count frame instructions that use this slot as callee (position 2)
if (instructions[ri][0] == "frame" && instructions[ri][2] == callee_slot) {
ref_count = ref_count + 1
}
// Also count setarg where slot is passed as value (position 3)
if (instructions[ri][0] == "setarg" && instructions[ri][3] == callee_slot) {
ref_count = ref_count + 1
}
}
ri = ri + 1
}
if (ref_count <= 1) is_single_use = true
}
// Check eligibility
if (!can_inline_structural(callee_func)) {
i = i + 1
continue
}
if (!is_single_use && !can_inline_size(callee_func, is_prefer)) {
i = i + 1
continue
}
// Slot remapping
base = func.nr_slots
func.nr_slots = func.nr_slots + callee_func.nr_slots
remap = array(callee_func.nr_slots, -1)
// Slot 0 (this) → arg_slots[0] if provided, else allocate a null slot
if (length(arg_slots) > 0 && arg_slots[0] >= 0) {
remap[0] = arg_slots[0]
} else {
remap[0] = base
}
// Params 1..nr_args → corresponding arg_slots
j = 1
while (j <= callee_func.nr_args) {
if (j < length(arg_slots) && arg_slots[j] >= 0) {
remap[j] = arg_slots[j]
} else {
remap[j] = base + j
}
j = j + 1
}
// Temporaries → fresh slots
j = callee_func.nr_args + 1
while (j < callee_func.nr_slots) {
remap[j] = base + j
j = j + 1
}
// Generate unique label prefix
inline_counter = inline_counter + 1
label_prefix = "_inl" + text(inline_counter) + "_"
cont_label = label_prefix + "cont"
// Build inlined body with remapping
// Unmapped params (e.g. caller passes 1 arg to a 2-param function)
// must be explicitly nulled. compress_slots may merge the fresh
// slot (base+j) with a previously-live slot that holds a non-null
// value, so the default-param jump_not_null preamble would skip
// the default assignment and use a stale value instead.
inlined_body = []
j = 0
while (j <= callee_func.nr_args) {
if (!(j < length(arg_slots) && arg_slots[j] >= 0)) {
inlined_body[] = ["null", remap[j], 0, 0]
}
j = j + 1
}
k = 0
while (k < length(callee_func.instructions)) {
cinstr = callee_func.instructions[k]
// Labels (strings that aren't nop markers)
if (is_text(cinstr)) {
if (starts_with(cinstr, "_nop_")) {
inlined_body[] = cinstr
} else {
inlined_body[] = label_prefix + cinstr
}
k = k + 1
continue
}
if (!is_array(cinstr)) {
inlined_body[] = cinstr
k = k + 1
continue
}
cop = cinstr[0]
// Handle return → move + jump to continuation
if (cop == "return") {
new_instr = ["move", result_slot, remap[cinstr[1]], cinstr[2], cinstr[3]]
inlined_body[] = new_instr
inlined_body[] = ["jump", cont_label, cinstr[2], cinstr[3]]
k = k + 1
continue
}
// Clone and remap the instruction
new_instr = array(cinstr)
refs = get_slot_refs(cinstr)
j = 0
while (j < length(refs)) {
if (new_instr[refs[j]] >= 0 && new_instr[refs[j]] < length(remap)) {
new_instr[refs[j]] = remap[new_instr[refs[j]]]
}
j = j + 1
}
// Remap labels in jump instructions
if (cop == "jump" && is_text(cinstr[1]) && !starts_with(cinstr[1], "_nop_")) {
new_instr[1] = label_prefix + cinstr[1]
} else if (is_cond_jump(cop)
&& is_text(cinstr[2]) && !starts_with(cinstr[2], "_nop_")) {
new_instr[2] = label_prefix + cinstr[2]
}
// Skip function instructions (don't inline nested function definitions)
if (cop == "function") {
// Keep the instruction but don't remap func_id — it still refers to ir.functions
// Only remap slot position 1 (the destination slot)
new_instr = array(cinstr)
if (cinstr[1] >= 0 && cinstr[1] < length(remap)) {
new_instr[1] = remap[cinstr[1]]
}
}
inlined_body[] = new_instr
k = k + 1
}
// Add continuation label
inlined_body[] = cont_label
// Splice: replace instructions[call_start..call_end] with inlined_body
before = array(instructions, 0, call_start)
after = array(instructions, call_end + 1, length(instructions))
spliced = array(before, inlined_body)
instructions = array(spliced, after)
func.instructions = instructions
changed = true
inline_count = inline_count + 1
// Continue scanning from after the inlined body
i = call_start + length(inlined_body)
}
return changed
}
// =========================================================
// Compose all passes
// =========================================================
@@ -2693,13 +3195,12 @@ var streamline = function(ir, log) {
ir._diagnostics = []
}
// Process main function
// Phase 1: Optimize all functions (bottom-up, existing behavior)
if (ir.main != null) {
optimize_function(ir.main, log)
insert_stone_text(ir.main, log)
}
// Process all sub-functions (resolve closure types from parent first)
var fi = 0
if (ir.functions != null) {
fi = 0
@@ -2711,7 +3212,60 @@ var streamline = function(ir, log) {
}
}
// Compress slots across all functions (must run after per-function passes)
// Phase 2: Inline pass
var changed_main = false
var changed_fns = null
if (ir.main != null) {
changed_main = inline_calls(ir.main, ir, log)
}
if (ir.functions != null) {
changed_fns = array(length(ir.functions), false)
fi = 0
while (fi < length(ir.functions)) {
changed_fns[fi] = inline_calls(ir.functions[fi], ir, log)
fi = fi + 1
}
}
// Phase 3: Re-optimize inlined functions
if (changed_main) {
optimize_function(ir.main, log)
insert_stone_text(ir.main, log)
}
if (ir.functions != null) {
fi = 0
while (fi < length(ir.functions)) {
if (changed_fns != null && changed_fns[fi]) {
optimize_function(ir.functions[fi], log)
insert_stone_text(ir.functions[fi], log)
}
fi = fi + 1
}
}
// Phase 4: Cascade — second inline round (callbacks inside inlined bodies)
if (changed_main) {
changed_main = inline_calls(ir.main, ir, log)
if (changed_main) {
optimize_function(ir.main, log)
insert_stone_text(ir.main, log)
}
}
if (ir.functions != null) {
fi = 0
while (fi < length(ir.functions)) {
if (changed_fns != null && changed_fns[fi]) {
changed_fns[fi] = inline_calls(ir.functions[fi], ir, log)
if (changed_fns[fi]) {
optimize_function(ir.functions[fi], log)
insert_stone_text(ir.functions[fi], log)
}
}
fi = fi + 1
}
}
// Phase 5: Compress slots across all functions (must run after per-function passes)
compress_slots(ir)
// Expose DEF/USE functions via log if requested

View File

@@ -3555,6 +3555,113 @@ run("inline map empty array", function() {
if (length(result) != 0) fail("map of empty should be empty")
})
// ============================================================================
// NUMERIC INTRINSIC CALLBACK INLINING
// ============================================================================
var mymap = function(arr, fn) {
var result = array(length(arr))
var i = 0
while (i < length(arr)) {
result[i] = fn(arr[i])
i = i + 1
}
return result
}
var myfold = function(arr, fn) {
var acc = arr[0]
var i = 1
while (i < length(arr)) {
acc = fn(acc, arr[i])
i = i + 1
}
return acc
}
run("inline callback abs", function() {
var result = mymap([-3, 5, -1.5, 0], function(a) { return abs(a) })
assert_eq(result[0], 3, "abs(-3)")
assert_eq(result[1], 5, "abs(5)")
assert_eq(result[2], 1.5, "abs(-1.5)")
assert_eq(result[3], 0, "abs(0)")
})
run("inline callback neg", function() {
var result = mymap([3, -5, 0, 1.5], function(a) { return neg(a) })
assert_eq(result[0], -3, "neg(3)")
assert_eq(result[1], 5, "neg(-5)")
assert_eq(result[2], 0, "neg(0)")
assert_eq(result[3], -1.5, "neg(1.5)")
})
run("inline callback sign", function() {
var result = mymap([-7, 0, 42, -0.5], function(a) { return sign(a) })
assert_eq(result[0], -1, "sign(-7)")
assert_eq(result[1], 0, "sign(0)")
assert_eq(result[2], 1, "sign(42)")
assert_eq(result[3], -1, "sign(-0.5)")
})
run("inline callback fraction", function() {
var result = mymap([3.75, -2.5, 5.0], function(a) { return fraction(a) })
assert_eq(result[0], 0.75, "fraction(3.75)")
assert_eq(result[1], -0.5, "fraction(-2.5)")
assert_eq(result[2], 0, "fraction(5.0)")
})
run("inline callback floor", function() {
var result = mymap([3.7, -2.3, 5.0, 1.9], function(a) { return floor(a) })
assert_eq(result[0], 3, "floor(3.7)")
assert_eq(result[1], -3, "floor(-2.3)")
assert_eq(result[2], 5, "floor(5.0)")
assert_eq(result[3], 1, "floor(1.9)")
})
run("inline callback ceiling", function() {
var result = mymap([3.2, -2.7, 5.0, 1.1], function(a) { return ceiling(a) })
assert_eq(result[0], 4, "ceiling(3.2)")
assert_eq(result[1], -2, "ceiling(-2.7)")
assert_eq(result[2], 5, "ceiling(5.0)")
assert_eq(result[3], 2, "ceiling(1.1)")
})
run("inline callback round", function() {
var result = mymap([3.5, -2.5, 5.0, 1.4], function(a) { return round(a) })
assert_eq(result[0], 4, "round(3.5)")
assert_eq(result[1], -3, "round(-2.5)")
assert_eq(result[2], 5, "round(5.0)")
assert_eq(result[3], 1, "round(1.4)")
})
run("inline callback trunc", function() {
var result = mymap([3.7, -2.3, 5.0, -1.9], function(a) { return trunc(a) })
assert_eq(result[0], 3, "trunc(3.7)")
assert_eq(result[1], -2, "trunc(-2.3)")
assert_eq(result[2], 5, "trunc(5.0)")
assert_eq(result[3], -1, "trunc(-1.9)")
})
run("inline callback max", function() {
var result = myfold([3, 7, 2, 9, 1], function(a, b) { return max(a, b) })
assert_eq(result, 9, "max reduce")
})
run("inline callback min", function() {
var result = myfold([3, 7, 2, 9, 1], function(a, b) { return min(a, b) })
assert_eq(result, 1, "min reduce")
})
run("inline callback modulo", function() {
var result = myfold([17, 5], function(a, b) { return modulo(a, b) })
assert_eq(result, 2, "modulo")
})
run("inline callback remainder", function() {
var result = myfold([-17, 5], function(a, b) { return remainder(a, b) })
assert_eq(result, -2, "remainder")
})
// ============================================================================
// STRING METHOD EDGE CASES
// ============================================================================
@@ -5875,6 +5982,187 @@ run("gc closure - factory pattern survives gc", function() {
assert_eq(b.say(), "hello bob", "second factory closure")
})
// ============================================================================
// INLINE LOOP EXPANSION TESTS
// ============================================================================
// --- filter inline expansion ---
run("filter inline - integer predicate", function() {
var result = filter([0, 1.25, 2, 3.5, 4, 5.75], is_integer)
assert_eq(length(result), 3, "filter integer count")
assert_eq(result[0], 0, "filter integer [0]")
assert_eq(result[1], 2, "filter integer [1]")
assert_eq(result[2], 4, "filter integer [2]")
})
run("filter inline - all pass", function() {
var result = filter([1, 2, 3], function(x) { return true })
assert_eq(length(result), 3, "filter all pass length")
})
run("filter inline - none pass", function() {
var result = filter([1, 2, 3], function(x) { return false })
assert_eq(length(result), 0, "filter none pass length")
})
run("filter inline - empty", function() {
var result = filter([], is_integer)
assert_eq(length(result), 0, "filter empty length")
})
run("filter inline - with index", function() {
var result = filter([10, 20, 30], function(e, i) { return i > 0 })
assert_eq(length(result), 2, "filter index length")
assert_eq(result[0], 20, "filter index [0]")
assert_eq(result[1], 30, "filter index [1]")
})
run("filter inline - large callback", function() {
var result = filter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(x) {
var a = x * 2
var b = a + 1
var c = b * 3
var d = c - a
return d > 20
})
if (length(result) < 1) fail("filter large callback should return elements")
})
// --- find inline expansion ---
run("find inline - function forward", function() {
var idx = find([1, 2, 3], function(x) { return x == 2 })
assert_eq(idx, 1, "find fn forward")
})
run("find inline - function not found", function() {
var idx = find([1, 2, 3], function(x) { return x == 99 })
assert_eq(idx, null, "find fn not found")
})
run("find inline - value forward", function() {
var idx = find([10, 20, 30], 20)
assert_eq(idx, 1, "find value forward")
})
run("find inline - value not found", function() {
var idx = find([10, 20, 30], 99)
assert_eq(idx, null, "find value not found")
})
run("find inline - reverse", function() {
var idx = find([1, 2, 1, 2], function(x) { return x == 1 }, true)
assert_eq(idx, 2, "find reverse")
})
run("find inline - from", function() {
var idx = find([1, 2, 3, 2], function(x) { return x == 2 }, false, 2)
assert_eq(idx, 3, "find from")
})
run("find inline - empty", function() {
var idx = find([], 1)
assert_eq(idx, null, "find empty")
})
run("find inline - value reverse", function() {
var idx = find([10, 20, 30, 20], 20, true)
assert_eq(idx, 3, "find value reverse")
})
run("find inline - value with from", function() {
var idx = find([10, 20, 30, 20], 20, false, 2)
assert_eq(idx, 3, "find value with from")
})
run("find inline - with index callback", function() {
var idx = find(["a", "b", "c"], (x, i) => i == 2)
assert_eq(idx, 2, "find index callback")
})
// --- arrfor inline expansion ---
run("arrfor inline - basic sum", function() {
var sum = 0
arrfor([1, 2, 3, 4, 5], function(x) { sum = sum + x })
assert_eq(sum, 15, "arrfor basic sum")
})
run("arrfor inline - reverse", function() {
var order = []
arrfor([1, 2, 3], function(x) { order[] = x }, true)
assert_eq(order[0], 3, "arrfor reverse [0]")
assert_eq(order[1], 2, "arrfor reverse [1]")
assert_eq(order[2], 1, "arrfor reverse [2]")
})
run("arrfor inline - exit", function() {
var result = arrfor([1, 2, 3, 4, 5], function(x) {
if (x > 3) return true
return null
}, false, true)
assert_eq(result, true, "arrfor exit")
})
run("arrfor inline - no exit returns null", function() {
var result = arrfor([1, 2, 3], function(x) { })
assert_eq(result, null, "arrfor no exit null")
})
run("arrfor inline - with index", function() {
var indices = []
arrfor([10, 20, 30], (x, i) => { indices[] = i })
assert_eq(indices[0], 0, "arrfor index [0]")
assert_eq(indices[1], 1, "arrfor index [1]")
assert_eq(indices[2], 2, "arrfor index [2]")
})
run("arrfor inline - reverse with index", function() {
var items = []
arrfor(["a", "b", "c"], function(x, i) { items[] = text(i) + x }, true)
assert_eq(items[0], "2c", "arrfor rev index [0]")
assert_eq(items[1], "1b", "arrfor rev index [1]")
assert_eq(items[2], "0a", "arrfor rev index [2]")
})
// --- reduce inline expansion ---
run("reduce inline - no initial forward", function() {
var result = reduce([1, 2, 3, 4, 5, 6, 7, 8, 9], function(a, b) { return a + b })
assert_eq(result, 45, "reduce sum 1-9")
})
run("reduce inline - single element", function() {
var result = reduce([42], function(a, b) { return a + b })
assert_eq(result, 42, "reduce single")
})
run("reduce inline - empty", function() {
var result = reduce([], function(a, b) { return a + b })
assert_eq(result, null, "reduce empty")
})
run("reduce inline - with initial", function() {
var result = reduce([1, 2, 3], function(a, b) { return a + b }, 10)
assert_eq(result, 16, "reduce with initial")
})
run("reduce inline - with initial empty", function() {
var result = reduce([], function(a, b) { return a + b }, 99)
assert_eq(result, 99, "reduce initial empty")
})
run("reduce inline - reverse", function() {
var result = reduce([1, 2, 3], function(a, b) { return a - b }, 0, true)
assert_eq(result, -6, "reduce reverse")
})
run("reduce inline - intrinsic callback", function() {
var result = reduce([3, 7, 2, 9, 1], max)
assert_eq(result, 9, "reduce max")
})
// ============================================================================
// SUMMARY
// ============================================================================