Merge remote-tracking branch 'origin/master'
This commit is contained in:
86
bench_arith.ce
Normal file
86
bench_arith.ce
Normal 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
67
bench_arith.js
Normal 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
68
bench_arith.lua
Normal 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
113
bench_array.ce
Normal 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
93
bench_array.js
Normal 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
93
bench_array.lua
Normal 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
21
bench_fib.ce
Normal 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
118
bench_object.ce
Normal 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
99
bench_object.js
Normal 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
101
bench_object.lua
Normal 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
302
benches/micro_core.cm
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -95,22 +95,9 @@
|
||||
"nr_close_slots": 0,
|
||||
"instructions": [
|
||||
["move", 2, 1, 14, 14],
|
||||
[
|
||||
"access",
|
||||
3,
|
||||
{
|
||||
"name": "is_blob",
|
||||
"kind": "name",
|
||||
"make": "intrinsic"
|
||||
},
|
||||
15,
|
||||
8
|
||||
],
|
||||
["frame", 4, 3, 1, 15, 8],
|
||||
["setarg", 4, 1, 1, 15, 8],
|
||||
["invoke", 4, 3, 15, 8],
|
||||
["is_blob", 3, 1, 15, 16],
|
||||
"_nop_bl_1",
|
||||
["jump_true", 3, "if_else_6", 15, 8],
|
||||
["jump_true", 3, "if_else_6", 15, 16],
|
||||
[
|
||||
"access",
|
||||
3,
|
||||
@@ -199,7 +186,7 @@
|
||||
"_nop_ur_1",
|
||||
"_nop_ur_2"
|
||||
],
|
||||
"_write_types": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "null", "text", "array", null, null, null, "text", null, null, null, null],
|
||||
"_write_types": [null, null, null, "bool", null, null, null, null, null, null, null, null, null, null, null, null, null, "null", "text", "array", null, null, null, "text", null, null, null, null],
|
||||
"name": "content_hash",
|
||||
"filename": ".cell/packages/core/internal/bootstrap.cm",
|
||||
"nr_args": 1
|
||||
@@ -222,7 +209,7 @@
|
||||
8
|
||||
],
|
||||
"_nop_bl_1",
|
||||
["jump_true", 2, "if_else_10", 20, 8],
|
||||
["wary_true", 2, "if_else_10", 20, 8],
|
||||
["null", 2, 20, 26],
|
||||
["return", 2, 20, 26],
|
||||
"_nop_ur_1",
|
||||
@@ -345,7 +332,7 @@
|
||||
8
|
||||
],
|
||||
"_nop_bl_1",
|
||||
["jump_true", 1, "if_else_18", 25, 8],
|
||||
["wary_true", 1, "if_else_18", 25, 8],
|
||||
["null", 1, 25, 26],
|
||||
["return", 1, 25, 26],
|
||||
"_nop_ur_1",
|
||||
@@ -427,7 +414,7 @@
|
||||
["invoke", 5, 3, 27, 8],
|
||||
"call_done_26",
|
||||
"_nop_bl_2",
|
||||
["jump_true", 3, "if_else_23", 27, 8],
|
||||
["wary_true", 3, "if_else_23", 27, 8],
|
||||
["get", 2, 11, 1, 27, 24],
|
||||
["is_proxy", 3, 2, 27, 24],
|
||||
["jump_false", 3, "record_path_27", 27, 24],
|
||||
@@ -628,7 +615,7 @@
|
||||
["invoke", 8, 6, 36, 8],
|
||||
"call_done_41",
|
||||
"_nop_bl_1",
|
||||
["jump_true", 6, "if_else_38", 36, 8],
|
||||
["wary_true", 6, "if_else_38", 36, 8],
|
||||
["access", 5, "error: missing seed: ", 37, 14],
|
||||
"_nop_tc_7",
|
||||
"_nop_tc_8",
|
||||
@@ -1026,30 +1013,11 @@
|
||||
"call_done_63",
|
||||
"if_end_58",
|
||||
["access", 3, 1, 66, 17],
|
||||
"_nop_tc_1",
|
||||
"_nop_tc_2",
|
||||
"_nop_tc_3",
|
||||
"_nop_tc_4",
|
||||
["add", 5, 5, 3, 66, 17],
|
||||
["jump", "num_done_65", 66, 17],
|
||||
"num_err_64",
|
||||
"_nop_ucfg_1",
|
||||
"_nop_ucfg_2",
|
||||
"_nop_ucfg_3",
|
||||
"_nop_ucfg_4",
|
||||
"_nop_ucfg_5",
|
||||
"_nop_ucfg_6",
|
||||
"_nop_ucfg_7",
|
||||
"_nop_ucfg_8",
|
||||
"_nop_ucfg_9",
|
||||
"_nop_ucfg_10",
|
||||
"_nop_ucfg_11",
|
||||
"_nop_ucfg_12",
|
||||
"num_done_65",
|
||||
["jump", "while_start_55", 66, 17],
|
||||
"while_end_56",
|
||||
["disrupt", 68, 5],
|
||||
"_nop_ucfg_13",
|
||||
"_nop_ucfg_1",
|
||||
"if_else_53",
|
||||
"if_end_54",
|
||||
["get", 3, 15, 1, 70, 10],
|
||||
@@ -1060,7 +1028,7 @@
|
||||
"_nop_ur_1",
|
||||
"_nop_ur_2"
|
||||
],
|
||||
"_write_types": [null, null, null, "int", null, null, "bool", null, null, null, null, null, null, null, null, null, null, null, "null", "bool", "bool", null, "int", "int", "bool", null, "int", "bool", null, null, null, null, "null", "bool", "bool", null, "null", "bool", null, null, null, null, null, null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "int", null, null, null, null, null, null, null, null, null, null, null, null, null],
|
||||
"_write_types": [null, null, null, "int", null, null, "bool", null, null, null, null, null, null, null, null, null, null, null, "null", "bool", "bool", null, "int", "int", "bool", null, "int", "bool", null, null, null, null, "null", "bool", "bool", null, "null", "bool", null, null, null, null, null, null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "array", null, "text", null, null, null, null, null, "null", "text", "array", null, null, null, "int", null, null, null, null],
|
||||
"name": "analyze",
|
||||
"filename": ".cell/packages/core/internal/bootstrap.cm",
|
||||
"nr_args": 2
|
||||
@@ -1078,7 +1046,7 @@
|
||||
"instructions": [
|
||||
["get", 3, 11, 1, 74, 21],
|
||||
["is_proxy", 4, 3, 74, 21],
|
||||
["jump_false", 4, "record_path_66", 74, 21],
|
||||
["jump_false", 4, "record_path_64", 74, 21],
|
||||
["null", 4, 74, 21],
|
||||
["access", 5, "slurp", 74, 21],
|
||||
["array", 6, 0, 74, 21],
|
||||
@@ -1089,14 +1057,14 @@
|
||||
["setarg", 7, 1, 5, 74, 21],
|
||||
["setarg", 7, 2, 6, 74, 21],
|
||||
["invoke", 7, 4, 74, 21],
|
||||
["jump", "call_done_67", 74, 21],
|
||||
"record_path_66",
|
||||
["jump", "call_done_65", 74, 21],
|
||||
"record_path_64",
|
||||
["load_field", 5, 3, "slurp", 74, 21],
|
||||
["frame", 6, 5, 1, 74, 21],
|
||||
["setarg", 6, 0, 3, 74, 21],
|
||||
["setarg", 6, 1, 2, 74, 21],
|
||||
["invoke", 6, 4, 74, 21],
|
||||
"call_done_67",
|
||||
"call_done_65",
|
||||
["move", 3, 4, 74, 21],
|
||||
["get", 5, 4, 1, 75, 14],
|
||||
["frame", 6, 5, 1, 75, 14],
|
||||
@@ -1113,10 +1081,10 @@
|
||||
["null", 8, 79, 20],
|
||||
["null", 9, 80, 19],
|
||||
["move", 10, 4, 81, 7],
|
||||
["jump_false", 4, "and_end_70", 81, 7],
|
||||
["wary_false", 4, "and_end_68", 81, 7],
|
||||
["get", 4, 11, 1, 81, 17],
|
||||
["is_proxy", 11, 4, 81, 17],
|
||||
["jump_false", 11, "record_path_71", 81, 17],
|
||||
["jump_false", 11, "record_path_69", 81, 17],
|
||||
["null", 11, 81, 17],
|
||||
["access", 12, "is_file", 81, 17],
|
||||
["array", 13, 0, 81, 17],
|
||||
@@ -1127,22 +1095,22 @@
|
||||
["setarg", 14, 1, 12, 81, 17],
|
||||
["setarg", 14, 2, 13, 81, 17],
|
||||
["invoke", 14, 11, 81, 17],
|
||||
["jump", "call_done_72", 81, 17],
|
||||
"record_path_71",
|
||||
["jump", "call_done_70", 81, 17],
|
||||
"record_path_69",
|
||||
["load_field", 12, 4, "is_file", 81, 17],
|
||||
["frame", 13, 12, 1, 81, 17],
|
||||
["setarg", 13, 0, 4, 81, 17],
|
||||
["setarg", 13, 1, 5, 81, 17],
|
||||
["invoke", 13, 11, 81, 17],
|
||||
"call_done_72",
|
||||
"call_done_70",
|
||||
["move", 10, 11, 81, 17],
|
||||
"and_end_70",
|
||||
["jump_false", 10, "if_else_68", 81, 17],
|
||||
"and_end_68",
|
||||
["wary_false", 10, "if_else_66", 81, 17],
|
||||
["null", 4, 81, 37],
|
||||
["return", 4, 81, 37],
|
||||
"_nop_ur_1",
|
||||
"if_else_68",
|
||||
"if_end_69",
|
||||
"if_else_66",
|
||||
"if_end_67",
|
||||
[
|
||||
"access",
|
||||
4,
|
||||
@@ -1174,7 +1142,7 @@
|
||||
["move", 7, 3, 83, 14],
|
||||
["get", 3, 12, 1, 84, 16],
|
||||
["is_proxy", 4, 3, 84, 16],
|
||||
["jump_false", 4, "record_path_73", 84, 16],
|
||||
["jump_false", 4, "record_path_71", 84, 16],
|
||||
["null", 4, 84, 16],
|
||||
["access", 6, "encode", 84, 16],
|
||||
["array", 10, 0, 84, 16],
|
||||
@@ -1185,14 +1153,14 @@
|
||||
["setarg", 11, 1, 6, 84, 16],
|
||||
["setarg", 11, 2, 10, 84, 16],
|
||||
["invoke", 11, 4, 84, 16],
|
||||
["jump", "call_done_74", 84, 16],
|
||||
"record_path_73",
|
||||
["jump", "call_done_72", 84, 16],
|
||||
"record_path_71",
|
||||
["load_field", 6, 3, "encode", 84, 16],
|
||||
["frame", 10, 6, 1, 84, 16],
|
||||
["setarg", 10, 0, 3, 84, 16],
|
||||
["setarg", 10, 1, 7, 84, 16],
|
||||
["invoke", 10, 4, 84, 16],
|
||||
"call_done_74",
|
||||
"call_done_72",
|
||||
["move", 8, 4, 84, 16],
|
||||
[
|
||||
"access",
|
||||
@@ -1210,13 +1178,13 @@
|
||||
["setarg", 6, 2, 4, 85, 15],
|
||||
["invoke", 6, 3, 85, 15],
|
||||
["move", 9, 3, 85, 15],
|
||||
["jump_false", 5, "if_else_75", 86, 7],
|
||||
["wary_false", 5, "if_else_73", 86, 7],
|
||||
["get", 3, 6, 1, 87, 5],
|
||||
["frame", 4, 3, 0, 87, 5],
|
||||
["invoke", 4, 3, 87, 5],
|
||||
["get", 3, 11, 1, 88, 5],
|
||||
["is_proxy", 4, 3, 88, 5],
|
||||
["jump_false", 4, "record_path_77", 88, 5],
|
||||
["jump_false", 4, "record_path_75", 88, 5],
|
||||
["null", 4, 88, 5],
|
||||
["access", 6, "slurpwrite", 88, 5],
|
||||
["array", 7, 0, 88, 5],
|
||||
@@ -1228,18 +1196,18 @@
|
||||
["setarg", 8, 1, 6, 88, 5],
|
||||
["setarg", 8, 2, 7, 88, 5],
|
||||
["invoke", 8, 4, 88, 5],
|
||||
["jump", "call_done_78", 88, 5],
|
||||
"record_path_77",
|
||||
["jump", "call_done_76", 88, 5],
|
||||
"record_path_75",
|
||||
["load_field", 6, 3, "slurpwrite", 88, 5],
|
||||
["frame", 7, 6, 2, 88, 5],
|
||||
["setarg", 7, 0, 3, 88, 5],
|
||||
["setarg", 7, 1, 5, 88, 5],
|
||||
["setarg", 7, 2, 9, 88, 5],
|
||||
["invoke", 7, 4, 88, 5],
|
||||
"call_done_78",
|
||||
["jump", "if_end_76", 88, 5],
|
||||
"if_else_75",
|
||||
"if_end_76",
|
||||
"call_done_76",
|
||||
["jump", "if_end_74", 88, 5],
|
||||
"if_else_73",
|
||||
"if_end_74",
|
||||
["null", 3, 88, 5],
|
||||
["return", 3, 88, 5]
|
||||
],
|
||||
@@ -1369,10 +1337,10 @@
|
||||
["move", 1, 22, 99, 26],
|
||||
["access", 17, 0, 101, 10],
|
||||
["null", 18, 102, 13],
|
||||
"while_start_79",
|
||||
"while_start_77",
|
||||
["length", 19, 1, 103, 20],
|
||||
["lt", 20, 17, 19, 103, 20],
|
||||
["jump_false", 20, "while_end_80", 103, 20],
|
||||
["jump_false", 20, "while_end_78", 103, 20],
|
||||
["load_index", 19, 1, 17, 104, 22],
|
||||
["move", 18, 19, 104, 22],
|
||||
["load_field", 20, 19, "name", 105, 21],
|
||||
@@ -1389,19 +1357,19 @@
|
||||
],
|
||||
["access", 21, "/", 105, 45],
|
||||
["is_text", 22, 19, 105, 45],
|
||||
["jump_false", 22, "add_cn_82", 105, 45],
|
||||
["jump_false", 22, "add_cn_80", 105, 45],
|
||||
"_nop_tc_1",
|
||||
"_nop_tc_2",
|
||||
["concat", 23, 19, 21, 105, 45],
|
||||
["jump", "add_done_81", 105, 45],
|
||||
"add_cn_82",
|
||||
["jump", "add_done_79", 105, 45],
|
||||
"add_cn_80",
|
||||
["is_num", 22, 19, 105, 45],
|
||||
["jump_false", 22, "add_err_83", 105, 45],
|
||||
["jump_false", 22, "add_err_81", 105, 45],
|
||||
"_nop_tc_3",
|
||||
"_nop_dj_1",
|
||||
"_nop_ucfg_1",
|
||||
"_nop_ucfg_2",
|
||||
"add_err_83",
|
||||
"add_err_81",
|
||||
[
|
||||
"access",
|
||||
19,
|
||||
@@ -1426,22 +1394,22 @@
|
||||
["setarg", 22, 2, 24, 105, 45],
|
||||
["invoke", 22, 19, 105, 45],
|
||||
["disrupt", 105, 45],
|
||||
"add_done_81",
|
||||
"add_done_79",
|
||||
["load_field", 19, 18, "path", 105, 51],
|
||||
"_nop_tc_1",
|
||||
"_nop_tc_2",
|
||||
["is_text", 21, 19, 105, 51],
|
||||
["jump_false", 21, "add_cn_85", 105, 51],
|
||||
["jump_false", 21, "add_cn_83", 105, 51],
|
||||
["concat", 21, 23, 19, 105, 51],
|
||||
["jump", "add_done_84", 105, 51],
|
||||
"add_cn_85",
|
||||
["jump", "add_done_82", 105, 51],
|
||||
"add_cn_83",
|
||||
"_nop_tc_3",
|
||||
["jump", "add_err_86", 105, 51],
|
||||
["jump", "add_err_84", 105, 51],
|
||||
"_nop_ucfg_1",
|
||||
"_nop_ucfg_2",
|
||||
"_nop_ucfg_3",
|
||||
"_nop_ucfg_4",
|
||||
"add_err_86",
|
||||
"add_err_84",
|
||||
[
|
||||
"access",
|
||||
19,
|
||||
@@ -1466,35 +1434,16 @@
|
||||
["setarg", 23, 2, 24, 105, 51],
|
||||
["invoke", 23, 19, 105, 51],
|
||||
["disrupt", 105, 51],
|
||||
"add_done_84",
|
||||
"add_done_82",
|
||||
["frame", 19, 9, 2, 105, 3],
|
||||
["setarg", 19, 1, 20, 105, 3],
|
||||
["stone_text", 21],
|
||||
["setarg", 19, 2, 21, 105, 3],
|
||||
["invoke", 19, 20, 105, 3],
|
||||
["access", 19, 1, 106, 13],
|
||||
"_nop_tc_4",
|
||||
"_nop_tc_5",
|
||||
"_nop_tc_6",
|
||||
"_nop_tc_7",
|
||||
["add", 17, 17, 19, 106, 13],
|
||||
["jump", "num_done_88", 106, 13],
|
||||
"num_err_87",
|
||||
"_nop_ucfg_3",
|
||||
"_nop_ucfg_4",
|
||||
"_nop_ucfg_5",
|
||||
"_nop_ucfg_6",
|
||||
"_nop_ucfg_7",
|
||||
"_nop_ucfg_8",
|
||||
"_nop_ucfg_9",
|
||||
"_nop_ucfg_10",
|
||||
"_nop_ucfg_11",
|
||||
"_nop_ucfg_12",
|
||||
"_nop_ucfg_13",
|
||||
"_nop_ucfg_14",
|
||||
"num_done_88",
|
||||
["jump", "while_start_79", 106, 13],
|
||||
"while_end_80",
|
||||
["jump", "while_start_77", 106, 13],
|
||||
"while_end_78",
|
||||
["access", 1, "bootstrap: cache seeded\n", 108, 10],
|
||||
[
|
||||
"access",
|
||||
@@ -1508,7 +1457,7 @@
|
||||
1
|
||||
],
|
||||
["is_proxy", 17, 9, 108, 1],
|
||||
["jump_false", 17, "record_path_89", 108, 1],
|
||||
["jump_false", 17, "record_path_85", 108, 1],
|
||||
["null", 17, 108, 1],
|
||||
["access", 18, "print", 108, 1],
|
||||
["array", 19, 0, 108, 1],
|
||||
@@ -1520,18 +1469,18 @@
|
||||
["setarg", 20, 1, 18, 108, 1],
|
||||
["setarg", 20, 2, 19, 108, 1],
|
||||
["invoke", 20, 17, 108, 1],
|
||||
["jump", "call_done_90", 108, 1],
|
||||
"record_path_89",
|
||||
["jump", "call_done_86", 108, 1],
|
||||
"record_path_85",
|
||||
["load_field", 18, 9, "print", 108, 1],
|
||||
["frame", 19, 18, 1, 108, 1],
|
||||
["setarg", 19, 0, 9, 108, 1],
|
||||
["stone_text", 1],
|
||||
["setarg", 19, 1, 1, 108, 1],
|
||||
["invoke", 19, 17, 108, 1],
|
||||
"call_done_90",
|
||||
"call_done_86",
|
||||
["return", 17, 108, 1]
|
||||
],
|
||||
"_write_types": [null, "function", "function", "function", null, "function", null, null, null, null, null, null, null, null, "function", "int", "function", "function", null, "array", "function", "function", "function", "function", "function", "function", "function", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", null, null, null, null, null, null, null, null, null, "text", null, null, null, "null", "text", "array", null, null, null],
|
||||
"_write_types": [null, "function", "function", "function", null, "function", null, null, null, null, null, null, null, null, "function", "int", "function", "function", null, "array", "function", "function", "function", "function", "function", "function", "function", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", "text", null, null, null, "null", "text", "array", null, null, null],
|
||||
"nr_args": 0
|
||||
},
|
||||
"name": ".cell/packages/core/internal/bootstrap.cm",
|
||||
|
||||
4786
boot/fold.cm.mcode
4786
boot/fold.cm.mcode
File diff suppressed because one or more lines are too long
25586
boot/mcode.cm.mcode
25586
boot/mcode.cm.mcode
File diff suppressed because one or more lines are too long
9732
boot/parse.cm.mcode
9732
boot/parse.cm.mcode
File diff suppressed because one or more lines are too long
27739
boot/streamline.cm.mcode
27739
boot/streamline.cm.mcode
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
)
|
||||
|
||||
|
||||
@@ -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
452
mcode.cm
@@ -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)) {
|
||||
|
||||
2086
qbe_emit.cm
2086
qbe_emit.cm
File diff suppressed because it is too large
Load Diff
350
source/mach.c
350
source/mach.c
@@ -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));
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
104
source/runtime.c
104
source/runtime.c
@@ -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);
|
||||
|
||||
648
streamline.cm
648
streamline.cm
@@ -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
|
||||
|
||||
434
vm_suite.ce
434
vm_suite.ce
@@ -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
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user