Merge branch 'optimize_mcode' into fix_aot
This commit is contained in:
@@ -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",
|
||||
|
||||
4786
boot/fold.cm.mcode
4786
boot/fold.cm.mcode
File diff suppressed because one or more lines are too long
25586
boot/mcode.cm.mcode
25586
boot/mcode.cm.mcode
File diff suppressed because one or more lines are too long
9732
boot/parse.cm.mcode
9732
boot/parse.cm.mcode
File diff suppressed because one or more lines are too long
27739
boot/streamline.cm.mcode
27739
boot/streamline.cm.mcode
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -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
399
mcode.cm
@@ -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)) {
|
||||
|
||||
159
source/mach.c
159
source/mach.c
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
582
streamline.cm
582
streamline.cm
@@ -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
|
||||
|
||||
288
vm_suite.ce
288
vm_suite.ce
@@ -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
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user