Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-02-22 09:04:03 -06:00
26 changed files with 38246 additions and 37689 deletions

86
bench_arith.ce Normal file
View File

@@ -0,0 +1,86 @@
// bench_arith.ce — arithmetic and number crunching benchmark
// Tests: integer add/mul, float ops, loop counter overhead, conditionals
var time = use('time')
def iterations = 2000000
// 1. Integer sum in tight loop
function bench_int_sum() {
var i = 0
var s = 0
for (i = 0; i < iterations; i++) {
s = s + i
}
return s
}
// 2. Integer multiply + mod (sieve-like)
function bench_int_mul_mod() {
var i = 0
var s = 0
for (i = 1; i < iterations; i++) {
s = s + (i * 7 % 1000)
}
return s
}
// 3. Float math — accumulate with division
function bench_float_arith() {
var i = 0
var s = 0.5
for (i = 1; i < iterations; i++) {
s = s + 1.0 / i
}
return s
}
// 4. Nested loop with branch (fizzbuzz-like counter)
function bench_branch() {
var i = 0
var fizz = 0
var buzz = 0
var fizzbuzz = 0
for (i = 1; i <= iterations; i++) {
if (i % 15 == 0) {
fizzbuzz = fizzbuzz + 1
} else if (i % 3 == 0) {
fizz = fizz + 1
} else if (i % 5 == 0) {
buzz = buzz + 1
}
}
return fizz + buzz + fizzbuzz
}
// 5. Nested loop (small inner)
function bench_nested() {
var i = 0
var j = 0
var s = 0
def outer = 5000
def inner = 5000
for (i = 0; i < outer; i++) {
for (j = 0; j < inner; j++) {
s = s + 1
}
}
return s
}
// Run each and print timing
function run(name, fn) {
var start = time.number()
var result = fn()
var elapsed = time.number() - start
var ms = whole(elapsed * 100000) / 100
log.console(` ${name}: ${ms} ms (result: ${result})`)
}
log.console("=== Arithmetic Benchmark ===")
log.console(` iterations: ${iterations}`)
run("int_sum ", bench_int_sum)
run("int_mul_mod ", bench_int_mul_mod)
run("float_arith ", bench_float_arith)
run("branch ", bench_branch)
run("nested_loop ", bench_nested)

67
bench_arith.js Normal file
View File

@@ -0,0 +1,67 @@
// bench_arith.js — arithmetic and number crunching benchmark (QuickJS)
const iterations = 2000000;
function bench_int_sum() {
let s = 0;
for (let i = 0; i < iterations; i++) {
s = s + i;
}
return s;
}
function bench_int_mul_mod() {
let s = 0;
for (let i = 1; i < iterations; i++) {
s = s + (i * 7 % 1000);
}
return s;
}
function bench_float_arith() {
let s = 0.5;
for (let i = 1; i < iterations; i++) {
s = s + 1.0 / i;
}
return s;
}
function bench_branch() {
let fizz = 0, buzz = 0, fizzbuzz = 0;
for (let i = 1; i <= iterations; i++) {
if (i % 15 === 0) {
fizzbuzz = fizzbuzz + 1;
} else if (i % 3 === 0) {
fizz = fizz + 1;
} else if (i % 5 === 0) {
buzz = buzz + 1;
}
}
return fizz + buzz + fizzbuzz;
}
function bench_nested() {
let s = 0;
const outer = 5000, inner = 5000;
for (let i = 0; i < outer; i++) {
for (let j = 0; j < inner; j++) {
s = s + 1;
}
}
return s;
}
function run(name, fn) {
const start = performance.now();
const result = fn();
const elapsed = performance.now() - start;
console.log(` ${name}: ${elapsed.toFixed(2)} ms (result: ${result})`);
}
console.log("=== Arithmetic Benchmark ===");
console.log(` iterations: ${iterations}`);
run("int_sum ", bench_int_sum);
run("int_mul_mod ", bench_int_mul_mod);
run("float_arith ", bench_float_arith);
run("branch ", bench_branch);
run("nested_loop ", bench_nested);

68
bench_arith.lua Normal file
View File

@@ -0,0 +1,68 @@
-- bench_arith.lua — arithmetic and number crunching benchmark (Lua)
local iterations = 2000000
local clock = os.clock
local function bench_int_sum()
local s = 0
for i = 0, iterations - 1 do
s = s + i
end
return s
end
local function bench_int_mul_mod()
local s = 0
for i = 1, iterations - 1 do
s = s + (i * 7 % 1000)
end
return s
end
local function bench_float_arith()
local s = 0.5
for i = 1, iterations - 1 do
s = s + 1.0 / i
end
return s
end
local function bench_branch()
local fizz, buzz, fizzbuzz = 0, 0, 0
for i = 1, iterations do
if i % 15 == 0 then
fizzbuzz = fizzbuzz + 1
elseif i % 3 == 0 then
fizz = fizz + 1
elseif i % 5 == 0 then
buzz = buzz + 1
end
end
return fizz + buzz + fizzbuzz
end
local function bench_nested()
local s = 0
local outer, inner = 5000, 5000
for i = 0, outer - 1 do
for j = 0, inner - 1 do
s = s + 1
end
end
return s
end
local function run(name, fn)
local start = clock()
local result = fn()
local elapsed = (clock() - start) * 1000
print(string.format(" %s: %.2f ms (result: %s)", name, elapsed, tostring(result)))
end
print("=== Arithmetic Benchmark ===")
print(string.format(" iterations: %d", iterations))
run("int_sum ", bench_int_sum)
run("int_mul_mod ", bench_int_mul_mod)
run("float_arith ", bench_float_arith)
run("branch ", bench_branch)
run("nested_loop ", bench_nested)

113
bench_array.ce Normal file
View File

@@ -0,0 +1,113 @@
// bench_array.ce — array operation benchmark
// Tests: sequential access, push/build, index write, sum reduction, sort
var time = use('time')
def size = 100000
// 1. Build array with push
function bench_push() {
var a = []
var i = 0
for (i = 0; i < size; i++) {
a[] = i
}
return length(a)
}
// 2. Index write into preallocated array
function bench_index_write() {
var a = array(size, 0)
var i = 0
for (i = 0; i < size; i++) {
a[i] = i
}
return a[size - 1]
}
// 3. Sequential read and sum
function bench_seq_read() {
var a = array(size, 0)
var i = 0
for (i = 0; i < size; i++) {
a[i] = i
}
var s = 0
for (i = 0; i < size; i++) {
s = s + a[i]
}
return s
}
// 4. Reverse array in-place
function bench_reverse() {
var a = array(size, 0)
var i = 0
for (i = 0; i < size; i++) {
a[i] = i
}
var lo = 0
var hi = size - 1
var tmp = 0
while (lo < hi) {
tmp = a[lo]
a[lo] = a[hi]
a[hi] = tmp
lo = lo + 1
hi = hi - 1
}
return a[0]
}
// 5. Nested array access (matrix-like, 300x300)
function bench_matrix() {
def n = 300
var mat = array(n, null)
var i = 0
var j = 0
for (i = 0; i < n; i++) {
mat[i] = array(n, 0)
for (j = 0; j < n; j++) {
mat[i][j] = i * n + j
}
}
// sum diagonal
var s = 0
for (i = 0; i < n; i++) {
s = s + mat[i][i]
}
return s
}
// 6. filter-like: count evens
function bench_filter_count() {
var a = array(size, 0)
var i = 0
for (i = 0; i < size; i++) {
a[i] = i
}
var count = 0
for (i = 0; i < size; i++) {
if (a[i] % 2 == 0) {
count = count + 1
}
}
return count
}
function run(name, fn) {
var start = time.number()
var result = fn()
var elapsed = time.number() - start
var ms = whole(elapsed * 100000) / 100
log.console(` ${name}: ${ms} ms (result: ${result})`)
}
log.console("=== Array Benchmark ===")
log.console(` size: ${size}`)
run("push ", bench_push)
run("index_write ", bench_index_write)
run("seq_read_sum ", bench_seq_read)
run("reverse ", bench_reverse)
run("matrix_300 ", bench_matrix)
run("filter_count ", bench_filter_count)

93
bench_array.js Normal file
View File

@@ -0,0 +1,93 @@
// bench_array.js — array operation benchmark (QuickJS)
const size = 100000;
function bench_push() {
let a = [];
for (let i = 0; i < size; i++) {
a.push(i);
}
return a.length;
}
function bench_index_write() {
let a = new Array(size).fill(0);
for (let i = 0; i < size; i++) {
a[i] = i;
}
return a[size - 1];
}
function bench_seq_read() {
let a = new Array(size).fill(0);
for (let i = 0; i < size; i++) {
a[i] = i;
}
let s = 0;
for (let i = 0; i < size; i++) {
s = s + a[i];
}
return s;
}
function bench_reverse() {
let a = new Array(size).fill(0);
for (let i = 0; i < size; i++) {
a[i] = i;
}
let lo = 0, hi = size - 1, tmp;
while (lo < hi) {
tmp = a[lo];
a[lo] = a[hi];
a[hi] = tmp;
lo = lo + 1;
hi = hi - 1;
}
return a[0];
}
function bench_matrix() {
const n = 300;
let mat = new Array(n);
for (let i = 0; i < n; i++) {
mat[i] = new Array(n).fill(0);
for (let j = 0; j < n; j++) {
mat[i][j] = i * n + j;
}
}
let s = 0;
for (let i = 0; i < n; i++) {
s = s + mat[i][i];
}
return s;
}
function bench_filter_count() {
let a = new Array(size).fill(0);
for (let i = 0; i < size; i++) {
a[i] = i;
}
let count = 0;
for (let i = 0; i < size; i++) {
if (a[i] % 2 === 0) {
count = count + 1;
}
}
return count;
}
function run(name, fn) {
const start = performance.now();
const result = fn();
const elapsed = performance.now() - start;
console.log(` ${name}: ${elapsed.toFixed(2)} ms (result: ${result})`);
}
console.log("=== Array Benchmark ===");
console.log(` size: ${size}`);
run("push ", bench_push);
run("index_write ", bench_index_write);
run("seq_read_sum ", bench_seq_read);
run("reverse ", bench_reverse);
run("matrix_300 ", bench_matrix);
run("filter_count ", bench_filter_count);

93
bench_array.lua Normal file
View File

@@ -0,0 +1,93 @@
-- bench_array.lua — array operation benchmark (Lua)
local size = 100000
local clock = os.clock
local function bench_push()
local a = {}
for i = 0, size - 1 do
a[#a + 1] = i
end
return #a
end
local function bench_index_write()
local a = {}
for i = 1, size do a[i] = 0 end
for i = 1, size do
a[i] = i - 1
end
return a[size]
end
local function bench_seq_read()
local a = {}
for i = 1, size do
a[i] = i - 1
end
local s = 0
for i = 1, size do
s = s + a[i]
end
return s
end
local function bench_reverse()
local a = {}
for i = 1, size do
a[i] = i - 1
end
local lo, hi = 1, size
while lo < hi do
a[lo], a[hi] = a[hi], a[lo]
lo = lo + 1
hi = hi - 1
end
return a[1]
end
local function bench_matrix()
local n = 300
local mat = {}
for i = 1, n do
mat[i] = {}
for j = 1, n do
mat[i][j] = (i - 1) * n + (j - 1)
end
end
local s = 0
for i = 1, n do
s = s + mat[i][i]
end
return s
end
local function bench_filter_count()
local a = {}
for i = 1, size do
a[i] = i - 1
end
local count = 0
for i = 1, size do
if a[i] % 2 == 0 then
count = count + 1
end
end
return count
end
local function run(name, fn)
local start = clock()
local result = fn()
local elapsed = (clock() - start) * 1000
print(string.format(" %s: %.2f ms (result: %s)", name, elapsed, tostring(result)))
end
print("=== Array Benchmark ===")
print(string.format(" size: %d", size))
run("push ", bench_push)
run("index_write ", bench_index_write)
run("seq_read_sum ", bench_seq_read)
run("reverse ", bench_reverse)
run("matrix_300 ", bench_matrix)
run("filter_count ", bench_filter_count)

21
bench_fib.ce Normal file
View File

@@ -0,0 +1,21 @@
var time = use('time')
function fib(n) {
if (n < 2) {
return n
}
return fib(n - 1) + fib(n - 2)
}
function run(name, fn) {
var start = time.number()
var result = fn()
var elapsed = time.number() - start
var ms = whole(elapsed * 100000) / 100
log.console(` ${name}: ${ms} ms (result: ${result})`)
}
log.console("=== Cell fib ===")
run("fib(25)", function() { return fib(25) })
run("fib(30)", function() { return fib(30) })
run("fib(35)", function() { return fib(35) })

118
bench_object.ce Normal file
View File

@@ -0,0 +1,118 @@
// bench_object.ce — object/record and string benchmark
// Tests: property read/write, string concat, string interpolation, method-like dispatch
var time = use('time')
def iterations = 200000
// 1. Record create + property write
function bench_record_create() {
var i = 0
var r = null
for (i = 0; i < iterations; i++) {
r = {x: i, y: i + 1, z: i + 2}
}
return r.z
}
// 2. Property read in loop
function bench_prop_read() {
var obj = {x: 10, y: 20, z: 30, w: 40}
var i = 0
var s = 0
for (i = 0; i < iterations; i++) {
s = s + obj.x + obj.y + obj.z + obj.w
}
return s
}
// 3. Dynamic property access (computed keys)
function bench_dynamic_prop() {
var obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
var keys = ["a", "b", "c", "d", "e"]
var i = 0
var j = 0
var s = 0
for (i = 0; i < iterations; i++) {
for (j = 0; j < 5; j++) {
s = s + obj[keys[j]]
}
}
return s
}
// 4. String concatenation
function bench_string_concat() {
var i = 0
var s = ""
def n = 10000
for (i = 0; i < n; i++) {
s = s + "x"
}
return length(s)
}
// 5. String interpolation
function bench_interpolation() {
var i = 0
var s = ""
def n = 50000
for (i = 0; i < n; i++) {
s = `item_${i}`
}
return s
}
// 6. Prototype chain / method-like call
function make_point(x, y) {
return {
x: x,
y: y,
sum: function(self) {
return self.x + self.y
}
}
}
function bench_method_call() {
var p = make_point(3, 4)
var i = 0
var s = 0
for (i = 0; i < iterations; i++) {
s = s + p.sum(p)
}
return s
}
// 7. Function call overhead (simple recursion depth)
function fib(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
function bench_fncall() {
var i = 0
var s = 0
for (i = 0; i < 20; i++) {
s = s + fib(25)
}
return s
}
function run(name, fn) {
var start = time.number()
var result = fn()
var elapsed = time.number() - start
var ms = whole(elapsed * 100000) / 100
log.console(` ${name}: ${ms} ms (result: ${result})`)
}
log.console("=== Object / String / Call Benchmark ===")
log.console(` iterations: ${iterations}`)
run("record_create ", bench_record_create)
run("prop_read ", bench_prop_read)
run("dynamic_prop ", bench_dynamic_prop)
run("string_concat ", bench_string_concat)
run("interpolation ", bench_interpolation)
run("method_call ", bench_method_call)
run("fncall_fib25 ", bench_fncall)

99
bench_object.js Normal file
View File

@@ -0,0 +1,99 @@
// bench_object.js — object/string/call benchmark (QuickJS)
const iterations = 200000;
function bench_record_create() {
let r;
for (let i = 0; i < iterations; i++) {
r = {x: i, y: i + 1, z: i + 2};
}
return r.z;
}
function bench_prop_read() {
const obj = {x: 10, y: 20, z: 30, w: 40};
let s = 0;
for (let i = 0; i < iterations; i++) {
s = s + obj.x + obj.y + obj.z + obj.w;
}
return s;
}
function bench_dynamic_prop() {
const obj = {a: 1, b: 2, c: 3, d: 4, e: 5};
const keys = ["a", "b", "c", "d", "e"];
let s = 0;
for (let i = 0; i < iterations; i++) {
for (let j = 0; j < 5; j++) {
s = s + obj[keys[j]];
}
}
return s;
}
function bench_string_concat() {
let s = "";
const n = 10000;
for (let i = 0; i < n; i++) {
s = s + "x";
}
return s.length;
}
function bench_interpolation() {
let s = "";
const n = 50000;
for (let i = 0; i < n; i++) {
s = `item_${i}`;
}
return s;
}
function make_point(x, y) {
return {
x: x,
y: y,
sum: function(self) {
return self.x + self.y;
}
};
}
function bench_method_call() {
const p = make_point(3, 4);
let s = 0;
for (let i = 0; i < iterations; i++) {
s = s + p.sum(p);
}
return s;
}
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
function bench_fncall() {
let s = 0;
for (let i = 0; i < 20; i++) {
s = s + fib(25);
}
return s;
}
function run(name, fn) {
const start = performance.now();
const result = fn();
const elapsed = performance.now() - start;
console.log(` ${name}: ${elapsed.toFixed(2)} ms (result: ${result})`);
}
console.log("=== Object / String / Call Benchmark ===");
console.log(` iterations: ${iterations}`);
run("record_create ", bench_record_create);
run("prop_read ", bench_prop_read);
run("dynamic_prop ", bench_dynamic_prop);
run("string_concat ", bench_string_concat);
run("interpolation ", bench_interpolation);
run("method_call ", bench_method_call);
run("fncall_fib25 ", bench_fncall);

101
bench_object.lua Normal file
View File

@@ -0,0 +1,101 @@
-- bench_object.lua — object/string/call benchmark (Lua)
local iterations = 200000
local clock = os.clock
local function bench_record_create()
local r
for i = 0, iterations - 1 do
r = {x = i, y = i + 1, z = i + 2}
end
return r.z
end
local function bench_prop_read()
local obj = {x = 10, y = 20, z = 30, w = 40}
local s = 0
for i = 0, iterations - 1 do
s = s + obj.x + obj.y + obj.z + obj.w
end
return s
end
local function bench_dynamic_prop()
local obj = {a = 1, b = 2, c = 3, d = 4, e = 5}
local keys = {"a", "b", "c", "d", "e"}
local s = 0
for i = 0, iterations - 1 do
for j = 1, 5 do
s = s + obj[keys[j]]
end
end
return s
end
local function bench_string_concat()
local parts = {}
local n = 10000
for i = 1, n do
parts[i] = "x"
end
local s = table.concat(parts)
return #s
end
local function bench_interpolation()
local s = ""
local n = 50000
for i = 0, n - 1 do
s = string.format("item_%d", i)
end
return s
end
local function make_point(x, y)
return {
x = x,
y = y,
sum = function(self)
return self.x + self.y
end
}
end
local function bench_method_call()
local p = make_point(3, 4)
local s = 0
for i = 0, iterations - 1 do
s = s + p.sum(p)
end
return s
end
local function fib(n)
if n <= 1 then return n end
return fib(n - 1) + fib(n - 2)
end
local function bench_fncall()
local s = 0
for i = 0, 19 do
s = s + fib(25)
end
return s
end
local function run(name, fn)
local start = clock()
local result = fn()
local elapsed = (clock() - start) * 1000
print(string.format(" %s: %.2f ms (result: %s)", name, elapsed, tostring(result)))
end
print("=== Object / String / Call Benchmark ===")
print(string.format(" iterations: %d", iterations))
run("record_create ", bench_record_create)
run("prop_read ", bench_prop_read)
run("dynamic_prop ", bench_dynamic_prop)
run("string_concat ", bench_string_concat)
run("interpolation ", bench_interpolation)
run("method_call ", bench_method_call)
run("fncall_fib25 ", bench_fncall)

302
benches/micro_core.cm Normal file
View File

@@ -0,0 +1,302 @@
// micro_core.cm — direct microbenchmarks for core ops
function blackhole(sink, x) {
return (sink + (x | 0)) | 0
}
function make_obj_xy(x, y) {
return {x: x, y: y}
}
function make_obj_yx(x, y) {
// Different insertion order to force a different shape
return {y: y, x: x}
}
function make_packed_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i++) push(a, i)
return a
}
function make_holey_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i += 2) a[i] = i
return a
}
return {
loop_empty: function(n) {
var sink = 0
var i = 0
for (i = 0; i < n; i++) {}
return blackhole(sink, n)
},
i32_add: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = (x + 3) | 0
return blackhole(sink, x)
},
f64_add: function(n) {
var sink = 0
var x = 1.0
var i = 0
for (i = 0; i < n; i++) x = x + 3.14159
return blackhole(sink, x | 0)
},
mixed_add: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = x + 0.25
return blackhole(sink, x | 0)
},
bit_ops: function(n) {
var sink = 0
var x = 0x12345678
var i = 0
for (i = 0; i < n; i++) x = ((x << 5) ^ (x >>> 3)) | 0
return blackhole(sink, x)
},
overflow_path: function(n) {
var sink = 0
var x = 0x70000000
var i = 0
for (i = 0; i < n; i++) x = (x + 0x10000000) | 0
return blackhole(sink, x)
},
call_direct: function(n) {
var sink = 0
var f = function(a) { return (a + 1) | 0 }
var x = 0
var i = 0
for (i = 0; i < n; i++) x = f(x)
return blackhole(sink, x)
},
call_indirect: function(n) {
var sink = 0
var f = function(a) { return (a + 1) | 0 }
var g = f
var x = 0
var i = 0
for (i = 0; i < n; i++) x = g(x)
return blackhole(sink, x)
},
call_closure: function(n) {
var sink = 0
var make_adder = function(k) {
return function(a) { return (a + k) | 0 }
}
var add3 = make_adder(3)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = add3(x)
return blackhole(sink, x)
},
array_read_packed: function(n) {
var sink = 0
var a = make_packed_array(1024)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = (x + a[i & 1023]) | 0
return blackhole(sink, x)
},
array_write_packed: function(n) {
var sink = 0
var a = make_packed_array(1024)
var i = 0
for (i = 0; i < n; i++) a[i & 1023] = i
return blackhole(sink, a[17] | 0)
},
array_read_holey: function(n) {
var sink = 0
var a = make_holey_array(2048)
var x = 0
var i = 0
var v = null
for (i = 0; i < n; i++) {
v = a[(i & 2047)]
if (v) x = (x + v) | 0
}
return blackhole(sink, x)
},
array_push_steady: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var a = null
for (j = 0; j < n; j++) {
a = []
for (i = 0; i < 256; i++) push(a, i)
x = (x + length(a)) | 0
}
return blackhole(sink, x)
},
array_indexed_sum: function(n) {
var sink = 0
var a = make_packed_array(1024)
var x = 0
var j = 0
var i = 0
for (j = 0; j < n; j++) {
x = 0
for (i = 0; i < 1024; i++) {
x = (x + a[i]) | 0
}
}
return blackhole(sink, x)
},
prop_read_mono: function(n) {
var sink = 0
var o = make_obj_xy(1, 2)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = (x + o.x) | 0
return blackhole(sink, x)
},
prop_read_poly_2: function(n) {
var sink = 0
var a = make_obj_xy(1, 2)
var b = make_obj_yx(1, 2)
var x = 0
var i = 0
var o = null
for (i = 0; i < n; i++) {
o = (i & 1) == 0 ? a : b
x = (x + o.x) | 0
}
return blackhole(sink, x)
},
prop_read_poly_4: function(n) {
var sink = 0
var shapes = [
{x: 1, y: 2},
{y: 2, x: 1},
{x: 1, z: 3, y: 2},
{w: 0, x: 1, y: 2}
]
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + shapes[i & 3].x) | 0
}
return blackhole(sink, x)
},
string_concat_small: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var s = null
for (j = 0; j < n; j++) {
s = ""
for (i = 0; i < 16; i++) s = s + "x"
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_concat_medium: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var s = null
for (j = 0; j < n; j++) {
s = ""
for (i = 0; i < 100; i++) s = s + "abcdefghij"
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_slice: function(n) {
var sink = 0
var base = "the quick brown fox jumps over the lazy dog"
var x = 0
var i = 0
var s = null
for (i = 0; i < n; i++) {
s = text(base, i % 10, i % 10 + 10)
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
guard_hot_number: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = x + 1
return blackhole(sink, x | 0)
},
guard_mixed_types: function(n) {
var sink = 0
var vals = [1, "a", 2, "b", 3, "c", 4, "d"]
var x = 0
var i = 0
for (i = 0; i < n; i++) {
if (is_number(vals[i & 7])) x = (x + vals[i & 7]) | 0
}
return blackhole(sink, x)
},
reduce_sum: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + reduce(a, function(acc, v) { return acc + v }, 0)) | 0
}
return blackhole(sink, x)
},
filter_evens: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + length(filter(a, function(v) { return v % 2 == 0 }))) | 0
}
return blackhole(sink, x)
},
arrfor_sum: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
var sum = 0
for (i = 0; i < n; i++) {
sum = 0
arrfor(a, function(v) { sum += v })
x = (x + sum) | 0
}
return blackhole(sink, x)
}
}

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -319,7 +319,8 @@ JSC_SCALL(os_system,
JSC_CCALL(os_exit,
int code = 0;
if (argc > 0) JS_ToInt32(js, &code, argv[0]);
if (argc > 0 && !JS_IsNull(argv[0]))
JS_ToInt32(js, &code, argv[0]);
exit(code);
)

View File

@@ -13,14 +13,33 @@ var os = use('internal/os')
var link = use('link')
// These come from env (via core_extras in engine.cm):
// analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env,
// content_hash, cache_path, ensure_build_dir
// analyze, run_ast_fn, core_json, use_cache, core_path, shop_path, actor_api,
// runtime_env, content_hash, cache_path, ensure_build_dir
var shop_json = core_json
var global_shop_path = shop_path
var my$_ = actor_api
var core = "core"
// Compiler fingerprint: hash of all compiler source files so that any compiler
// change invalidates the entire build cache. Folded into hash_path().
var compiler_fingerprint = (function() {
var files = [
"tokenize", "parse", "fold", "mcode", "streamline",
"qbe", "qbe_emit", "ir_stats"
]
var combined = ""
var i = 0
var path = null
while (i < length(files)) {
path = core_path + '/' + files[i] + '.cm'
if (fd.is_file(path))
combined = combined + text(fd.slurp(path))
i = i + 1
}
return content_hash(stone(blob(combined)))
})()
// Make a package name safe for use in C identifiers.
// Replaces /, ., -, @ with _ so the result is a valid C identifier fragment.
function safe_c_name(name) {
@@ -43,7 +62,7 @@ function put_into_cache(content, obj)
function hash_path(content, salt)
{
var s = salt || 'mach'
return global_shop_path + '/build/' + content_hash(stone(blob(text(content) + '\n' + s)))
return global_shop_path + '/build/' + content_hash(stone(blob(text(content) + '\n' + s + '\n' + compiler_fingerprint)))
}
var Shop = {}
@@ -456,6 +475,15 @@ Shop.extract_commit_hash = function(pkg, response) {
var open_dls = {}
var package_dylibs = {} // pkg -> [{file, symbol, dylib}, ...]
function open_dylib_cached(path) {
var handle = open_dls[path]
if (handle) return handle
handle = os.dylib_open(path)
if (!handle) return null
open_dls[path] = handle
return handle
}
// Host target detection for native dylib resolution
function detect_host_target() {
var platform = os.platform()
@@ -492,7 +520,7 @@ function try_native_mod_dylib(pkg, stem) {
if (!fd.is_file(build_path)) return null
log.shop('native dylib cache hit: ' + stem)
var handle = os.dylib_open(build_path)
var handle = open_dylib_cached(build_path)
if (!handle) return null
var sym = Shop.c_symbol_for_file(pkg, stem)
return {_native: true, _handle: handle, _sym: sym}
@@ -1250,8 +1278,17 @@ Shop.is_loaded = function is_loaded(path, package_context) {
}
// Create a use function bound to a specific package context
function make_use_fn(pkg) {
function make_use_fn(pkg, force_native) {
return function(path) {
var _native = null
if (force_native && !native_mode) {
_native = function() {
return Shop.use_native(path, pkg)
} disruption {
return Shop.use(path, pkg)
}
return _native()
}
return Shop.use(path, pkg)
}
}
@@ -1282,7 +1319,7 @@ function execute_module(info)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
pkg = file_info.package
env.use = make_use_fn(pkg)
env.use = make_use_fn(pkg, true)
env = stone(env)
used = os.native_module_load_named(
mod_resolve.symbol._handle, mod_resolve.symbol._sym, env)
@@ -2186,7 +2223,7 @@ Shop.load_as_dylib = function(path, pkg) {
if (!file_info) file_info = Shop.file_info(file_path)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
env.use = make_use_fn(real_pkg)
env.use = make_use_fn(real_pkg, true)
env = stone(env)
return os.native_module_load_named(result._handle, result._sym, env)
}
@@ -2233,18 +2270,45 @@ Shop.parse_package = function(locator) {
Shop.use_native = function(path, package_context) {
var src_path = path
if (!starts_with(path, '/'))
var locator = null
var lookup = null
var cache_key = null
var cfg = null
var old_native = null
if (!starts_with(path, '/') && !fd.is_file(path)) {
lookup = ends_with(path, '.cm') ? path : path + '.cm'
locator = resolve_locator(lookup, package_context)
if (!locator) { print('Module not found: ' + path); disrupt }
src_path = locator.path
} else if (!starts_with(path, '/')) {
src_path = fd.realpath(path)
}
if (!fd.is_file(src_path)) { log.error('File not found: ' + path); disrupt }
var file_info = Shop.file_info(src_path)
var pkg = file_info.package || package_context
var pkg = file_info.package || (locator ? locator.pkg : package_context)
var sym_stem = fd.basename(src_path)
var pkg_dir = null
cache_key = 'native:' + text(pkg || '') + ':' + src_path
if (use_cache[cache_key]) return use_cache[cache_key]
var sym_name = null
if (pkg)
sym_name = Shop.c_symbol_for_file(pkg, fd.basename(src_path))
if (pkg) {
pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
if (starts_with(src_path, pkg_dir + '/')) {
sym_stem = text(src_path, length(pkg_dir) + 1)
}
sym_name = Shop.c_symbol_for_file(pkg, sym_stem)
}
var build = Shop.use('build', 'core')
var build = use_cache['core/build'] || use_cache['build']
if (!build) {
cfg = Shop.load_config()
old_native = cfg.policy.native
cfg.policy.native = false
build = Shop.use('build', 'core')
cfg.policy.native = old_native
}
var dylib_path = build.compile_native(src_path, null, null, pkg)
var handle = os.dylib_open(dylib_path)
@@ -2253,12 +2317,16 @@ Shop.use_native = function(path, package_context) {
// Build env with runtime functions and capabilities
var inject = Shop.script_inject_for(file_info)
var env = inject_env(inject)
env.use = make_use_fn(pkg)
env.use = make_use_fn(pkg, true)
env = stone(env)
var loaded = null
if (sym_name)
return os.native_module_load_named(handle, sym_name, env)
return os.native_module_load(handle, env)
loaded = os.native_module_load_named(handle, sym_name, env)
else
loaded = os.native_module_load(handle, env)
use_cache[cache_key] = loaded
return loaded
}
return Shop

452
mcode.cm
View File

@@ -38,6 +38,11 @@ var mcode = function(ast) {
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"
}
@@ -873,6 +878,78 @@ var mcode = function(ast) {
var inline_every = true
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.
@@ -889,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)
@@ -907,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 {
@@ -936,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
}
@@ -1034,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)
@@ -1087,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)
@@ -1101,6 +1160,159 @@ var mcode = function(ast) {
// --- Inline expansion: filter(arr, fn) ---
var expand_inline_filter = function(dest, arr_slot, fn_slot) {
var result = alloc_slot()
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 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", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot)
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()
var len = alloc_slot()
var i = alloc_slot()
@@ -1114,12 +1326,11 @@ var mcode = function(ast) {
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 loop_label = gen_label("map_loop")
var call_one_label = gen_label("map_call_one")
var call_two_label = gen_label("map_call_two")
var call_done_label = gen_label("map_call_done")
var done_label = gen_label("map_done")
add_instr(["array", result, 0])
emit_2("length", len, arr_slot)
emit_2("int", i, 0)
@@ -1152,9 +1363,37 @@ var mcode = function(ast) {
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_2("push", result, val)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(done_label)
emit_2("move", dest, result)
return dest
}
// --- Inline expansion: array(arr, intrinsic_op) → map with direct opcode ---
var expand_inline_map_intrinsic = function(dest, arr_slot, opcode) {
var result = alloc_slot()
var len = alloc_slot()
var i = alloc_slot()
var check = alloc_slot()
var item = alloc_slot()
var val = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var loop_label = gen_label("mapi_loop")
var done_label = gen_label("mapi_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_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_2(opcode, val, item)
emit_2("push", result, val)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(done_label)
@@ -1246,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)
@@ -1268,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)
@@ -1315,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)
@@ -1327,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)
@@ -1896,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)
@@ -1921,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)
@@ -1929,6 +2186,25 @@ var mcode = function(ast) {
d = alloc_slot()
return expand_inline_reduce(d, {arr: a0, fn: a1, init: a2, rev: a3}, nargs)
}
// array(arr, fn) → inline map expansion
// Skip when first arg is a number literal (that's array(N, fn) — creation, not map)
if (nargs == 2 && fname == "array" && inline_map
&& args_list[0].kind != "number") {
// Specialized: array(arr, known_sensory_intrinsic) → direct opcode loop
if (args_list[1].kind == "name" && args_list[1].intrinsic == true
&& sensory_ops[args_list[1].name] != null) {
a0 = gen_expr(args_list[0], -1)
d = alloc_slot()
return expand_inline_map_intrinsic(d, a0, sensory_ops[args_list[1].name])
}
// General: array(arr, fn_literal) → map loop with arity dispatch
if (args_list[1].kind == "function") {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
d = alloc_slot()
return expand_inline_map(d, a0, a1)
}
}
}
// Collect arg slots
@@ -2085,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)
@@ -2310,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])
@@ -2351,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])
@@ -2386,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
@@ -2420,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)) {

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,7 @@
*/
#include "quickjs-internal.h"
#include <assert.h>
/* ============================================================
Mach VM instruction definitions (private to mach.c)
@@ -88,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)
@@ -269,6 +270,22 @@ typedef enum MachOpcode {
MACH_IS_STONE, /* R(A) = is_stone(R(B)) */
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
MACH_IS_BLOB, /* R(A) = is_blob(R(B)) */
MACH_IS_DATA, /* R(A) = is_data(R(B)) — plain record, not array/func/blob */
MACH_IS_TRUE, /* R(A) = (R(B) === true) */
MACH_IS_FALSE, /* R(A) = (R(B) === false) */
MACH_IS_FIT, /* R(A) = is_fit(R(B)) — safe integer */
MACH_IS_CHAR, /* R(A) = is_character(R(B)) — single char text */
MACH_IS_DIGIT, /* R(A) = is_digit(R(B)) */
MACH_IS_LETTER, /* R(A) = is_letter(R(B)) */
MACH_IS_LOWER, /* R(A) = is_lower(R(B)) */
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;
@@ -381,6 +398,22 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_IS_STONE] = "is_stone",
[MACH_LENGTH] = "length",
[MACH_IS_PROXY] = "is_proxy",
[MACH_IS_BLOB] = "is_blob",
[MACH_IS_DATA] = "is_data",
[MACH_IS_TRUE] = "is_true",
[MACH_IS_FALSE] = "is_false",
[MACH_IS_FIT] = "is_fit",
[MACH_IS_CHAR] = "is_char",
[MACH_IS_DIGIT] = "is_digit",
[MACH_IS_LETTER] = "is_letter",
[MACH_IS_LOWER] = "is_lower",
[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 ---- */
@@ -1396,6 +1429,14 @@ vm_dispatch:
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
DT(MACH_IS_BLOB), DT(MACH_IS_DATA),
DT(MACH_IS_TRUE), DT(MACH_IS_FALSE),
DT(MACH_IS_FIT), DT(MACH_IS_CHAR),
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
@@ -1988,21 +2029,12 @@ vm_dispatch:
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
if (!target) {
fprintf(stderr, "GETUP: NULL outer_frame at depth 0! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
pc-1, a, depth, c, code->nr_slots, instr);
result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame");
goto disrupt;
}
assert(depth > 0);
assert(target != NULL);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
if (!next) {
fprintf(stderr, "GETUP: NULL outer_frame at depth %d! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
d, pc-1, a, depth, c, code->nr_slots, instr);
result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame at depth %d", d);
goto disrupt;
}
assert(next != NULL);
target = next;
}
stone_mutable_text(target->slots[c]);
@@ -2015,22 +2047,14 @@ vm_dispatch:
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
assert(depth > 0);
assert(target != NULL);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
assert(target != NULL);
}
{
uint64_t tcap = objhdr_cap56(target->header);
if ((unsigned)c >= tcap) {
fprintf(stderr, "MACH_SETUP OOB: slot=%d >= target_cap=%llu depth=%d "
"cur_fn=%s (%s) pc=%u\n",
c, (unsigned long long)tcap, depth,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?", pc - 1);
fflush(stderr);
VM_BREAK();
}
}
assert((unsigned)c < objhdr_cap56(target->header));
target->slots[c] = frame->slots[a];
VM_BREAK();
}
@@ -2055,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) {
@@ -2081,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) {
@@ -2386,6 +2400,166 @@ vm_dispatch:
frame->slots[a] = JS_NewBool(ctx, is_proxy);
VM_BREAK();
}
VM_CASE(MACH_IS_BLOB):
frame->slots[a] = JS_NewBool(ctx, mist_is_blob(frame->slots[b]));
VM_BREAK();
VM_CASE(MACH_IS_DATA): {
JSValue v = frame->slots[b];
int result = 0;
if (mist_is_gc_object(v) && !mist_is_array(v)
&& !mist_is_function(v) && !mist_is_blob(v))
result = 1;
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_TRUE):
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_TRUE);
VM_BREAK();
VM_CASE(MACH_IS_FALSE):
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_FALSE);
VM_BREAK();
VM_CASE(MACH_IS_FIT): {
JSValue v = frame->slots[b];
int result = 0;
if (JS_IsInt(v)) {
result = 1;
} else if (JS_IsShortFloat(v)) {
double d = JS_VALUE_GET_FLOAT64(v);
result = (isfinite(d) && trunc(d) == d && fabs(d) <= 9007199254740992.0);
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_CHAR): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v))
result = (MIST_GetImmediateASCIILen(v) == 1);
else if (mist_is_text(v))
result = (js_string_value_len(v) == 1);
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_DIGIT): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch >= '0' && ch <= '9');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch >= '0' && ch <= '9');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_LETTER): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_LOWER): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch >= 'a' && ch <= 'z');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch >= 'a' && ch <= 'z');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_UPPER): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch >= 'A' && ch <= 'Z');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch >= 'A' && ch <= 'Z');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_WS): {
JSValue v = frame->slots[b];
int result = 0;
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
result = (ch == ' ' || ch == '\t' || ch == '\n'
|| ch == '\r' || ch == '\f' || ch == '\v');
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
result = (ch == ' ' || ch == '\t' || ch == '\n'
|| ch == '\r' || ch == '\f' || ch == '\v');
}
frame->slots[a] = JS_NewBool(ctx, result);
VM_BREAK();
}
VM_CASE(MACH_IS_ACTOR): {
JSValue v = frame->slots[b];
int result = 0;
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym)) {
result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
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]);
@@ -2695,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;
@@ -3031,6 +3266,19 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
else if (strcmp(op, "is_stone") == 0) { AB2(MACH_IS_STONE); }
else if (strcmp(op, "length") == 0) { AB2(MACH_LENGTH); }
else if (strcmp(op, "is_proxy") == 0) { AB2(MACH_IS_PROXY); }
else if (strcmp(op, "is_blob") == 0) { AB2(MACH_IS_BLOB); }
else if (strcmp(op, "is_data") == 0) { AB2(MACH_IS_DATA); }
else if (strcmp(op, "is_true") == 0) { AB2(MACH_IS_TRUE); }
else if (strcmp(op, "is_false") == 0) { AB2(MACH_IS_FALSE); }
else if (strcmp(op, "is_fit") == 0) { AB2(MACH_IS_FIT); }
else if (strcmp(op, "is_char") == 0) { AB2(MACH_IS_CHAR); }
else if (strcmp(op, "is_digit") == 0) { AB2(MACH_IS_DIGIT); }
else if (strcmp(op, "is_letter") == 0) { AB2(MACH_IS_LETTER); }
else if (strcmp(op, "is_lower") == 0) { AB2(MACH_IS_LOWER); }
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); }
@@ -3181,6 +3429,34 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "jump_null") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPNULL, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "wary_true") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_WARYTRUE, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "wary_false") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_WARYFALSE, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
else if (strcmp(op, "jump_empty") == 0) {
int reg = A1;
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
int pc_now = s.code_count;
EM(MACH_AsBx(MACH_JMPEMPTY, reg, 0));
ml_patch(&s, pc_now, lbl, 0, reg);
}
/* Return / error */
else if (strcmp(op, "return") == 0) {
EM(MACH_ABC(MACH_RETURN, A1, 0, 0));

View File

@@ -17,10 +17,6 @@ JSValue qbe_new_float64(JSContext *ctx, double d) {
return __JS_NewFloat64(ctx, d);
}
JSValue qbe_new_string(JSContext *ctx, const char *str) {
return JS_NewString(ctx, str);
}
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
enum {
QBE_CMP_EQ = 0,
@@ -31,128 +27,89 @@ enum {
QBE_CMP_GE = 5
};
/* ============================================================
Float binary arithmetic
============================================================ */
static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b,
double (*op)(double, double)) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
double r = op(da, db);
if (!isfinite(r))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
static double op_add(double a, double b) { return a + b; }
static double op_sub(double a, double b) { return a - b; }
static double op_mul(double a, double b) { return a * b; }
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_add);
}
/* Generic add: concat if both text, float add if both numeric, else type error */
JSValue cell_rt_add(JSContext *ctx, JSValue a, JSValue b) {
if (JS_IsText(a) && JS_IsText(b))
return JS_ConcatString(ctx, a, b);
if (JS_IsNumber(a) && JS_IsNumber(b))
return qbe_float_binop(ctx, a, b, op_add);
JS_RaiseDisrupt(ctx, "cannot add incompatible types");
return JS_NULL;
}
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_sub);
}
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_mul);
}
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
if (db == 0.0)
return JS_NULL;
double r = da / db;
if (!isfinite(r))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
if (db == 0.0)
return JS_NULL;
double r = fmod(da, db);
if (!isfinite(r))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
double r = pow(da, db);
if (!isfinite(r) && isfinite(da) && isfinite(db))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
/* ============================================================
Float unary ops
============================================================ */
JSValue qbe_float_neg(JSContext *ctx, JSValue v) {
double d;
JS_ToFloat64(ctx, &d, v);
return JS_NewFloat64(ctx, -d);
}
JSValue qbe_float_inc(JSContext *ctx, JSValue v) {
double d;
JS_ToFloat64(ctx, &d, v);
return JS_NewFloat64(ctx, d + 1);
}
JSValue qbe_float_dec(JSContext *ctx, JSValue v) {
double d;
JS_ToFloat64(ctx, &d, v);
return JS_NewFloat64(ctx, d - 1);
}
/* ============================================================
Float comparison — returns 0 or 1 for QBE branching
============================================================ */
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
switch (op) {
case QBE_CMP_EQ: return da == db;
case QBE_CMP_NE: return da != db;
case QBE_CMP_LT: return da < db;
case QBE_CMP_LE: return da <= db;
case QBE_CMP_GT: return da > db;
case QBE_CMP_GE: return da >= db;
default: return 0;
/* Generic comparison helper matching MACH eq/ne/lt/le/gt/ge semantics. */
JSValue cell_rt_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
if (JS_VALUE_IS_BOTH_INT(a, b)) {
int32_t ia = JS_VALUE_GET_INT(a);
int32_t ib = JS_VALUE_GET_INT(b);
switch (op) {
case QBE_CMP_EQ: return JS_NewBool(ctx, ia == ib);
case QBE_CMP_NE: return JS_NewBool(ctx, ia != ib);
case QBE_CMP_LT: return JS_NewBool(ctx, ia < ib);
case QBE_CMP_LE: return JS_NewBool(ctx, ia <= ib);
case QBE_CMP_GT: return JS_NewBool(ctx, ia > ib);
case QBE_CMP_GE: return JS_NewBool(ctx, ia >= ib);
default: return JS_NewBool(ctx, 0);
}
}
}
/* ============================================================
Boolean conversion wrapper
============================================================ */
/* Fast path: identity after chasing forward pointers (matches MACH). */
{
JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a;
JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b;
if (ca == cb) {
if (op == QBE_CMP_EQ || op == QBE_CMP_LE || op == QBE_CMP_GE)
return JS_TRUE;
if (op == QBE_CMP_NE)
return JS_FALSE;
}
}
int qbe_to_bool(JSContext *ctx, JSValue v) {
return JS_ToBool(ctx, v);
if (JS_IsNumber(a) && JS_IsNumber(b)) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
switch (op) {
case QBE_CMP_EQ: return JS_NewBool(ctx, da == db);
case QBE_CMP_NE: return JS_NewBool(ctx, da != db);
case QBE_CMP_LT: return JS_NewBool(ctx, da < db);
case QBE_CMP_LE: return JS_NewBool(ctx, da <= db);
case QBE_CMP_GT: return JS_NewBool(ctx, da > db);
case QBE_CMP_GE: return JS_NewBool(ctx, da >= db);
default: return JS_NewBool(ctx, 0);
}
}
if (mist_is_text(a) && mist_is_text(b)) {
int cmp = js_string_compare_value(ctx, a, b, FALSE);
switch (op) {
case QBE_CMP_EQ: return JS_NewBool(ctx, cmp == 0);
case QBE_CMP_NE: return JS_NewBool(ctx, cmp != 0);
case QBE_CMP_LT: return JS_NewBool(ctx, cmp < 0);
case QBE_CMP_LE: return JS_NewBool(ctx, cmp <= 0);
case QBE_CMP_GT: return JS_NewBool(ctx, cmp > 0);
case QBE_CMP_GE: return JS_NewBool(ctx, cmp >= 0);
default: return JS_NewBool(ctx, 0);
}
}
if (JS_IsNull(a) && JS_IsNull(b)) {
if (op == QBE_CMP_EQ || op == QBE_CMP_LE || op == QBE_CMP_GE)
return JS_TRUE;
return JS_FALSE;
}
if (JS_IsBool(a) && JS_IsBool(b)) {
int ba = JS_VALUE_GET_BOOL(a);
int bb = JS_VALUE_GET_BOOL(b);
switch (op) {
case QBE_CMP_EQ: return JS_NewBool(ctx, ba == bb);
case QBE_CMP_NE: return JS_NewBool(ctx, ba != bb);
case QBE_CMP_LT: return JS_NewBool(ctx, ba < bb);
case QBE_CMP_LE: return JS_NewBool(ctx, ba <= bb);
case QBE_CMP_GT: return JS_NewBool(ctx, ba > bb);
case QBE_CMP_GE: return JS_NewBool(ctx, ba >= bb);
default: return JS_NewBool(ctx, 0);
}
}
if (op == QBE_CMP_EQ)
return JS_NewBool(ctx, 0);
if (op == QBE_CMP_NE)
return JS_NewBool(ctx, 1);
JS_RaiseDisrupt(ctx, "cannot compare: operands must be same type");
return JS_EXCEPTION;
}
/* ============================================================
@@ -190,29 +147,33 @@ JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewInt32(ctx, ia ^ ib);
}
/* ============================================================
Shift ops on floats (convert to int32, shift, re-tag)
============================================================ */
/* Concat helper matching MACH_CONCAT semantics exactly. */
JSValue cell_rt_concat(JSContext *ctx, JSValue left, JSValue right, int self_assign) {
if (self_assign) {
if (JS_IsPtr(left)) {
JSText *s = (JSText *)chase(left);
int slen = (int)s->length;
int rlen = js_string_value_len(right);
int cap = (int)objhdr_cap56(s->hdr);
if (objhdr_type(s->hdr) == OBJ_TEXT
&& !(s->hdr & OBJHDR_S_MASK)
&& slen + rlen <= cap) {
for (int i = 0; i < rlen; i++)
string_put(s, slen + i, js_string_value_get(right, i));
s->length = slen + rlen;
return left;
}
}
JSValue res = JS_ConcatStringGrow(ctx, left, right);
if (JS_IsException(res))
return JS_EXCEPTION;
return res;
}
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia << (ib & 31));
}
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia >> (ib & 31));
}
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
JSValue res = JS_ConcatString(ctx, left, right);
if (JS_IsException(res))
return JS_EXCEPTION;
return res;
}
/* ============================================================
@@ -381,13 +342,6 @@ static JSValue cell_rt_load_field_key(JSContext *ctx, JSValue obj, JSValue key)
return JS_GetProperty(ctx, obj, key);
}
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
JSValue key = aot_key_from_cstr(ctx, name);
if (JS_IsException(key))
return JS_EXCEPTION;
return cell_rt_load_field_key(ctx, obj, key);
}
JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
JSValue key = aot_lit_from_index(ctx, lit_idx);
if (JS_IsException(key))
@@ -395,29 +349,12 @@ JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
return cell_rt_load_field_key(ctx, obj, key);
}
/* Like cell_rt_load_field but without the function guard.
Used by load_dynamic when the key happens to be a static string. */
JSValue cell_rt_load_prop_str(JSContext *ctx, JSValue obj, const char *name) {
JSValue key = aot_key_from_cstr(ctx, name);
if (JS_IsException(key))
return JS_EXCEPTION;
return JS_GetProperty(ctx, obj, key);
}
static int cell_rt_store_field_key(JSContext *ctx, JSValue val, JSValue obj,
JSValue key) {
int ret = JS_SetProperty(ctx, obj, key, val);
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
}
int cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
const char *name) {
JSValue key = aot_key_from_cstr(ctx, name);
if (JS_IsException(key))
return 0;
return cell_rt_store_field_key(ctx, val, obj, key);
}
int cell_rt_store_field_lit(JSContext *ctx, JSValue val, JSValue obj,
int64_t lit_idx) {
JSValue key = aot_lit_from_index(ctx, lit_idx);
@@ -455,25 +392,6 @@ int cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
}
}
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
if (JS_IsInt(idx))
return JS_GetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx));
return JS_GetProperty(ctx, arr, idx);
}
int cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
JSValue idx) {
int ret = 0;
JSValue nr = JS_NULL;
if (JS_IsInt(idx))
nr = JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val);
else
ret = JS_SetProperty(ctx, arr, idx, val);
if (JS_IsInt(idx))
return JS_IsException(nr) ? 0 : 1;
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
}
/* --- Intrinsic/global lookup --- */
void cell_rt_set_native_env(JSContext *ctx, JSValue env) {
@@ -546,52 +464,6 @@ JSValue cell_rt_get_intrinsic_lit(JSContext *ctx, int64_t lit_idx) {
return cell_rt_get_intrinsic_key(ctx, key);
}
/* --- Closure access ---
Walk the outer_frame chain on JSFunction (JS_FUNC_KIND_NATIVE).
The frame's function field links to the JSFunction, whose
u.native.outer_frame points to the enclosing frame.
GC traces outer_frame naturally — no registry needed. */
/* Get the outer frame's slots from a frame pointer.
The frame's function must be JS_FUNC_KIND_NATIVE. */
static JSValue *get_outer_frame_slots(JSValue *fp) {
/* fp points to frame->slots[0]; frame header is before it */
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
if (JS_IsNull(frame->function))
return NULL;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
if (fn->kind != JS_FUNC_KIND_NATIVE)
return NULL;
JSValue outer = fn->u.cell.outer_frame;
if (JS_IsNull(outer))
return NULL;
JSFrameRegister *outer_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(outer);
return (JSValue *)outer_frame->slots;
}
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
int64_t slot) {
(void)ctx;
JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) {
frame = get_outer_frame_slots(frame);
if (!frame)
return JS_NULL;
}
return frame[slot];
}
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
int64_t slot) {
(void)ctx;
JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) {
frame = get_outer_frame_slots(frame);
if (!frame) return;
}
frame[slot] = val;
}
/* --- GC-managed AOT frame stack ---
Each native dispatch loop pushes a GC ref so the GC can find and
update the current frame pointer when it moves objects.
@@ -740,18 +612,6 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
to call another function (instead of recursing via C stack).
============================================================ */
/* Poll pause state on taken backward jumps (AOT backedges).
MACH can suspend/resume a register VM frame at pc granularity; native AOT
does not currently have an equivalent resume point, so we acknowledge timer
pauses by clearing pause_flag and continuing the current turn. */
int cell_rt_check_backedge(JSContext *ctx) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf >= 1) {
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
}
return 0;
}
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
NativeRTState *st = native_state(ctx);
if (!st) return;
@@ -779,6 +639,46 @@ static int cell_check_call_arity(JSContext *ctx, JSFunction *fn, int argc) {
return 1;
}
JSValue cell_rt_apply(JSContext *ctx, JSValue fn_val, JSValue arg_val) {
if (!mist_is_function(fn_val))
return fn_val;
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
if (!mist_is_array(arg_val)) {
if (!cell_check_call_arity(ctx, fn, 1))
return JS_EXCEPTION;
return JS_CallInternal(ctx, fn_val, JS_NULL, 1, &arg_val, 0);
}
JSArray *arr = JS_VALUE_GET_ARRAY(arg_val);
int len = arr->len;
if (!cell_check_call_arity(ctx, fn, len))
return JS_EXCEPTION;
if (len == 0)
return JS_CallInternal(ctx, fn_val, JS_NULL, 0, NULL, 0);
JSValue args[len];
for (int i = 0; i < len; i++)
args[i] = arr->values[i];
return JS_CallInternal(ctx, fn_val, JS_NULL, len, args, 0);
}
static inline void cell_copy_args_0_4(JSValue *fp, JSValue *argv, int copy) {
/* fp[0] is `this`; copy args into fp[1..4] */
switch (copy) {
case 4: fp[4] = argv[3];
case 3: fp[3] = argv[2];
case 2: fp[2] = argv[1];
case 1: fp[1] = argv[0];
case 0: break;
default: break;
}
}
static inline void cell_sync_dl_from_native_fn(NativeRTState *st, JSFunction *fn) {
st->current_dl_handle = JS_VALUE_GET_CODE(fn->u.cell.code)->u.native.dl_handle;
}
/* Entry point called from JS_CallInternal / JS_Call / MACH_INVOKE
for JS_FUNC_KIND_NATIVE functions. */
JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
@@ -823,8 +723,14 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp[0] = this_obj;
int copy = (argc < arity) ? argc : arity;
if (copy < 0) copy = argc; /* variadic: copy all */
for (int i = 0; i < copy && i < nr_slots - 1; i++)
fp[1 + i] = argv[i];
if (copy > nr_slots - 1)
copy = nr_slots - 1;
if (unlikely(copy > 4)) {
JS_RaiseDisrupt(ctx, "native calls support at most 4 arguments");
RETURN_DISPATCH(JS_EXCEPTION);
}
if (copy > 0 && argv)
cell_copy_args_0_4(fp, argv, copy);
/* Link function to frame for closure access */
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
@@ -873,17 +779,29 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
if (!JS_IsFunction(callee_fn_val)) {
JS_RaiseDisrupt(ctx, "not a function");
{
int ret_info = JS_VALUE_GET_INT(frame->address);
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF)
fp[ret_slot] = JS_EXCEPTION;
}
/* Resume caller with exception pending */
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) {
int ret_info = JS_VALUE_GET_INT(frame->address);
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF)
fp[ret_slot] = JS_EXCEPTION;
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
@@ -911,6 +829,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
fp = (JSValue *)frame->slots;
fn = callee_ptr;
cell_sync_dl_from_native_fn(st, callee_fn);
} else {
/* Regular call: link caller and push prepared callee frame. */
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -932,12 +851,14 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp = (JSValue *)frame->slots;
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
fp = (JSValue *)frame->slots;
fn = callee_ptr;
cell_sync_dl_from_native_fn(st, callee_fn);
}
} else {
/* Non-native callee (C function, register VM, etc.) —
@@ -969,6 +890,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
Just resume it — it will detect JS_EXCEPTION in the return slot. */
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
@@ -1000,6 +922,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
/* Resume caller */
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, caller_fn);
} else {
/* Regular call: store result and resume current function */
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -1009,6 +932,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
/* fn stays the same — we resume the same function at next segment */
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, cur_fn);
}
}
JS_PopGCRef(ctx, &callee_ref);
@@ -1042,6 +966,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_caller_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_caller_fn);
continue;
}
@@ -1066,6 +991,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, caller_fn);
continue;
}
@@ -1159,56 +1085,10 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
return JS_MKPTR(new_frame);
}
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
if (frame_val == JS_EXCEPTION || frame_val == JS_NULL) return;
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fr->slots[idx] = val;
}
/* cell_rt_invoke — still used for non-dispatch-loop paths (e.g. old code) */
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
if (frame_val == JS_EXCEPTION) return JS_EXCEPTION;
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
int c_argc = JS_VALUE_GET_INT(fr->address);
if (c_argc < 0) c_argc = 0;
JSValue fn_val = fr->function;
if (!JS_IsFunction(fn_val)) {
JS_RaiseDisrupt(ctx, "not a function");
return JS_EXCEPTION;
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
JSValue result;
if (!cell_check_call_arity(ctx, fn, c_argc))
return JS_EXCEPTION;
if (fn->kind == JS_FUNC_KIND_C) {
result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
result = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
} else {
JSValue args[c_argc > 0 ? c_argc : 1];
for (int i = 0; i < c_argc; i++)
args[i] = fr->slots[i + 1];
result = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, args, 0);
}
if (JS_IsException(result))
return JS_EXCEPTION;
if (JS_HasException(ctx))
JS_GetException(ctx);
return result;
}
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
return cell_rt_frame(ctx, fn, nargs);
}
JSValue cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) {
return cell_rt_invoke(ctx, frame_val);
}
/* --- Array push/pop --- */
JSValue cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {
@@ -1236,13 +1116,6 @@ static JSValue cell_rt_delete_key(JSContext *ctx, JSValue obj, JSValue key) {
return JS_NewBool(ctx, ret >= 0);
}
JSValue cell_rt_delete_str(JSContext *ctx, JSValue obj, const char *name) {
JSValue key = aot_key_from_cstr(ctx, name);
if (JS_IsException(key))
return JS_EXCEPTION;
return cell_rt_delete_key(ctx, obj, key);
}
JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
JSValue key = aot_lit_from_index(ctx, lit_idx);
if (JS_IsException(key))
@@ -1250,49 +1123,6 @@ JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
return cell_rt_delete_key(ctx, obj, key);
}
/* --- Typeof --- */
JSValue cell_rt_typeof(JSContext *ctx, JSValue val) {
if (JS_IsNull(val)) return JS_NewString(ctx, "null");
if (JS_IsInt(val) || JS_IsNumber(val)) return JS_NewString(ctx, "number");
if (JS_IsBool(val)) return JS_NewString(ctx, "logical");
if (JS_IsText(val)) return JS_NewString(ctx, "text");
if (JS_IsFunction(val)) return JS_NewString(ctx, "function");
if (JS_IsArray(val)) return JS_NewString(ctx, "array");
if (JS_IsRecord(val)) return JS_NewString(ctx, "object");
return JS_NewString(ctx, "unknown");
}
/* --- Text comparison stubs (called from QBE type-dispatch branches) --- */
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) < 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) > 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) <= 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) >= 0 : 0;
return JS_NewBool(ctx, r);
}
static int cell_rt_tol_eq_inner(JSContext *ctx, JSValue a, JSValue b,
JSValue tol) {
if (JS_IsNumber(a) && JS_IsNumber(b) && JS_IsNumber(tol)) {
@@ -1326,31 +1156,11 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b, JSValue tol) {
return JS_NewBool(ctx, !cell_rt_tol_eq_inner(ctx, a, b, tol));
}
/* --- Type check: is_proxy (function with arity 2) --- */
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
(void)ctx;
if (!JS_IsFunction(v)) return 0;
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
return fn->length == 2;
}
/* --- Identity check (chases forwarding pointers) --- */
JSValue cell_rt_is_identical(JSContext *ctx, JSValue a, JSValue b) {
if (JS_IsPtr(a)) a = JS_MKPTR(chase(a));
if (JS_IsPtr(b)) b = JS_MKPTR(chase(b));
return JS_NewBool(ctx, a == b);
}
/* --- Short-circuit and/or (non-allocating) --- */
JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {
return JS_ToBool(ctx, left) ? right : left;
}
JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) {
return JS_ToBool(ctx, left) ? left : right;
int cell_rt_is_actor(JSContext *ctx, JSValue v) {
int result = 0;
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym))
result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0;
return result;
}
/* --- Exception checking ---

View File

@@ -149,8 +149,10 @@ int JS_IsPretext (JSValue v) {
}
JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) {
if (ref == ctx->top_gc_ref)
return &ref->val; /* already at top — loop re-entry; just update val */
if (ref == ctx->top_gc_ref) {
fprintf(stderr, "[warn] JS_PushGCRef duplicate top ref (non-fatal)\n");
return &ref->val;
}
ref->prev = ctx->top_gc_ref;
ctx->top_gc_ref = ref;
ref->val = JS_NULL;
@@ -202,7 +204,10 @@ JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) {
}
JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) {
assert(ref != ctx->last_gc_ref && "JS_AddGCRef: same address added twice — cycle in GC ref list");
if (ref == ctx->last_gc_ref) {
fprintf(stderr, "[warn] JS_AddGCRef duplicate tail ref (non-fatal)\n");
return &ref->val;
}
ref->prev = ctx->last_gc_ref;
ctx->last_gc_ref = ref;
ref->val = JS_NULL;
@@ -9441,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);
@@ -10531,6 +10544,8 @@ static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue
if (!JS_IsArray (obj)) return JS_NULL;
JSArray *arr = JS_VALUE_GET_ARRAY (obj);
if (objhdr_s (arr->mist_hdr))
return JS_RaiseDisrupt (ctx, "cannot pop from a stoned array");
if (arr->len == 0) return JS_NULL;
@@ -11398,6 +11413,79 @@ static JSValue js_cell_is_letter (JSContext *ctx, JSValue this_val, int argc, JS
return JS_NewBool (ctx, (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}
/* is_true(val) - check if value is exactly true */
static JSValue js_cell_is_true (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
return JS_NewBool (ctx, argv[0] == JS_TRUE);
}
/* is_false(val) - check if value is exactly false */
static JSValue js_cell_is_false (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
return JS_NewBool (ctx, argv[0] == JS_FALSE);
}
/* is_fit(val) - check if value is a safe integer (int32 or float with integer value <= 2^53) */
static JSValue js_cell_is_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (JS_IsInt (val)) return JS_TRUE;
if (JS_IsShortFloat (val)) {
double d = JS_VALUE_GET_FLOAT64 (val);
return JS_NewBool (ctx, isfinite (d) && trunc (d) == d && fabs (d) <= 9007199254740992.0);
}
return JS_FALSE;
}
/* is_character(val) - check if value is a single character text */
static JSValue js_cell_is_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (!JS_IsText (val)) return JS_FALSE;
return JS_NewBool (ctx, js_string_value_len (val) == 1);
}
/* is_digit(val) - check if value is a single digit character */
static JSValue js_cell_is_digit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (!JS_IsText (val)) return JS_FALSE;
if (js_string_value_len (val) != 1) return JS_FALSE;
uint32_t c = js_string_value_get (val, 0);
return JS_NewBool (ctx, c >= '0' && c <= '9');
}
/* is_lower(val) - check if value is a single lowercase letter */
static JSValue js_cell_is_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (!JS_IsText (val)) return JS_FALSE;
if (js_string_value_len (val) != 1) return JS_FALSE;
uint32_t c = js_string_value_get (val, 0);
return JS_NewBool (ctx, c >= 'a' && c <= 'z');
}
/* is_upper(val) - check if value is a single uppercase letter */
static JSValue js_cell_is_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (!JS_IsText (val)) return JS_FALSE;
if (js_string_value_len (val) != 1) return JS_FALSE;
uint32_t c = js_string_value_get (val, 0);
return JS_NewBool (ctx, c >= 'A' && c <= 'Z');
}
/* is_whitespace(val) - check if value is a single whitespace character */
static JSValue js_cell_is_whitespace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (!JS_IsText (val)) return JS_FALSE;
if (js_string_value_len (val) != 1) return JS_FALSE;
uint32_t c = js_string_value_get (val, 0);
return JS_NewBool (ctx, c == ' ' || c == '\t' || c == '\n'
|| c == '\r' || c == '\f' || c == '\v');
}
/* is_proto(val, master) - check if val has master in prototype chain */
static JSValue js_cell_is_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2) return JS_FALSE;
@@ -11565,6 +11653,14 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1);
js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2);
js_set_global_cfunc(ctx, "is_letter", js_cell_is_letter, 1);
js_set_global_cfunc(ctx, "is_true", js_cell_is_true, 1);
js_set_global_cfunc(ctx, "is_false", js_cell_is_false, 1);
js_set_global_cfunc(ctx, "is_fit", js_cell_is_fit, 1);
js_set_global_cfunc(ctx, "is_character", js_cell_is_character, 1);
js_set_global_cfunc(ctx, "is_digit", js_cell_is_digit, 1);
js_set_global_cfunc(ctx, "is_lower", js_cell_is_lower, 1);
js_set_global_cfunc(ctx, "is_upper", js_cell_is_upper, 1);
js_set_global_cfunc(ctx, "is_whitespace", js_cell_is_whitespace, 1);
/* Utility functions */
js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2);

View File

@@ -46,13 +46,17 @@ var streamline = function(ir, log) {
not: true, and: true, or: true,
is_int: true, is_text: true, is_num: true,
is_bool: true, is_null: true, is_identical: true,
is_array: true, is_func: true, is_record: true, is_stone: true
is_array: true, is_func: true, is_record: true, is_stone: true,
is_blob: true, is_data: true,
is_true: true, is_false: true, is_fit: true,
is_char: true, is_digit: true, is_letter: true,
is_lower: true, is_upper: true, is_ws: true, is_actor: true
}
var type_check_map = {
is_int: T_INT, is_text: T_TEXT, is_num: T_NUM,
is_bool: T_BOOL, is_null: T_NULL,
is_array: T_ARRAY, is_func: T_FUNCTION,
is_record: T_RECORD
is_record: T_RECORD, is_blob: T_BLOB
}
// simplify_algebra dispatch tables
@@ -65,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
@@ -148,6 +159,21 @@ var streamline = function(ir, log) {
slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
return null
}
if (op == "load_index") {
slot_types[instr[2]] = T_ARRAY
slot_types[instr[3]] = T_INT
} else if (op == "store_index") {
slot_types[instr[1]] = T_ARRAY
slot_types[instr[3]] = T_INT
} else if (op == "load_field") {
slot_types[instr[2]] = T_RECORD
} else if (op == "store_field") {
slot_types[instr[1]] = T_RECORD
} else if (op == "push") {
slot_types[instr[1]] = T_ARRAY
} else if (op == "pop") {
slot_types[instr[2]] = T_ARRAY
}
rule = write_rules[op]
if (rule != null) {
typ = rule[1]
@@ -359,7 +385,12 @@ var streamline = function(ir, log) {
is_int: [1, T_BOOL], is_text: [1, T_BOOL], is_num: [1, T_BOOL],
is_bool: [1, T_BOOL], is_null: [1, T_BOOL], is_identical: [1, T_BOOL],
is_array: [1, T_BOOL], is_func: [1, T_BOOL],
is_record: [1, T_BOOL], is_stone: [1, T_BOOL]
is_record: [1, T_BOOL], is_stone: [1, T_BOOL],
is_blob: [1, T_BOOL], is_data: [1, T_BOOL],
is_true: [1, T_BOOL], is_false: [1, T_BOOL], is_fit: [1, T_BOOL],
is_char: [1, T_BOOL], is_digit: [1, T_BOOL], is_letter: [1, T_BOOL],
is_lower: [1, T_BOOL], is_upper: [1, T_BOOL], is_ws: [1, T_BOOL],
is_actor: [1, T_BOOL]
}
// Known intrinsic return types for invoke result inference.
@@ -367,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) {
@@ -667,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
@@ -743,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
@@ -826,26 +904,32 @@ var streamline = function(ir, log) {
// Dynamic access reduction
if (op == "load_dynamic") {
old_op = op
if (slot_is(slot_types, instr[3], T_TEXT)) {
if (slot_is(slot_types, instr[2], T_RECORD) && slot_is(slot_types, instr[3], T_TEXT)) {
instr[0] = "load_field"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_field",
rule: "dynamic_record_to_field",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[2], object_type: slot_types[instr[2]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
} else if (slot_is(slot_types, instr[3], T_INT)) {
} else if (slot_is(slot_types, instr[2], T_ARRAY) && slot_is(slot_types, instr[3], T_INT)) {
instr[0] = "load_index"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_index",
rule: "dynamic_array_to_index",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[2], object_type: slot_types[instr[2]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
}
@@ -855,26 +939,32 @@ var streamline = function(ir, log) {
}
if (op == "store_dynamic") {
old_op = op
if (slot_is(slot_types, instr[3], T_TEXT)) {
if (slot_is(slot_types, instr[1], T_RECORD) && slot_is(slot_types, instr[3], T_TEXT)) {
instr[0] = "store_field"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_field",
rule: "dynamic_record_to_field",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[1], object_type: slot_types[instr[1]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
} else if (slot_is(slot_types, instr[3], T_INT)) {
} else if (slot_is(slot_types, instr[1], T_ARRAY) && slot_is(slot_types, instr[3], T_INT)) {
instr[0] = "store_index"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_index",
rule: "dynamic_array_to_index",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[1], object_type: slot_types[instr[1]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
}
@@ -882,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
}
@@ -1039,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",
@@ -1056,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",
@@ -1073,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
@@ -1160,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
@@ -1342,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)) {
@@ -1549,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
@@ -1654,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]
}
@@ -1684,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: []
}
@@ -1697,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: []
}
@@ -1834,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)) {
@@ -2555,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
// =========================================================
@@ -2657,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
@@ -2675,7 +3212,60 @@ var streamline = function(ir, log) {
}
}
// Compress slots across all functions (must run after per-function passes)
// Phase 2: Inline pass
var changed_main = false
var changed_fns = null
if (ir.main != null) {
changed_main = inline_calls(ir.main, ir, log)
}
if (ir.functions != null) {
changed_fns = array(length(ir.functions), false)
fi = 0
while (fi < length(ir.functions)) {
changed_fns[fi] = inline_calls(ir.functions[fi], ir, log)
fi = fi + 1
}
}
// Phase 3: Re-optimize inlined functions
if (changed_main) {
optimize_function(ir.main, log)
insert_stone_text(ir.main, log)
}
if (ir.functions != null) {
fi = 0
while (fi < length(ir.functions)) {
if (changed_fns != null && changed_fns[fi]) {
optimize_function(ir.functions[fi], log)
insert_stone_text(ir.functions[fi], log)
}
fi = fi + 1
}
}
// Phase 4: Cascade — second inline round (callbacks inside inlined bodies)
if (changed_main) {
changed_main = inline_calls(ir.main, ir, log)
if (changed_main) {
optimize_function(ir.main, log)
insert_stone_text(ir.main, log)
}
}
if (ir.functions != null) {
fi = 0
while (fi < length(ir.functions)) {
if (changed_fns != null && changed_fns[fi]) {
changed_fns[fi] = inline_calls(ir.functions[fi], ir, log)
if (changed_fns[fi]) {
optimize_function(ir.functions[fi], log)
insert_stone_text(ir.functions[fi], log)
}
}
fi = fi + 1
}
}
// Phase 5: Compress slots across all functions (must run after per-function passes)
compress_slots(ir)
// Expose DEF/USE functions via log if requested

View File

@@ -981,6 +981,99 @@ run("is_proto", function() {
if (!is_proto(b, a)) fail("is_proto failed on meme")
})
run("is_data", function() {
if (!is_data({})) fail("is_data {} should be true")
if (is_data([])) fail("is_data [] should be false")
if (is_data(42)) fail("is_data number should be false")
if (is_data("hello")) fail("is_data string should be false")
if (is_data(null)) fail("is_data null should be false")
if (is_data(true)) fail("is_data bool should be false")
if (is_data(function(){})) fail("is_data function should be false")
})
run("is_true", function() {
if (!is_true(true)) fail("is_true true should be true")
if (is_true(false)) fail("is_true false should be false")
if (is_true(1)) fail("is_true 1 should be false")
if (is_true("true")) fail("is_true string should be false")
if (is_true(null)) fail("is_true null should be false")
})
run("is_false", function() {
if (!is_false(false)) fail("is_false false should be true")
if (is_false(true)) fail("is_false true should be false")
if (is_false(0)) fail("is_false 0 should be false")
if (is_false("")) fail("is_false empty string should be false")
if (is_false(null)) fail("is_false null should be false")
})
run("is_fit", function() {
if (!is_fit(0)) fail("is_fit 0 should be true")
if (!is_fit(42)) fail("is_fit 42 should be true")
if (!is_fit(-100)) fail("is_fit -100 should be true")
if (!is_fit(3.0)) fail("is_fit 3.0 should be true")
if (is_fit(3.5)) fail("is_fit 3.5 should be false")
if (is_fit("42")) fail("is_fit string should be false")
if (is_fit(null)) fail("is_fit null should be false")
})
run("is_character", function() {
if (!is_character("a")) fail("is_character a should be true")
if (!is_character("Z")) fail("is_character Z should be true")
if (!is_character("5")) fail("is_character 5 should be true")
if (!is_character(" ")) fail("is_character space should be true")
if (is_character("ab")) fail("is_character ab should be false")
if (is_character("")) fail("is_character empty should be false")
if (is_character(42)) fail("is_character number should be false")
if (is_character(null)) fail("is_character null should be false")
})
run("is_digit", function() {
if (!is_digit("0")) fail("is_digit 0 should be true")
if (!is_digit("5")) fail("is_digit 5 should be true")
if (!is_digit("9")) fail("is_digit 9 should be true")
if (is_digit("a")) fail("is_digit a should be false")
if (is_digit("55")) fail("is_digit 55 should be false")
if (is_digit(5)) fail("is_digit number should be false")
if (is_digit(null)) fail("is_digit null should be false")
})
run("is_letter", function() {
if (!is_letter("a")) fail("is_letter a should be true")
if (!is_letter("Z")) fail("is_letter Z should be true")
if (is_letter("5")) fail("is_letter 5 should be false")
if (is_letter("ab")) fail("is_letter ab should be false")
if (is_letter(42)) fail("is_letter number should be false")
})
run("is_lower", function() {
if (!is_lower("a")) fail("is_lower a should be true")
if (!is_lower("z")) fail("is_lower z should be true")
if (is_lower("A")) fail("is_lower A should be false")
if (is_lower("5")) fail("is_lower 5 should be false")
if (is_lower("ab")) fail("is_lower ab should be false")
if (is_lower(42)) fail("is_lower number should be false")
})
run("is_upper", function() {
if (!is_upper("A")) fail("is_upper A should be true")
if (!is_upper("Z")) fail("is_upper Z should be true")
if (is_upper("a")) fail("is_upper a should be false")
if (is_upper("5")) fail("is_upper 5 should be false")
if (is_upper("AB")) fail("is_upper AB should be false")
if (is_upper(42)) fail("is_upper number should be false")
})
run("is_whitespace", function() {
if (!is_whitespace(" ")) fail("is_whitespace space should be true")
if (!is_whitespace("\t")) fail("is_whitespace tab should be true")
if (!is_whitespace("\n")) fail("is_whitespace newline should be true")
if (is_whitespace("a")) fail("is_whitespace a should be false")
if (is_whitespace(" ")) fail("is_whitespace two spaces should be false")
if (is_whitespace(42)) fail("is_whitespace number should be false")
if (is_whitespace(null)) fail("is_whitespace null should be false")
})
// ============================================================================
// GLOBAL FUNCTIONS - LENGTH
// ============================================================================
@@ -3409,6 +3502,166 @@ run("array map with exit", function() {
if (length(result) != 5) fail("array map with exit length unexpected")
})
run("inline map intrinsic is_data", function() {
var items = [{}, [], "hello", 42, true]
var result = array(items, is_data)
if (result[0] != true) fail("is_data {} should be true")
if (result[1] != false) fail("is_data [] should be false")
if (result[2] != false) fail("is_data string should be false")
if (result[3] != false) fail("is_data number should be false")
if (result[4] != false) fail("is_data bool should be false")
if (length(result) != 5) fail("result length should be 5")
})
run("inline map intrinsic is_number", function() {
var items = [1, "two", 3.14, null, true]
var result = array(items, is_number)
if (result[0] != true) fail("1 should be number")
if (result[1] != false) fail("'two' should not be number")
if (result[2] != true) fail("3.14 should be number")
if (result[3] != false) fail("null should not be number")
if (result[4] != false) fail("true should not be number")
})
run("inline map intrinsic is_text", function() {
var items = ["hello", 42, "", null]
var result = array(items, is_text)
if (result[0] != true) fail("'hello' should be text")
if (result[1] != false) fail("42 should not be text")
if (result[2] != true) fail("'' should be text")
if (result[3] != false) fail("null should not be text")
})
run("inline map intrinsic is_digit", function() {
var chars = array("a5B2 ")
var result = array(chars, is_digit)
if (result[0] != false) fail("a should not be digit")
if (result[1] != true) fail("5 should be digit")
if (result[2] != false) fail("B should not be digit")
if (result[3] != true) fail("2 should be digit")
if (result[4] != false) fail("space should not be digit")
})
run("inline map lambda", function() {
var arr = [10, 20, 30]
var result = array(arr, function(x) { return x + 1 })
if (result[0] != 11) fail("10+1 should be 11")
if (result[1] != 21) fail("20+1 should be 21")
if (result[2] != 31) fail("30+1 should be 31")
})
run("inline map empty array", function() {
var result = array([], is_number)
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
// ============================================================================
@@ -5729,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
// ============================================================================