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, "nr_close_slots": 0,
"instructions": [ "instructions": [
["move", 2, 1, 14, 14], ["move", 2, 1, 14, 14],
[ ["is_blob", 3, 1, 15, 16],
"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],
"_nop_bl_1", "_nop_bl_1",
["jump_true", 3, "if_else_6", 15, 8], ["jump_true", 3, "if_else_6", 15, 16],
[ [
"access", "access",
3, 3,
@@ -199,7 +186,7 @@
"_nop_ur_1", "_nop_ur_1",
"_nop_ur_2" "_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", "name": "content_hash",
"filename": ".cell/packages/core/internal/bootstrap.cm", "filename": ".cell/packages/core/internal/bootstrap.cm",
"nr_args": 1 "nr_args": 1
@@ -222,7 +209,7 @@
8 8
], ],
"_nop_bl_1", "_nop_bl_1",
["jump_true", 2, "if_else_10", 20, 8], ["wary_true", 2, "if_else_10", 20, 8],
["null", 2, 20, 26], ["null", 2, 20, 26],
["return", 2, 20, 26], ["return", 2, 20, 26],
"_nop_ur_1", "_nop_ur_1",
@@ -345,7 +332,7 @@
8 8
], ],
"_nop_bl_1", "_nop_bl_1",
["jump_true", 1, "if_else_18", 25, 8], ["wary_true", 1, "if_else_18", 25, 8],
["null", 1, 25, 26], ["null", 1, 25, 26],
["return", 1, 25, 26], ["return", 1, 25, 26],
"_nop_ur_1", "_nop_ur_1",
@@ -427,7 +414,7 @@
["invoke", 5, 3, 27, 8], ["invoke", 5, 3, 27, 8],
"call_done_26", "call_done_26",
"_nop_bl_2", "_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], ["get", 2, 11, 1, 27, 24],
["is_proxy", 3, 2, 27, 24], ["is_proxy", 3, 2, 27, 24],
["jump_false", 3, "record_path_27", 27, 24], ["jump_false", 3, "record_path_27", 27, 24],
@@ -628,7 +615,7 @@
["invoke", 8, 6, 36, 8], ["invoke", 8, 6, 36, 8],
"call_done_41", "call_done_41",
"_nop_bl_1", "_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], ["access", 5, "error: missing seed: ", 37, 14],
"_nop_tc_7", "_nop_tc_7",
"_nop_tc_8", "_nop_tc_8",
@@ -1026,30 +1013,11 @@
"call_done_63", "call_done_63",
"if_end_58", "if_end_58",
["access", 3, 1, 66, 17], ["access", 3, 1, 66, 17],
"_nop_tc_1",
"_nop_tc_2",
"_nop_tc_3",
"_nop_tc_4",
["add", 5, 5, 3, 66, 17], ["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], ["jump", "while_start_55", 66, 17],
"while_end_56", "while_end_56",
["disrupt", 68, 5], ["disrupt", 68, 5],
"_nop_ucfg_13", "_nop_ucfg_1",
"if_else_53", "if_else_53",
"if_end_54", "if_end_54",
["get", 3, 15, 1, 70, 10], ["get", 3, 15, 1, 70, 10],
@@ -1060,7 +1028,7 @@
"_nop_ur_1", "_nop_ur_1",
"_nop_ur_2" "_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", "name": "analyze",
"filename": ".cell/packages/core/internal/bootstrap.cm", "filename": ".cell/packages/core/internal/bootstrap.cm",
"nr_args": 2 "nr_args": 2
@@ -1078,7 +1046,7 @@
"instructions": [ "instructions": [
["get", 3, 11, 1, 74, 21], ["get", 3, 11, 1, 74, 21],
["is_proxy", 4, 3, 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], ["null", 4, 74, 21],
["access", 5, "slurp", 74, 21], ["access", 5, "slurp", 74, 21],
["array", 6, 0, 74, 21], ["array", 6, 0, 74, 21],
@@ -1089,14 +1057,14 @@
["setarg", 7, 1, 5, 74, 21], ["setarg", 7, 1, 5, 74, 21],
["setarg", 7, 2, 6, 74, 21], ["setarg", 7, 2, 6, 74, 21],
["invoke", 7, 4, 74, 21], ["invoke", 7, 4, 74, 21],
["jump", "call_done_67", 74, 21], ["jump", "call_done_65", 74, 21],
"record_path_66", "record_path_64",
["load_field", 5, 3, "slurp", 74, 21], ["load_field", 5, 3, "slurp", 74, 21],
["frame", 6, 5, 1, 74, 21], ["frame", 6, 5, 1, 74, 21],
["setarg", 6, 0, 3, 74, 21], ["setarg", 6, 0, 3, 74, 21],
["setarg", 6, 1, 2, 74, 21], ["setarg", 6, 1, 2, 74, 21],
["invoke", 6, 4, 74, 21], ["invoke", 6, 4, 74, 21],
"call_done_67", "call_done_65",
["move", 3, 4, 74, 21], ["move", 3, 4, 74, 21],
["get", 5, 4, 1, 75, 14], ["get", 5, 4, 1, 75, 14],
["frame", 6, 5, 1, 75, 14], ["frame", 6, 5, 1, 75, 14],
@@ -1113,10 +1081,10 @@
["null", 8, 79, 20], ["null", 8, 79, 20],
["null", 9, 80, 19], ["null", 9, 80, 19],
["move", 10, 4, 81, 7], ["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], ["get", 4, 11, 1, 81, 17],
["is_proxy", 11, 4, 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], ["null", 11, 81, 17],
["access", 12, "is_file", 81, 17], ["access", 12, "is_file", 81, 17],
["array", 13, 0, 81, 17], ["array", 13, 0, 81, 17],
@@ -1127,22 +1095,22 @@
["setarg", 14, 1, 12, 81, 17], ["setarg", 14, 1, 12, 81, 17],
["setarg", 14, 2, 13, 81, 17], ["setarg", 14, 2, 13, 81, 17],
["invoke", 14, 11, 81, 17], ["invoke", 14, 11, 81, 17],
["jump", "call_done_72", 81, 17], ["jump", "call_done_70", 81, 17],
"record_path_71", "record_path_69",
["load_field", 12, 4, "is_file", 81, 17], ["load_field", 12, 4, "is_file", 81, 17],
["frame", 13, 12, 1, 81, 17], ["frame", 13, 12, 1, 81, 17],
["setarg", 13, 0, 4, 81, 17], ["setarg", 13, 0, 4, 81, 17],
["setarg", 13, 1, 5, 81, 17], ["setarg", 13, 1, 5, 81, 17],
["invoke", 13, 11, 81, 17], ["invoke", 13, 11, 81, 17],
"call_done_72", "call_done_70",
["move", 10, 11, 81, 17], ["move", 10, 11, 81, 17],
"and_end_70", "and_end_68",
["jump_false", 10, "if_else_68", 81, 17], ["wary_false", 10, "if_else_66", 81, 17],
["null", 4, 81, 37], ["null", 4, 81, 37],
["return", 4, 81, 37], ["return", 4, 81, 37],
"_nop_ur_1", "_nop_ur_1",
"if_else_68", "if_else_66",
"if_end_69", "if_end_67",
[ [
"access", "access",
4, 4,
@@ -1174,7 +1142,7 @@
["move", 7, 3, 83, 14], ["move", 7, 3, 83, 14],
["get", 3, 12, 1, 84, 16], ["get", 3, 12, 1, 84, 16],
["is_proxy", 4, 3, 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], ["null", 4, 84, 16],
["access", 6, "encode", 84, 16], ["access", 6, "encode", 84, 16],
["array", 10, 0, 84, 16], ["array", 10, 0, 84, 16],
@@ -1185,14 +1153,14 @@
["setarg", 11, 1, 6, 84, 16], ["setarg", 11, 1, 6, 84, 16],
["setarg", 11, 2, 10, 84, 16], ["setarg", 11, 2, 10, 84, 16],
["invoke", 11, 4, 84, 16], ["invoke", 11, 4, 84, 16],
["jump", "call_done_74", 84, 16], ["jump", "call_done_72", 84, 16],
"record_path_73", "record_path_71",
["load_field", 6, 3, "encode", 84, 16], ["load_field", 6, 3, "encode", 84, 16],
["frame", 10, 6, 1, 84, 16], ["frame", 10, 6, 1, 84, 16],
["setarg", 10, 0, 3, 84, 16], ["setarg", 10, 0, 3, 84, 16],
["setarg", 10, 1, 7, 84, 16], ["setarg", 10, 1, 7, 84, 16],
["invoke", 10, 4, 84, 16], ["invoke", 10, 4, 84, 16],
"call_done_74", "call_done_72",
["move", 8, 4, 84, 16], ["move", 8, 4, 84, 16],
[ [
"access", "access",
@@ -1210,13 +1178,13 @@
["setarg", 6, 2, 4, 85, 15], ["setarg", 6, 2, 4, 85, 15],
["invoke", 6, 3, 85, 15], ["invoke", 6, 3, 85, 15],
["move", 9, 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], ["get", 3, 6, 1, 87, 5],
["frame", 4, 3, 0, 87, 5], ["frame", 4, 3, 0, 87, 5],
["invoke", 4, 3, 87, 5], ["invoke", 4, 3, 87, 5],
["get", 3, 11, 1, 88, 5], ["get", 3, 11, 1, 88, 5],
["is_proxy", 4, 3, 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], ["null", 4, 88, 5],
["access", 6, "slurpwrite", 88, 5], ["access", 6, "slurpwrite", 88, 5],
["array", 7, 0, 88, 5], ["array", 7, 0, 88, 5],
@@ -1228,18 +1196,18 @@
["setarg", 8, 1, 6, 88, 5], ["setarg", 8, 1, 6, 88, 5],
["setarg", 8, 2, 7, 88, 5], ["setarg", 8, 2, 7, 88, 5],
["invoke", 8, 4, 88, 5], ["invoke", 8, 4, 88, 5],
["jump", "call_done_78", 88, 5], ["jump", "call_done_76", 88, 5],
"record_path_77", "record_path_75",
["load_field", 6, 3, "slurpwrite", 88, 5], ["load_field", 6, 3, "slurpwrite", 88, 5],
["frame", 7, 6, 2, 88, 5], ["frame", 7, 6, 2, 88, 5],
["setarg", 7, 0, 3, 88, 5], ["setarg", 7, 0, 3, 88, 5],
["setarg", 7, 1, 5, 88, 5], ["setarg", 7, 1, 5, 88, 5],
["setarg", 7, 2, 9, 88, 5], ["setarg", 7, 2, 9, 88, 5],
["invoke", 7, 4, 88, 5], ["invoke", 7, 4, 88, 5],
"call_done_78", "call_done_76",
["jump", "if_end_76", 88, 5], ["jump", "if_end_74", 88, 5],
"if_else_75", "if_else_73",
"if_end_76", "if_end_74",
["null", 3, 88, 5], ["null", 3, 88, 5],
["return", 3, 88, 5] ["return", 3, 88, 5]
], ],
@@ -1369,10 +1337,10 @@
["move", 1, 22, 99, 26], ["move", 1, 22, 99, 26],
["access", 17, 0, 101, 10], ["access", 17, 0, 101, 10],
["null", 18, 102, 13], ["null", 18, 102, 13],
"while_start_79", "while_start_77",
["length", 19, 1, 103, 20], ["length", 19, 1, 103, 20],
["lt", 20, 17, 19, 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], ["load_index", 19, 1, 17, 104, 22],
["move", 18, 19, 104, 22], ["move", 18, 19, 104, 22],
["load_field", 20, 19, "name", 105, 21], ["load_field", 20, 19, "name", 105, 21],
@@ -1389,19 +1357,19 @@
], ],
["access", 21, "/", 105, 45], ["access", 21, "/", 105, 45],
["is_text", 22, 19, 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_1",
"_nop_tc_2", "_nop_tc_2",
["concat", 23, 19, 21, 105, 45], ["concat", 23, 19, 21, 105, 45],
["jump", "add_done_81", 105, 45], ["jump", "add_done_79", 105, 45],
"add_cn_82", "add_cn_80",
["is_num", 22, 19, 105, 45], ["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_tc_3",
"_nop_dj_1", "_nop_dj_1",
"_nop_ucfg_1", "_nop_ucfg_1",
"_nop_ucfg_2", "_nop_ucfg_2",
"add_err_83", "add_err_81",
[ [
"access", "access",
19, 19,
@@ -1426,22 +1394,22 @@
["setarg", 22, 2, 24, 105, 45], ["setarg", 22, 2, 24, 105, 45],
["invoke", 22, 19, 105, 45], ["invoke", 22, 19, 105, 45],
["disrupt", 105, 45], ["disrupt", 105, 45],
"add_done_81", "add_done_79",
["load_field", 19, 18, "path", 105, 51], ["load_field", 19, 18, "path", 105, 51],
"_nop_tc_1", "_nop_tc_1",
"_nop_tc_2", "_nop_tc_2",
["is_text", 21, 19, 105, 51], ["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], ["concat", 21, 23, 19, 105, 51],
["jump", "add_done_84", 105, 51], ["jump", "add_done_82", 105, 51],
"add_cn_85", "add_cn_83",
"_nop_tc_3", "_nop_tc_3",
["jump", "add_err_86", 105, 51], ["jump", "add_err_84", 105, 51],
"_nop_ucfg_1", "_nop_ucfg_1",
"_nop_ucfg_2", "_nop_ucfg_2",
"_nop_ucfg_3", "_nop_ucfg_3",
"_nop_ucfg_4", "_nop_ucfg_4",
"add_err_86", "add_err_84",
[ [
"access", "access",
19, 19,
@@ -1466,35 +1434,16 @@
["setarg", 23, 2, 24, 105, 51], ["setarg", 23, 2, 24, 105, 51],
["invoke", 23, 19, 105, 51], ["invoke", 23, 19, 105, 51],
["disrupt", 105, 51], ["disrupt", 105, 51],
"add_done_84", "add_done_82",
["frame", 19, 9, 2, 105, 3], ["frame", 19, 9, 2, 105, 3],
["setarg", 19, 1, 20, 105, 3], ["setarg", 19, 1, 20, 105, 3],
["stone_text", 21], ["stone_text", 21],
["setarg", 19, 2, 21, 105, 3], ["setarg", 19, 2, 21, 105, 3],
["invoke", 19, 20, 105, 3], ["invoke", 19, 20, 105, 3],
["access", 19, 1, 106, 13], ["access", 19, 1, 106, 13],
"_nop_tc_4",
"_nop_tc_5",
"_nop_tc_6",
"_nop_tc_7",
["add", 17, 17, 19, 106, 13], ["add", 17, 17, 19, 106, 13],
["jump", "num_done_88", 106, 13], ["jump", "while_start_77", 106, 13],
"num_err_87", "while_end_78",
"_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",
["access", 1, "bootstrap: cache seeded\n", 108, 10], ["access", 1, "bootstrap: cache seeded\n", 108, 10],
[ [
"access", "access",
@@ -1508,7 +1457,7 @@
1 1
], ],
["is_proxy", 17, 9, 108, 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], ["null", 17, 108, 1],
["access", 18, "print", 108, 1], ["access", 18, "print", 108, 1],
["array", 19, 0, 108, 1], ["array", 19, 0, 108, 1],
@@ -1520,18 +1469,18 @@
["setarg", 20, 1, 18, 108, 1], ["setarg", 20, 1, 18, 108, 1],
["setarg", 20, 2, 19, 108, 1], ["setarg", 20, 2, 19, 108, 1],
["invoke", 20, 17, 108, 1], ["invoke", 20, 17, 108, 1],
["jump", "call_done_90", 108, 1], ["jump", "call_done_86", 108, 1],
"record_path_89", "record_path_85",
["load_field", 18, 9, "print", 108, 1], ["load_field", 18, 9, "print", 108, 1],
["frame", 19, 18, 1, 108, 1], ["frame", 19, 18, 1, 108, 1],
["setarg", 19, 0, 9, 108, 1], ["setarg", 19, 0, 9, 108, 1],
["stone_text", 1], ["stone_text", 1],
["setarg", 19, 1, 1, 108, 1], ["setarg", 19, 1, 1, 108, 1],
["invoke", 19, 17, 108, 1], ["invoke", 19, 17, 108, 1],
"call_done_90", "call_done_86",
["return", 17, 108, 1] ["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 "nr_args": 0
}, },
"name": ".cell/packages/core/internal/bootstrap.cm", "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') var link = use('link')
// These come from env (via core_extras in engine.cm): // These come from env (via core_extras in engine.cm):
// analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env, // analyze, run_ast_fn, core_json, use_cache, core_path, shop_path, actor_api,
// content_hash, cache_path, ensure_build_dir // runtime_env, content_hash, cache_path, ensure_build_dir
var shop_json = core_json var shop_json = core_json
var global_shop_path = shop_path var global_shop_path = shop_path
var my$_ = actor_api var my$_ = actor_api
var core = "core" 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. // Make a package name safe for use in C identifiers.
// Replaces /, ., -, @ with _ so the result is a valid C identifier fragment. // Replaces /, ., -, @ with _ so the result is a valid C identifier fragment.
function safe_c_name(name) { function safe_c_name(name) {
@@ -43,7 +62,7 @@ function put_into_cache(content, obj)
function hash_path(content, salt) function hash_path(content, salt)
{ {
var s = salt || 'mach' 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 = {} var Shop = {}

399
mcode.cm
View File

@@ -879,6 +879,77 @@ var mcode = function(ast) {
var inline_some = true var inline_some = true
var inline_reduce = true var inline_reduce = true
var inline_map = 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 --- // --- 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}; emits loop updating acc in-place.
@@ -895,13 +966,12 @@ var mcode = function(ast) {
var null_s = alloc_slot() var null_s = alloc_slot()
var one = alloc_slot() var one = alloc_slot()
var zero = alloc_slot() var zero = alloc_slot()
var arity_is_zero = alloc_slot() var az = alloc_slot()
var arity_is_one = alloc_slot() var ao = alloc_slot()
var f = alloc_slot() var f = alloc_slot()
var loop_label = gen_label("reduce_loop") var loop_label = gen_label("reduce_loop")
var call_one_label = gen_label("reduce_call_one") var ctx = {fn: fn_slot, fn_arity: fn_arity, result: acc, null_s: null_s,
var call_two_label = gen_label("reduce_call_two") frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "reduce"}
var call_done_label = gen_label("reduce_call_done")
emit_2("int", one, 1) emit_2("int", one, 1)
emit_2("int", zero, 0) emit_2("int", zero, 0)
emit_1("null", null_s) emit_1("null", null_s)
@@ -913,27 +983,7 @@ var mcode = function(ast) {
} }
emit_jump_cond("jump_false", check, done_label) emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i) emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero) emit_arity_call(ctx, [acc, item], 2)
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)
if (forward) { if (forward) {
emit_3("add", i, i, one) emit_3("add", i, i, one)
} else { } else {
@@ -942,60 +992,63 @@ var mcode = function(ast) {
emit_jump(loop_label) emit_jump(loop_label)
} }
// --- Inline expansion: arrfor(arr, fn) --- // --- Inline expansion: arrfor(arr, fn[, rev[, exit]]) ---
var expand_inline_arrfor = function(dest, arr_slot, fn_slot) { var expand_inline_arrfor = function(dest, args, nargs) {
var arr_slot = args.arr
var fn_slot = args.fn
var len = alloc_slot() var len = alloc_slot()
var i = alloc_slot() var i = alloc_slot()
var check = alloc_slot() var check = alloc_slot()
var item = alloc_slot() var item = alloc_slot()
var fn_arity = alloc_slot() var fn_arity = alloc_slot()
var arity_is_zero = alloc_slot() var az = alloc_slot()
var arity_is_one = alloc_slot() var ao = alloc_slot()
var null_s = alloc_slot() var null_s = alloc_slot()
var zero = alloc_slot() var zero = alloc_slot()
var one = alloc_slot() var one = alloc_slot()
var f = alloc_slot() var f = alloc_slot()
var discard = alloc_slot() var val = alloc_slot()
var loop_label = gen_label("arrfor_loop") var eq_check = alloc_slot()
var done_label = gen_label("arrfor_done") var early_exit = gen_label("arrfor_exit")
var call_one_label = gen_label("arrfor_call_one") var done_final = gen_label("arrfor_final")
var call_two_label = gen_label("arrfor_call_two") var rev_label = gen_label("arrfor_rev")
var call_done_label = gen_label("arrfor_call_done") 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("length", len, arr_slot)
emit_2("int", i, 0)
emit_2("int", zero, 0) emit_2("int", zero, 0)
emit_2("int", one, 1) emit_2("int", one, 1)
emit_1("null", null_s) emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot) emit_2("length", fn_arity, fn_slot)
emit_label(loop_label) if (nargs <= 2) {
emit_3("lt", check, i, len) emit_forward_loop(fwd_L, body_fn)
emit_jump_cond("jump_false", check, done_label) } else {
emit_3("load_index", item, arr_slot, i) emit_jump_cond("wary_true", args.rev, rev_label)
emit_3("eq", arity_is_zero, fn_arity, zero) emit_forward_loop(fwd_L, body_fn)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) emit_jump(final_label)
emit_3("frame", f, fn_slot, 0) emit_label(rev_label)
emit_3("setarg", f, 0, null_s) emit_reverse_loop(rev_L, body_fn)
emit_2("invoke", f, discard) emit_label(final_label)
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)
emit_1("null", dest) 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 return dest
} }
@@ -1040,7 +1093,7 @@ var mcode = function(ast) {
emit_3("setarg", f, 1, item) emit_3("setarg", f, 1, item)
emit_2("invoke", f, val) emit_2("invoke", f, val)
emit_label(call_done_label) 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_3("add", i, i, one)
emit_jump(loop_label) emit_jump(loop_label)
emit_label(ret_true) emit_label(ret_true)
@@ -1093,7 +1146,7 @@ var mcode = function(ast) {
emit_3("setarg", f, 1, item) emit_3("setarg", f, 1, item)
emit_2("invoke", f, val) emit_2("invoke", f, val)
emit_label(call_done_label) 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_3("add", i, i, one)
emit_jump(loop_label) emit_jump(loop_label)
emit_label(ret_true) emit_label(ret_true)
@@ -1113,61 +1166,151 @@ var mcode = function(ast) {
var check = alloc_slot() var check = alloc_slot()
var item = alloc_slot() var item = alloc_slot()
var fn_arity = alloc_slot() var fn_arity = alloc_slot()
var arity_is_zero = alloc_slot() var az = alloc_slot()
var arity_is_one = alloc_slot() var ao = alloc_slot()
var null_s = alloc_slot() var null_s = alloc_slot()
var zero = alloc_slot() var zero = alloc_slot()
var one = alloc_slot() var one = alloc_slot()
var f = alloc_slot() var f = alloc_slot()
var val = alloc_slot() var val = alloc_slot()
var loop_label = gen_label("filter_loop") var skip = gen_label("filter_skip")
var call_one_label = gen_label("filter_call_one") var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
var call_two_label = gen_label("filter_call_two") frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "filter"}
var call_done_label = gen_label("filter_call_done") var L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
var skip_label = gen_label("filter_skip") loop_label: gen_label("filter_loop"), done_label: gen_label("filter_done")}
var done_label = gen_label("filter_done")
add_instr(["array", result, 0]) add_instr(["array", result, 0])
emit_2("length", len, arr_slot) emit_2("length", len, arr_slot)
emit_2("int", i, 0)
emit_2("int", zero, 0) emit_2("int", zero, 0)
emit_2("int", one, 1) emit_2("int", one, 1)
emit_1("null", null_s) emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot) emit_2("length", fn_arity, fn_slot)
emit_label(loop_label) emit_forward_loop(L, function(L) {
emit_3("lt", check, i, len) emit_arity_call(ctx, [L.item, L.i], 2)
emit_jump_cond("jump_false", check, done_label) emit_jump_cond("wary_false", val, skip)
emit_3("load_index", item, arr_slot, i) emit_2("push", result, L.item)
emit_3("eq", arity_is_zero, fn_arity, zero) emit_label(skip)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) return null
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_2("move", dest, result) emit_2("move", dest, result)
return dest 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 --- // --- Inline expansion: array(arr, fn) → map ---
var expand_inline_map = function(dest, arr_slot, fn_slot) { var expand_inline_map = function(dest, arr_slot, fn_slot) {
var result = alloc_slot() var result = alloc_slot()
@@ -1342,7 +1485,7 @@ var mcode = function(ast) {
// No initial // No initial
emit_3("lt", check, zero, len) emit_3("lt", check, zero, len)
emit_jump_cond("jump_false", check, null_label) 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 // No initial, forward
emit_3("load_index", acc, arr_slot, zero) emit_3("load_index", acc, arr_slot, zero)
emit_2("move", i, one) emit_2("move", i, one)
@@ -1364,7 +1507,7 @@ var mcode = function(ast) {
emit_jump(final_label) emit_jump(final_label)
// Has initial // Has initial
emit_label(has_init) 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 // Has initial, forward
emit_2("move", acc, init_slot) emit_2("move", acc, init_slot)
emit_2("int", i, 0) emit_2("int", i, 0)
@@ -1411,7 +1554,7 @@ var mcode = function(ast) {
left_slot = gen_expr(left, -1) left_slot = gen_expr(left, -1)
dest = alloc_slot() dest = alloc_slot()
emit_2("move", dest, left_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) right_slot = gen_expr(right, -1)
emit_2("move", dest, right_slot) emit_2("move", dest, right_slot)
emit_label(end_label) emit_label(end_label)
@@ -1423,7 +1566,7 @@ var mcode = function(ast) {
left_slot = gen_expr(left, -1) left_slot = gen_expr(left, -1)
dest = alloc_slot() dest = alloc_slot()
emit_2("move", dest, left_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) right_slot = gen_expr(right, -1)
emit_2("move", dest, right_slot) emit_2("move", dest, right_slot)
emit_label(end_label) emit_label(end_label)
@@ -1992,12 +2135,22 @@ var mcode = function(ast) {
emit_label(guard_done) emit_label(guard_done)
return a1 return a1
} }
// Callback intrinsics → inline mcode loops // apply(fn, arr) → direct opcode
if (nargs == 2 && fname == "arrfor" && inline_arrfor) { if (nargs == 2 && fname == "apply") {
a0 = gen_expr(args_list[0], -1) a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1) a1 = gen_expr(args_list[1], -1)
d = alloc_slot() 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) { if (nargs == 2 && fname == "every" && inline_every) {
a0 = gen_expr(args_list[0], -1) a0 = gen_expr(args_list[0], -1)
@@ -2017,6 +2170,14 @@ var mcode = function(ast) {
d = alloc_slot() d = alloc_slot()
return expand_inline_filter(d, a0, a1) 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) { if (fname == "reduce" && nargs >= 2 && nargs <= 4 && inline_reduce) {
a0 = gen_expr(args_list[0], -1) a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1) a1 = gen_expr(args_list[1], -1)
@@ -2200,7 +2361,7 @@ var mcode = function(ast) {
else_label = gen_label("tern_else") else_label = gen_label("tern_else")
end_label = gen_label("tern_end") end_label = gen_label("tern_end")
cond_slot = gen_expr(cond, -1) 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() dest = alloc_slot()
then_slot = gen_expr(then_expr, -1) then_slot = gen_expr(then_expr, -1)
emit_2("move", dest, then_slot) emit_2("move", dest, then_slot)
@@ -2425,7 +2586,7 @@ var mcode = function(ast) {
else_label = gen_label("if_else") else_label = gen_label("if_else")
end_label = gen_label("if_end") end_label = gen_label("if_end")
cond_slot = gen_expr(cond, -1) 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 _i = 0
while (_i < length(then_stmts)) { while (_i < length(then_stmts)) {
gen_statement(then_stmts[_i]) gen_statement(then_stmts[_i])
@@ -2466,7 +2627,7 @@ var mcode = function(ast) {
} }
emit_label(start_label) emit_label(start_label)
cond_slot = gen_expr(cond, -1) 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 _i = 0
while (_i < length(stmts)) { while (_i < length(stmts)) {
gen_statement(stmts[_i]) gen_statement(stmts[_i])
@@ -2501,7 +2662,7 @@ var mcode = function(ast) {
} }
emit_label(cond_label) emit_label(cond_label)
cond_slot = gen_expr(cond, -1) 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) emit_label(end_label)
s_loop_break = old_break s_loop_break = old_break
s_loop_continue = old_continue s_loop_continue = old_continue
@@ -2535,7 +2696,7 @@ var mcode = function(ast) {
emit_label(start_label) emit_label(start_label)
if (test != null) { if (test != null) {
test_slot = gen_expr(test, -1) 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 _i = 0
while (_i < length(stmts)) { 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_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY [P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR [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] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G]) [P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation) [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_UPPER, /* R(A) = is_upper(R(B)) */
MACH_IS_WS, /* R(A) = is_whitespace(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_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 MACH_OP_COUNT
} MachOpcode; } MachOpcode;
@@ -406,6 +410,10 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_IS_UPPER] = "is_upper", [MACH_IS_UPPER] = "is_upper",
[MACH_IS_WS] = "is_ws", [MACH_IS_WS] = "is_ws",
[MACH_IS_ACTOR] = "is_actor", [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 ---- */ /* ---- Compile-time constant pool entry ---- */
@@ -1427,6 +1435,8 @@ vm_dispatch:
DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER), DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER),
DT(MACH_IS_LOWER), DT(MACH_IS_UPPER), DT(MACH_IS_LOWER), DT(MACH_IS_UPPER),
DT(MACH_IS_WS), DT(MACH_IS_ACTOR), DT(MACH_IS_WS), DT(MACH_IS_ACTOR),
DT(MACH_APPLY),
DT(MACH_WARYTRUE), DT(MACH_WARYFALSE), DT(MACH_JMPEMPTY),
}; };
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#undef DT #undef DT
@@ -2069,12 +2079,7 @@ vm_dispatch:
} }
VM_CASE(MACH_JMPTRUE): { VM_CASE(MACH_JMPTRUE): {
JSValue v = frame->slots[a]; if (frame->slots[a] == JS_TRUE) {
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); int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset); pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) { if (offset < 0) {
@@ -2095,12 +2100,7 @@ vm_dispatch:
} }
VM_CASE(MACH_JMPFALSE): { VM_CASE(MACH_JMPFALSE): {
JSValue v = frame->slots[a]; if (frame->slots[a] == JS_FALSE) {
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); int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset); pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) { if (offset < 0) {
@@ -2517,6 +2517,49 @@ vm_dispatch:
frame->slots[a] = JS_NewBool(ctx, result); frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK(); 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 */ /* Logical */
VM_CASE(MACH_NOT): { VM_CASE(MACH_NOT): {
int bval = JS_ToBool(ctx, frame->slots[b]); int bval = JS_ToBool(ctx, frame->slots[b]);
@@ -2826,6 +2869,67 @@ vm_dispatch:
VM_BREAK(); 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) */ /* Disrupt (mcode alias) */
VM_CASE(MACH_DISRUPT): VM_CASE(MACH_DISRUPT):
goto 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_upper") == 0) { AB2(MACH_IS_UPPER); }
else if (strcmp(op, "is_ws") == 0) { AB2(MACH_IS_WS); } 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, "is_actor") == 0) { AB2(MACH_IS_ACTOR); }
else if (strcmp(op, "apply") == 0) { ABC3(MACH_APPLY); }
/* Logical */ /* Logical */
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); } else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); } 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)); EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg); 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 */ /* Return / error */
else if (strcmp(op, "return") == 0) { else if (strcmp(op, "return") == 0) {
EM(MACH_ABC(MACH_RETURN, A1, 0, 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 (argc < 1) return JS_NULL;
if (!JS_IsFunction (argv[0])) return argv[0]; if (!JS_IsFunction (argv[0])) return argv[0];
JSFunction *fn = JS_VALUE_GET_FUNCTION (argv[0]);
if (argc < 2) if (argc < 2)
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0); 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); return JS_CallInternal (ctx, argv[0], JS_NULL, 1, &argv[1], 0);
}
JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]); JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]);
int len = arr->len; 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) if (len == 0)
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 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 = { var no_clear_ops = {
int: true, access: true, true: true, false: true, move: true, null: true, int: true, access: true, true: true, false: true, move: true, null: true,
jump: true, jump_true: true, jump_false: true, jump_not_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, return: true, disrupt: true,
store_field: true, store_index: true, store_dynamic: true, store_field: true, store_index: true, store_dynamic: true,
push: true, setarg: true, invoke: true, tail_invoke: true, push: true, setarg: true, invoke: true, tail_invoke: true,
stone_text: 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 --- // --- Logging support ---
var ir_stats = null var ir_stats = null
@@ -391,7 +398,12 @@ var streamline = function(ir, log) {
abs: T_NUM, floor: T_NUM, ceiling: T_NUM, abs: T_NUM, floor: T_NUM, ceiling: T_NUM,
round: T_NUM, trunc: T_NUM, fraction: T_NUM, round: T_NUM, trunc: T_NUM, fraction: T_NUM,
integer: T_NUM, whole: T_NUM, sign: 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) { var narrow_arith_type = function(write_types, param_types, instr, typ) {
@@ -691,7 +703,49 @@ var streamline = function(ir, log) {
if (is_array(next)) { if (is_array(next)) {
next_op = next[0] 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] target_label = next[2]
if (slot_is(slot_types, src, checked_type)) { if (slot_is(slot_types, src, checked_type)) {
nc = nc + 1 nc = nc + 1
@@ -767,7 +821,7 @@ var streamline = function(ir, log) {
continue 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] target_label = next[2]
if (slot_is(slot_types, src, checked_type)) { if (slot_is(slot_types, src, checked_type)) {
nc = nc + 1 nc = nc + 1
@@ -918,6 +972,32 @@ var streamline = function(ir, log) {
continue 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) track_types(slot_types, instr)
i = i + 1 i = i + 1
} }
@@ -1075,11 +1155,12 @@ var streamline = function(ir, log) {
next_op = next[0] next_op = next[0]
nlen = length(next) 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]) { if (next_op == "jump_false" && next[1] == instr[1]) {
nc = nc + 1 nc = nc + 1
instructions[i] = "_nop_bl_" + text(nc) 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) { if (events != null) {
events[] = { events[] = {
event: "rewrite", pass: "simplify_booleans", event: "rewrite", pass: "simplify_booleans",
@@ -1092,11 +1173,11 @@ var streamline = function(ir, log) {
continue 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]) { if (next_op == "jump_true" && next[1] == instr[1]) {
nc = nc + 1 nc = nc + 1
instructions[i] = "_nop_bl_" + text(nc) 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) { if (events != null) {
events[] = { events[] = {
event: "rewrite", pass: "simplify_booleans", event: "rewrite", pass: "simplify_booleans",
@@ -1109,6 +1190,40 @@ var streamline = function(ir, log) {
continue 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 // not d1, x; not d2, d1 → move d2, x
if (next_op == "not" && next[2] == instr[1]) { if (next_op == "not" && next[2] == instr[1]) {
nc = nc + 1 nc = nc + 1
@@ -1196,7 +1311,7 @@ var streamline = function(ir, log) {
} }
// Control flow with a read at position 1: substitute then clear // 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])] actual = copies[text(instr[1])]
if (actual != null) { if (actual != null) {
instr[1] = actual instr[1] = actual
@@ -1378,7 +1493,7 @@ var streamline = function(ir, log) {
op = instr[0] op = instr[0]
if (op == "jump") { if (op == "jump") {
target = instr[1] target = instr[1]
} else if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") { } else if (is_cond_jump(op)) {
target = instr[2] target = instr[2]
} }
if (target != null && is_text(target)) { if (target != null && is_text(target)) {
@@ -1585,7 +1700,7 @@ var streamline = function(ir, log) {
if (is_number(tgt)) stack[] = tgt if (is_number(tgt)) stack[] = tgt
continue continue
} }
if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") { if (is_cond_jump(op)) {
tgt = label_map[instr[2]] tgt = label_map[instr[2]]
if (is_number(tgt)) stack[] = tgt if (is_number(tgt)) stack[] = tgt
stack[] = idx + 1 stack[] = idx + 1
@@ -1690,6 +1805,7 @@ var streamline = function(ir, log) {
frame: [1, 2], goframe: [1, 2], frame: [1, 2], goframe: [1, 2],
jump: [], disrupt: [], jump: [], disrupt: [],
jump_true: [1], jump_false: [1], jump_not_null: [1], jump_true: [1], jump_false: [1], jump_not_null: [1],
wary_true: [1], wary_false: [1], jump_null: [1], jump_empty: [1],
return: [1], return: [1],
stone_text: [1] stone_text: [1]
} }
@@ -1720,6 +1836,7 @@ var streamline = function(ir, log) {
setarg: [], store_field: [], store_index: [], store_dynamic: [], setarg: [], store_field: [], store_index: [], store_dynamic: [],
push: [], set_var: [], stone_text: [], push: [], set_var: [], stone_text: [],
jump: [], jump_true: [], jump_false: [], jump_not_null: [], jump: [], jump_true: [], jump_false: [], jump_not_null: [],
wary_true: [], wary_false: [], jump_null: [], jump_empty: [],
return: [], disrupt: [] return: [], disrupt: []
} }
@@ -1733,6 +1850,7 @@ var streamline = function(ir, log) {
store_dynamic: [1, 2, 3], store_dynamic: [1, 2, 3],
push: [1, 2], set_var: [1], stone_text: [1], push: [1, 2], set_var: [1], stone_text: [1],
jump: [], jump_true: [1], jump_false: [1], jump_not_null: [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: [] return: [1], disrupt: []
} }
@@ -1870,7 +1988,7 @@ var streamline = function(ir, log) {
target = null target = null
if (op == "jump") { if (op == "jump") {
target = instr[1] target = instr[1]
} else if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") { } else if (is_cond_jump(op)) {
target = instr[2] target = instr[2]
} }
if (target == null || !is_text(target)) { if (target == null || !is_text(target)) {
@@ -2591,6 +2709,390 @@ var streamline = function(ir, log) {
return null 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 // Compose all passes
// ========================================================= // =========================================================
@@ -2693,13 +3195,12 @@ var streamline = function(ir, log) {
ir._diagnostics = [] ir._diagnostics = []
} }
// Process main function // Phase 1: Optimize all functions (bottom-up, existing behavior)
if (ir.main != null) { if (ir.main != null) {
optimize_function(ir.main, log) optimize_function(ir.main, log)
insert_stone_text(ir.main, log) insert_stone_text(ir.main, log)
} }
// Process all sub-functions (resolve closure types from parent first)
var fi = 0 var fi = 0
if (ir.functions != null) { if (ir.functions != null) {
fi = 0 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) compress_slots(ir)
// Expose DEF/USE functions via log if requested // 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") 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 // 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") 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 // SUMMARY
// ============================================================================ // ============================================================================