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