Merge branch 'fix_gc' into pitweb

This commit is contained in:
2026-02-15 10:04:54 -06:00
52 changed files with 709697 additions and 135532 deletions

View File

@@ -107,6 +107,7 @@ var v = a[] // pop: v is 3, a is [1, 2]
- Core is the `core` package — its symbols follow the same `js_core_<name>_use` pattern as all other packages - Core is the `core` package — its symbols follow the same `js_core_<name>_use` pattern as all other packages
- Package directories should contain only source files (no `.mach`/`.mcode` alongside source) - Package directories should contain only source files (no `.mach`/`.mcode` alongside source)
- Build cache files in `build/` are bare hashes (no extensions) - Build cache files in `build/` are bare hashes (no extensions)
- Use `JS_FRAME`/`JS_ROOT`/`JS_RETURN` macros for any C function that allocates multiple heap objects. Any `JS_New*`/`JS_SetProperty*` call can trigger GC.
## Project Layout ## Project Layout

View File

@@ -381,19 +381,21 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JSValue js_core_miniz_use(JSContext *js) JSValue js_core_miniz_use(JSContext *js)
{ {
JS_FRAME(js);
JS_NewClassID(&js_reader_class_id); JS_NewClassID(&js_reader_class_id);
JS_NewClass(js, js_reader_class_id, &js_reader_class); JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js); JS_ROOT(reader_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, reader_proto.val, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto); JS_SetClassProto(js, js_reader_class_id, reader_proto.val);
JS_NewClassID(&js_writer_class_id); JS_NewClassID(&js_writer_class_id);
JS_NewClass(js, js_writer_class_id, &js_writer_class); JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js); JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, writer_proto.val, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto); JS_SetClassProto(js, js_writer_class_id, writer_proto.val);
JSValue export = JS_NewObject(js); JS_ROOT(export, JS_NewObject(js));
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, export.val, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
return export; JS_RETURN(export.val);
} }

183
bench_native.ce Normal file
View File

@@ -0,0 +1,183 @@
// bench_native.ce — compare VM vs native execution speed
//
// Usage:
// cell --dev bench_native.ce <module.cm> [iterations]
//
// Compiles (if needed) and benchmarks a module via both VM and native dylib.
// Reports median/mean timing per benchmark + speedup ratio.
var os = use('os')
var fd = use('fd')
if (length(args) < 1) {
print('usage: cell --dev bench_native.ce <module.cm> [iterations]')
return
}
var file = args[0]
var name = file
if (ends_with(name, '.cm')) {
name = text(name, 0, length(name) - 3)
}
var iterations = 11
if (length(args) > 1) {
iterations = number(args[1])
}
def WARMUP = 3
var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var dylib_path = './' + file + '.dylib'
// --- Statistics ---
var stat_sort = function(arr) {
return sort(arr)
}
var stat_median = function(arr) {
if (length(arr) == 0) return 0
var sorted = stat_sort(arr)
var mid = floor(length(arr) / 2)
if (length(arr) % 2 == 0) {
return (sorted[mid - 1] + sorted[mid]) / 2
}
return sorted[mid]
}
var stat_mean = function(arr) {
if (length(arr) == 0) return 0
var sum = reduce(arr, function(a, b) { return a + b })
return sum / length(arr)
}
var format_ns = function(ns) {
if (ns < 1000) return text(round(ns)) + 'ns'
if (ns < 1000000) return text(round(ns / 1000 * 100) / 100) + 'us'
if (ns < 1000000000) return text(round(ns / 1000000 * 100) / 100) + 'ms'
return text(round(ns / 1000000000 * 100) / 100) + 's'
}
// --- Collect benchmarks from module ---
var collect_benches = function(mod) {
var benches = []
if (is_function(mod)) {
push(benches, {name: 'main', fn: mod})
} else if (is_object(mod)) {
var keys = array(mod)
var i = 0
while (i < length(keys)) {
var k = keys[i]
if (is_function(mod[k])) {
push(benches, {name: k, fn: mod[k]})
}
i = i + 1
}
}
return benches
}
// --- Run one benchmark function ---
var run_bench = function(fn, label) {
var samples = []
var i = 0
var t1 = 0
var t2 = 0
// warmup
i = 0
while (i < WARMUP) {
fn(1)
i = i + 1
}
// collect samples
i = 0
while (i < iterations) {
t1 = os.now()
fn(1)
t2 = os.now()
push(samples, t2 - t1)
i = i + 1
}
return {
label: label,
median: stat_median(samples),
mean: stat_mean(samples)
}
}
// --- Load VM module ---
print('loading VM module: ' + file)
var vm_mod = use(name)
var vm_benches = collect_benches(vm_mod)
if (length(vm_benches) == 0) {
print('no benchmarkable functions found in ' + file)
return
}
// --- Load native module ---
var native_mod = null
var native_benches = []
var has_native = fd.is_file(dylib_path)
if (has_native) {
print('loading native module: ' + dylib_path)
var lib = os.dylib_open(dylib_path)
native_mod = os.dylib_symbol(lib, symbol)
native_benches = collect_benches(native_mod)
} else {
print('no ' + dylib_path + ' found -- VM-only benchmarking')
print(' hint: cell --dev compile.ce ' + file)
}
// --- Run benchmarks ---
print('')
print('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')')
print('')
var pad = function(s, n) {
while (length(s) < n) s = s + ' '
return s
}
var i = 0
while (i < length(vm_benches)) {
var b = vm_benches[i]
var vm_result = run_bench(b.fn, 'vm')
print(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)')
// find matching native bench
var j = 0
var found = false
while (j < length(native_benches)) {
if (native_benches[j].name == b.name) {
var nat_result = run_bench(native_benches[j].fn, 'native')
print(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)')
if (nat_result.median > 0) {
var speedup = vm_result.median / nat_result.median
print(pad('', 20) + ' speedup: ' + text(round(speedup * 100) / 100) + 'x')
}
found = true
}
j = j + 1
}
if (has_native && !found) {
print(pad('', 20) + ' NT: (no matching function)')
}
print('')
i = i + 1
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
// compile.ce — compile a .cm module to native .dylib via QBE // compile.ce — compile a .cm module to native .dylib via QBE
// //
// Usage: // Usage:
// cell --core . compile.ce <file.cm> // cell --dev compile.ce <file.cm>
// //
// Produces <file>.dylib in the current directory. // Produces <file>.dylib in the current directory.
@@ -9,7 +9,7 @@ var fd = use('fd')
var os = use('os') var os = use('os')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --core . compile.ce <file.cm>') print('usage: cell --dev compile.ce <file.cm>')
return return
} }
@@ -26,39 +26,22 @@ var ssa_path = tmp + '.ssa'
var s_path = tmp + '.s' var s_path = tmp + '.s'
var o_path = tmp + '.o' var o_path = tmp + '.o'
var rt_o_path = '/tmp/qbe_rt.o' var rt_o_path = '/tmp/qbe_rt.o'
var dylib_path = base + '.dylib' var dylib_path = file + '.dylib'
var cwd = fd.getcwd() var cwd = fd.getcwd()
var rc = 0 var rc = 0
// Step 1: emit QBE IL // Step 1: emit QBE IL
print('emit qbe...') print('emit qbe...')
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path) rc = os.system('cd ' + cwd + ' && ./cell --dev qbe.ce ' + file + ' > ' + ssa_path)
if (rc != 0) { if (rc != 0) {
print('failed to emit qbe il') print('failed to emit qbe il')
return return
} }
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper // Step 2: append wrapper function — called as symbol(ctx) by os.dylib_symbol.
// Use awk via shell to avoid blob/slurpwrite issues with long strings
print('post-process...')
var awk_cmd = `awk '
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
print "@_dead_" dead_id; dead_id++; need_label=0
}
/^@/ || /^}/ || NF==0 { need_label=0 }
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
{ print }
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
rc = os.system(awk_cmd)
if (rc != 0) {
print('post-process failed')
return
}
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
// Delegates to cell_rt_module_entry which heap-allocates a frame // Delegates to cell_rt_module_entry which heap-allocates a frame
// (so closures survive) and calls cell_main. // (so closures survive) and calls cell_main.
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa` var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + ssa_path
rc = os.system(wrapper_cmd) rc = os.system(wrapper_cmd)
if (rc != 0) { if (rc != 0) {
print('wrapper append failed') print('wrapper append failed')
@@ -67,7 +50,7 @@ if (rc != 0) {
// Step 3: compile QBE IL to assembly // Step 3: compile QBE IL to assembly
print('qbe compile...') print('qbe compile...')
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa') rc = os.system('qbe -o ' + s_path + ' ' + ssa_path)
if (rc != 0) { if (rc != 0) {
print('qbe compilation failed') print('qbe compilation failed')
return return

98
compile_seed.ce Normal file
View File

@@ -0,0 +1,98 @@
// compile_seed.ce — compile a .cm module to native .dylib via QBE (seed mode)
// Usage: ./cell --dev --seed compile_seed <file.cm>
var fd = use("fd")
var os = use("os")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var qbe_macros = use("qbe")
var qbe_emit = use("qbe_emit")
if (length(args) < 1) {
print("usage: cell --dev --seed compile_seed <file.cm>")
disrupt
}
var file = args[0]
var base = file
if (ends_with(base, ".cm")) {
base = text(base, 0, length(base) - 3)
} else if (ends_with(base, ".ce")) {
base = text(base, 0, length(base) - 3)
}
var safe = replace(replace(replace(base, "/", "_"), "-", "_"), ".", "_")
var symbol = "js_" + safe + "_use"
var tmp = "/tmp/qbe_" + safe
var ssa_path = tmp + ".ssa"
var s_path = tmp + ".s"
var o_path = tmp + ".o"
var rt_o_path = "/tmp/qbe_rt.o"
var dylib_path = file + ".dylib"
var rc = 0
// Step 1: compile to QBE IL
print("compiling " + file + " to QBE IL...")
var src = text(fd.slurp(file))
var result = tokenize(src, file)
var ast = parse(result.tokens, src, file, tokenize)
var folded = fold(ast)
var compiled = mcode(folded)
var optimized = streamline(compiled)
var il = qbe_emit(optimized, qbe_macros)
// Step 2: append wrapper function
var wrapper = `
export function l $${symbol}(l %ctx) {
@entry
%result =l call $cell_rt_module_entry(l %ctx)
ret %result
}
`
il = il + wrapper
// Write IL to file — remove old file first to avoid leftover content
if (fd.is_file(ssa_path)) fd.unlink(ssa_path)
var out_fd = fd.open(ssa_path, 1537, 420)
fd.write(out_fd, il)
fd.close(out_fd)
print("wrote " + ssa_path + " (" + text(length(il)) + " bytes)")
// Step 3: compile QBE IL to assembly
print("qbe compile...")
rc = os.system("qbe -o " + s_path + " " + ssa_path)
if (rc != 0) {
print("qbe compilation failed")
disrupt
}
// Step 4: assemble
print("assemble...")
rc = os.system("cc -c " + s_path + " -o " + o_path)
if (rc != 0) {
print("assembly failed")
disrupt
}
// Step 5: compile runtime stubs
if (!fd.is_file(rt_o_path)) {
print("compile runtime stubs...")
rc = os.system("cc -c source/qbe_helpers.c -o " + rt_o_path + " -fPIC -Isource")
if (rc != 0) {
print("runtime stubs compilation failed")
disrupt
}
}
// Step 6: link dylib
print("link...")
rc = os.system("cc -shared -fPIC -undefined dynamic_lookup " + o_path + " " + rt_o_path + " -o " + dylib_path)
if (rc != 0) {
print("linking failed")
disrupt
}
print("built: " + dylib_path)

View File

@@ -240,7 +240,8 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
JSValue js_core_crypto_use(JSContext *js) JSValue js_core_crypto_use(JSContext *js)
{ {
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0])); JS_ROOT(mod, JS_NewObject(js));
return obj; JS_SetPropertyFunctionList(js, mod.val, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
JS_RETURN(mod.val);
} }

View File

@@ -22,7 +22,8 @@ static const JSCFunctionListEntry js_debug_funcs[] = {
}; };
JSValue js_core_debug_use(JSContext *js) { JSValue js_core_debug_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_debug_funcs, countof(js_debug_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -21,7 +21,8 @@ static const JSCFunctionListEntry js_js_funcs[] = {
}; };
JSValue js_core_js_use(JSContext *js) { JSValue js_core_js_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_js_funcs, countof(js_js_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -13,6 +13,34 @@ Source → Tokenize → Parse → Fold → Mcode → Streamline → Machine
Mcode is produced by `mcode.cm`, optimized by `streamline.cm`, then either serialized to 32-bit bytecode for the Mach VM (`mach.c`), or lowered to QBE/LLVM IL for native compilation (`qbe_emit.cm`). See [Compilation Pipeline](pipeline.md) for the full overview. Mcode is produced by `mcode.cm`, optimized by `streamline.cm`, then either serialized to 32-bit bytecode for the Mach VM (`mach.c`), or lowered to QBE/LLVM IL for native compilation (`qbe_emit.cm`). See [Compilation Pipeline](pipeline.md) for the full overview.
## Module Structure
An `.mcode` file is a JSON object representing a compiled module:
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Module name (typically the source filename) |
| `filename` | string | Source filename |
| `data` | object | Constant pool — string and number literals used by instructions |
| `main` | function | The top-level function (module body) |
| `functions` | array | Nested function definitions (referenced by `function dest, id`) |
### Function Record
Each function (both `main` and entries in `functions`) has:
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Function name (`"<anonymous>"` for lambdas) |
| `filename` | string | Source filename |
| `nr_args` | integer | Number of parameters |
| `nr_slots` | integer | Total register slots needed (args + locals + temporaries) |
| `nr_close_slots` | integer | Number of closure slots captured from parent scope |
| `disruption_pc` | integer | Instruction index of the disruption handler (0 if none) |
| `instructions` | array | Instruction arrays and label strings |
Slot 0 is reserved. Slots 1 through `nr_args` hold parameters. Remaining slots up to `nr_slots - 1` are locals and temporaries.
## Instruction Format ## Instruction Format
Each instruction is a JSON array. The first element is the instruction name (string), followed by operands. The last two elements are line and column numbers for source mapping: Each instruction is a JSON array. The first element is the instruction name (string), followed by operands. The last two elements are line and column numbers for source mapping:

7
fit.c
View File

@@ -250,7 +250,8 @@ static const JSCFunctionListEntry js_fit_funcs[] = {
JSValue js_core_fit_use(JSContext *js) JSValue js_core_fit_use(JSContext *js)
{ {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_fit_funcs, countof(js_fit_funcs));
JS_RETURN(mod.val);
} }

59
fold.cm
View File

@@ -5,6 +5,34 @@ var fold = function(ast) {
var scopes = ast.scopes var scopes = ast.scopes
var nr_scopes = length(scopes) var nr_scopes = length(scopes)
var type_tag_map = {
array: "array", record: "record", text: "text",
number: "number", blob: "blob"
}
var binary_ops = {
"+": true, "-": true, "*": true, "/": true, "%": true,
"**": true, "==": true, "!=": true, "<": true, ">": true,
"<=": true, ">=": true, "&": true, "|": true, "^": true,
"<<": true, ">>": true, ">>>": true, "&&": true, "||": true,
",": true, in: true
}
var unary_ops = {
"!": true, "~": true, "-unary": true, "+unary": true, delete: true
}
var assign_ops = {
assign: true, "+=": true, "-=": true, "*=": true,
"/=": true, "%=": true, "<<=": true, ">>=": true,
">>>=": true, "&=": true, "^=": true, "|=": true,
"**=": true, "&&=": true, "||=": true
}
var arith_ops = {
"+": true, "-": true, "*": true, "/": true, "%": true, "**": true
}
var comparison_ops = {
"==": true, "!=": true, "<": true, ">": true, "<=": true, ">=": true
}
// ============================================================ // ============================================================
// Helpers // Helpers
// ============================================================ // ============================================================
@@ -194,11 +222,7 @@ var fold = function(ast) {
if (rhs_target != null && rhs_target.intrinsic == true) { if (rhs_target != null && rhs_target.intrinsic == true) {
sv = scope_var(fn_nr, name) sv = scope_var(fn_nr, name)
if (sv != null && sv.type_tag == null) { if (sv != null && sv.type_tag == null) {
if (rhs_target.name == "array") sv.type_tag = "array" if (type_tag_map[rhs_target.name] != null) sv.type_tag = type_tag_map[rhs_target.name]
else if (rhs_target.name == "record") sv.type_tag = "record"
else if (rhs_target.name == "text") sv.type_tag = "text"
else if (rhs_target.name == "number") sv.type_tag = "number"
else if (rhs_target.name == "blob") sv.type_tag = "blob"
} }
} }
} }
@@ -357,17 +381,13 @@ var fold = function(ast) {
var arg = null var arg = null
// Recurse into children first (bottom-up) // Recurse into children first (bottom-up)
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || if (binary_ops[k] == true) {
k == "**" || k == "==" || k == "!=" || k == "<" || k == ">" ||
k == "<=" || k == ">=" || k == "&" || k == "|" || k == "^" ||
k == "<<" || k == ">>" || k == ">>>" || k == "&&" || k == "||" ||
k == "," || k == "in") {
expr.left = fold_expr(expr.left, fn_nr) expr.left = fold_expr(expr.left, fn_nr)
expr.right = fold_expr(expr.right, fn_nr) expr.right = fold_expr(expr.right, fn_nr)
} else if (k == "." || k == "[") { } else if (k == "." || k == "[") {
expr.left = fold_expr(expr.left, fn_nr) expr.left = fold_expr(expr.left, fn_nr)
if (k == "[" && expr.right != null) expr.right = fold_expr(expr.right, fn_nr) if (k == "[" && expr.right != null) expr.right = fold_expr(expr.right, fn_nr)
} else if (k == "!" || k == "~" || k == "-unary" || k == "+unary" || k == "delete") { } else if (unary_ops[k] == true) {
expr.expression = fold_expr(expr.expression, fn_nr) expr.expression = fold_expr(expr.expression, fn_nr)
} else if (k == "++" || k == "--") { } else if (k == "++" || k == "--") {
return expr return expr
@@ -382,7 +402,7 @@ var fold = function(ast) {
expr.list[i] = fold_expr(expr.list[i], fn_nr) expr.list[i] = fold_expr(expr.list[i], fn_nr)
i = i + 1 i = i + 1
} }
} else if (k == "array") { } else if (k == "array" || k == "text literal") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
expr.list[i] = fold_expr(expr.list[i], fn_nr) expr.list[i] = fold_expr(expr.list[i], fn_nr)
@@ -394,19 +414,10 @@ var fold = function(ast) {
expr.list[i].right = fold_expr(expr.list[i].right, fn_nr) expr.list[i].right = fold_expr(expr.list[i].right, fn_nr)
i = i + 1 i = i + 1
} }
} else if (k == "text literal") {
i = 0
while (i < length(expr.list)) {
expr.list[i] = fold_expr(expr.list[i], fn_nr)
i = i + 1
}
} else if (k == "function") { } else if (k == "function") {
fold_fn(expr) fold_fn(expr)
return expr return expr
} else if (k == "assign" || k == "+=" || k == "-=" || k == "*=" || } else if (assign_ops[k] == true) {
k == "/=" || k == "%=" || k == "<<=" || k == ">>=" ||
k == ">>>=" || k == "&=" || k == "^=" || k == "|=" ||
k == "**=" || k == "&&=" || k == "||=") {
expr.right = fold_expr(expr.right, fn_nr) expr.right = fold_expr(expr.right, fn_nr)
return expr return expr
} }
@@ -428,7 +439,7 @@ var fold = function(ast) {
} }
// Binary constant folding // Binary constant folding
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || k == "**") { if (arith_ops[k] == true) {
left = expr.left left = expr.left
right = expr.right right = expr.right
if (left != null && right != null && left.kind == "number" && right.kind == "number") { if (left != null && right != null && left.kind == "number" && right.kind == "number") {
@@ -460,7 +471,7 @@ var fold = function(ast) {
} }
// Comparison folding // Comparison folding
if (k == "==" || k == "!=" || k == "<" || k == ">" || k == "<=" || k == ">=") { if (comparison_ops[k] == true) {
left = expr.left left = expr.left
right = expr.right right = expr.right
if (left != null && right != null) { if (left != null && right != null) {

View File

@@ -412,117 +412,117 @@ JSC_CCALL(fd_close,
JSC_CCALL(fd_fstat, JSC_CCALL(fd_fstat,
int fd = js2fd(js, argv[0]); int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION; if (fd < 0) return JS_EXCEPTION;
struct stat st; struct stat st;
if (fstat(fd, &st) != 0) if (fstat(fd, &st) != 0)
return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno)); return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno));
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32 #ifndef _WIN32
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else #else
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif #endif
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
// Add boolean properties for file type JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); JS_RETURN(obj.val);
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
return obj;
) )
JSC_CCALL(fd_stat, JSC_CCALL(fd_stat,
const char *path = JS_ToCString(js, argv[0]); const char *path = JS_ToCString(js, argv[0]);
if (!path) return JS_EXCEPTION; if (!path) return JS_EXCEPTION;
struct stat st; struct stat st;
if (stat(path, &st) != 0) { if (stat(path, &st) != 0) {
JS_FreeCString(js, path); JS_FreeCString(js, path);
return JS_NewObject(js); return JS_NewObject(js);
} }
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
#ifndef _WIN32 #ifndef _WIN32
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else #else
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif #endif
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
// Add boolean properties for file type JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_FreeCString(js, path); JS_FreeCString(js, path);
return obj; JS_RETURN(obj.val);
) )
JSC_SCALL(fd_readdir, JSC_SCALL(fd_readdir,
JS_FRAME(js);
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATA ffd; WIN32_FIND_DATA ffd;
char path[PATH_MAX]; char path[PATH_MAX];
snprintf(path, sizeof(path), "%s\\*", str); snprintf(path, sizeof(path), "%s\\*", str);
HANDLE hFind = FindFirstFile(path, &ffd); HANDLE hFind = FindFirstFile(path, &ffd);
if (hFind == INVALID_HANDLE_VALUE) { if (hFind == INVALID_HANDLE_VALUE) {
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path); ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
} else { } else {
ret = JS_NewArray(js); JS_ROOT(arr, JS_NewArray(js));
do { do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName)); JS_ArrayPush(js, &arr.val, JS_NewString(js, ffd.cFileName));
} while (FindNextFile(hFind, &ffd) != 0); } while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind); FindClose(hFind);
ret = arr.val;
} }
#else #else
DIR *d; DIR *d;
struct dirent *dir; struct dirent *dir;
d = opendir(str); d = opendir(str);
if (d) { if (d) {
ret = JS_NewArray(js); JS_ROOT(arr, JS_NewArray(js));
while ((dir = readdir(d)) != NULL) { while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
JS_ArrayPush(js, &ret, JS_NewString(js, dir->d_name)); JS_ArrayPush(js, &arr.val, JS_NewString(js, dir->d_name));
} }
closedir(d); closedir(d);
ret = arr.val;
} else { } else {
ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno)); ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno));
} }
#endif #endif
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
) )
JSC_CCALL(fd_is_file, JSC_CCALL(fd_is_file,
@@ -585,9 +585,9 @@ JSC_CCALL(fd_slurpwrite,
) )
// Helper function for recursive enumeration // Helper function for recursive enumeration
static void visit_directory(JSContext *js, JSValue results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) { static void visit_directory(JSContext *js, JSValue *results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) {
if (!curr_path) return; if (!curr_path) return;
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATA ffd; WIN32_FIND_DATA ffd;
char search_path[PATH_MAX]; char search_path[PATH_MAX];
@@ -602,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, ffd.cFileName); strcpy(item_rel, ffd.cFileName);
} }
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -627,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, dir->d_name); strcpy(item_rel, dir->d_name);
} }
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -651,14 +651,16 @@ JSC_SCALL(fd_enumerate,
if (argc > 1) if (argc > 1)
recurse = JS_ToBool(js, argv[1]); recurse = JS_ToBool(js, argv[1]);
JSValue results = JS_NewArray(js); JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
int result_count = 0; int result_count = 0;
struct stat st; struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
visit_directory(js, results, &result_count, path, "", recurse); visit_directory(js, &arr.val, &result_count, path, "", recurse);
ret = results; ret = arr.val;
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
) )
JSC_CCALL(fd_realpath, JSC_CCALL(fd_realpath,
@@ -753,7 +755,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
}; };
JSValue js_core_internal_fd_use(JSContext *js) { JSValue js_core_internal_fd_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_fd_funcs, countof(js_fd_funcs));
} JS_RETURN(mod.val);
}

View File

@@ -75,7 +75,8 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
JSValue js_core_kim_use(JSContext *js) JSValue js_core_kim_use(JSContext *js)
{ {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_kim_funcs, countof(js_kim_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -277,29 +277,31 @@ JSC_CCALL(os_mallinfo,
) )
static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) { static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
JSValue ret = JS_NULL; JS_FRAME(js);
ret = JS_NewObject(js); JS_ROOT(ret, JS_NewObject(js));
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__) || defined(__APPLE__)
struct rusage jsmem; struct rusage jsmem;
getrusage(RUSAGE_SELF, &jsmem); getrusage(RUSAGE_SELF, &jsmem);
JSJMEMRET(ru_maxrss); #define JSJMEMRET_GC(FIELD) JS_SetPropertyStr(js, ret.val, #FIELD, number2js(js, jsmem.FIELD));
JSJMEMRET(ru_ixrss); JSJMEMRET_GC(ru_maxrss);
JSJMEMRET(ru_idrss); JSJMEMRET_GC(ru_ixrss);
JSJMEMRET(ru_isrss); JSJMEMRET_GC(ru_idrss);
JSJMEMRET(ru_minflt); JSJMEMRET_GC(ru_isrss);
JSJMEMRET(ru_majflt); JSJMEMRET_GC(ru_minflt);
JSJMEMRET(ru_nswap); JSJMEMRET_GC(ru_majflt);
JSJMEMRET(ru_inblock); JSJMEMRET_GC(ru_nswap);
JSJMEMRET(ru_oublock); JSJMEMRET_GC(ru_inblock);
JSJMEMRET(ru_msgsnd); JSJMEMRET_GC(ru_oublock);
JSJMEMRET(ru_msgrcv); JSJMEMRET_GC(ru_msgsnd);
JSJMEMRET(ru_nsignals); JSJMEMRET_GC(ru_msgrcv);
JSJMEMRET(ru_nvcsw); JSJMEMRET_GC(ru_nsignals);
JSJMEMRET(ru_nivcsw); JSJMEMRET_GC(ru_nvcsw);
JSJMEMRET_GC(ru_nivcsw);
#undef JSJMEMRET_GC
#endif #endif
return ret; JS_RETURN(ret.val);
} }
JSC_SCALL(os_system, JSC_SCALL(os_system,
@@ -645,7 +647,8 @@ JSValue js_core_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id); JS_NewClassID(&js_dylib_class_id);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class); JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_os_funcs, countof(js_os_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -71,10 +71,11 @@ static const JSCFunctionListEntry js_time_funcs[] = {
JSValue JSValue
js_core_internal_time_use(JSContext *ctx) js_core_internal_time_use(JSContext *ctx)
{ {
JSValue obj = JS_NewObject(ctx); JS_FRAME(ctx);
JS_SetPropertyFunctionList(ctx, obj, JS_ROOT(mod, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, mod.val,
js_time_funcs, js_time_funcs,
sizeof(js_time_funcs) / sizeof(js_time_funcs) /
sizeof(js_time_funcs[0])); sizeof(js_time_funcs[0]));
return obj; JS_RETURN(mod.val);
} }

142
mcode.cm
View File

@@ -28,6 +28,13 @@ var mcode = function(ast) {
"<<=": "shl", ">>=": "shr", ">>>=": "ushr" "<<=": "shl", ">>=": "shr", ">>>=": "ushr"
} }
var sensory_ops = {
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",
length: "length"
}
// Compiler state // Compiler state
var s_instructions = null var s_instructions = null
var s_data = null var s_data = null
@@ -273,19 +280,70 @@ var mcode = function(ast) {
return node.kind == "null" return node.kind == "null"
} }
// emit_add_decomposed: emit generic add (VM dispatches int/float/text) // emit_add_decomposed: emit type-dispatched add (text → concat, num → add)
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure // reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_add_decomposed = function() { var emit_add_decomposed = function() {
// Known text+text → concat directly (skip numeric check in VM)
if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) { if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) {
emit_3("concat", _bp_dest, _bp_left, _bp_right) emit_3("concat", _bp_dest, _bp_left, _bp_right)
return null return null
} }
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
emit_3("add", _bp_dest, _bp_left, _bp_right)
return null
}
// Unknown types: emit full dispatch
var t0 = alloc_slot()
var t1 = alloc_slot()
var done = gen_label("add_done")
var check_num = gen_label("add_cn")
// Check text path first (since add doubles as concat)
emit_2("is_text", t0, _bp_left)
emit_jump_cond("jump_false", t0, check_num)
emit_2("is_text", t1, _bp_right)
emit_jump_cond("jump_false", t1, check_num)
emit_3("concat", _bp_dest, _bp_left, _bp_right)
emit_jump(done)
// Numeric path
var err = gen_label("add_err")
emit_label(check_num)
emit_2("is_num", t0, _bp_left)
emit_jump_cond("jump_false", t0, err)
emit_2("is_num", t1, _bp_right)
emit_jump_cond("jump_false", t1, err)
emit_3("add", _bp_dest, _bp_left, _bp_right) emit_3("add", _bp_dest, _bp_left, _bp_right)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null return null
} }
// emit_numeric_binop removed — generic ops emitted directly via passthrough // emit_numeric_binop: emit type-guarded numeric binary op
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_numeric_binop = function(op_str) {
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
return null
}
var t0 = alloc_slot()
var t1 = alloc_slot()
var err = gen_label("num_err")
var done = gen_label("num_done")
emit_2("is_num", t0, _bp_left)
emit_jump_cond("jump_false", t0, err)
emit_2("is_num", t1, _bp_right)
emit_jump_cond("jump_false", t1, err)
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null
}
// emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false) // emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
// reads _bp_dest, _bp_left, _bp_right from closure // reads _bp_dest, _bp_left, _bp_right from closure
@@ -511,15 +569,36 @@ var mcode = function(ast) {
return null return null
} }
// emit_neg_decomposed: emit generic negate (VM dispatches int/float) // emit_neg_decomposed: emit type-guarded negate
var emit_neg_decomposed = function(dest, src, src_node) { var emit_neg_decomposed = function(dest, src, src_node) {
if (is_known_number(src_node)) {
emit_2("negate", dest, src)
return null
}
var t0 = alloc_slot()
var err = gen_label("neg_err")
var done = gen_label("neg_done")
emit_2("is_num", t0, src)
emit_jump_cond("jump_false", t0, err)
emit_2("negate", dest, src) emit_2("negate", dest, src)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null return null
} }
// Central router: maps op string to decomposition helper // Central router: maps op string to decomposition helper
// Sets _bp_* closure vars then calls helper with reduced args // Sets _bp_* closure vars then calls helper with reduced args
var relational_ops = {
lt: ["lt_int", "lt_float", "lt_text"],
le: ["le_int", "le_float", "le_text"],
gt: ["gt_int", "gt_float", "gt_text"],
ge: ["ge_int", "ge_float", "ge_text"]
}
var emit_binop = function(op_str, dest, left, right) { var emit_binop = function(op_str, dest, left, right) {
var rel = null
_bp_dest = dest _bp_dest = dest
_bp_left = left _bp_left = left
_bp_right = right _bp_right = right
@@ -529,18 +608,17 @@ var mcode = function(ast) {
emit_eq_decomposed() emit_eq_decomposed()
} else if (op_str == "ne") { } else if (op_str == "ne") {
emit_ne_decomposed() emit_ne_decomposed()
} else if (op_str == "lt") {
emit_relational("lt_int", "lt_float", "lt_text")
} else if (op_str == "le") {
emit_relational("le_int", "le_float", "le_text")
} else if (op_str == "gt") {
emit_relational("gt_int", "gt_float", "gt_text")
} else if (op_str == "ge") {
emit_relational("ge_int", "ge_float", "ge_text")
} else { } else {
// Passthrough for subtract, multiply, divide, modulo, rel = relational_ops[op_str]
// bitwise, pow, in, etc. if (rel != null) {
emit_3(op_str, dest, left, right) emit_relational(rel[0], rel[1], rel[2])
} else if (op_str == "subtract" || op_str == "multiply" ||
op_str == "divide" || op_str == "modulo" || op_str == "pow") {
emit_numeric_binop(op_str)
} else {
// Passthrough for bitwise, in, etc.
emit_3(op_str, dest, left, right)
}
} }
return null return null
} }
@@ -1670,37 +1748,11 @@ var mcode = function(ast) {
fname = callee.name fname = callee.name
nargs = args_list != null ? length(args_list) : 0 nargs = args_list != null ? length(args_list) : 0
// 1-arg type check intrinsics → direct opcode // 1-arg type check intrinsics → direct opcode
if (nargs == 1) { if (nargs == 1 && sensory_ops[fname] != null) {
if (fname == "is_array" || fname == "is_function" ||
fname == "is_object" || fname == "is_stone" ||
fname == "is_integer" || fname == "is_text" ||
fname == "is_number" || fname == "is_logical" ||
fname == "is_null" || fname == "length") {
a0 = gen_expr(args_list[0], -1) a0 = gen_expr(args_list[0], -1)
d = alloc_slot() d = alloc_slot()
if (fname == "is_array") { emit_2(sensory_ops[fname], d, a0)
emit_2("is_array", d, a0)
} else if (fname == "is_function") {
emit_2("is_func", d, a0)
} else if (fname == "is_object") {
emit_2("is_record", d, a0)
} else if (fname == "is_stone") {
emit_2("is_stone", d, a0)
} else if (fname == "is_integer") {
emit_2("is_int", d, a0)
} else if (fname == "is_text") {
emit_2("is_text", d, a0)
} else if (fname == "is_number") {
emit_2("is_num", d, a0)
} else if (fname == "is_logical") {
emit_2("is_bool", d, a0)
} else if (fname == "is_null") {
emit_2("is_null", d, a0)
} else if (fname == "length") {
emit_2("length", d, a0)
}
return d return d
}
} }
// 2-arg push: push(arr, val) → guarded direct opcode // 2-arg push: push(arr, val) → guarded direct opcode
if (nargs == 2 && fname == "push") { if (nargs == 2 && fname == "push") {
@@ -1930,7 +1982,7 @@ var mcode = function(ast) {
_i = _i + 1 _i = _i + 1
} }
dest = alloc_slot() dest = alloc_slot()
add_instr(["array", dest, 0]) add_instr(["array", dest, count])
_i = 0 _i = 0
while (_i < count) { while (_i < count) {
emit_2("push", dest, elem_slots[_i]) emit_2("push", dest, elem_slots[_i])
@@ -1943,7 +1995,7 @@ var mcode = function(ast) {
if (kind == "record") { if (kind == "record") {
list = expr.list list = expr.list
dest = alloc_slot() dest = alloc_slot()
push(s_instructions, ["record", dest, 0]) push(s_instructions, ["record", dest, length(list)])
_i = 0 _i = 0
while (_i < length(list)) { while (_i < length(list)) {
pair = list[_i] pair = list[_i]

View File

@@ -22,6 +22,10 @@ if get_option('validate_gc')
add_project_arguments('-DVALIDATE_GC', language: 'c') add_project_arguments('-DVALIDATE_GC', language: 'c')
endif endif
if get_option('force_gc')
add_project_arguments('-DFORCE_GC_AT_MALLOC', language: 'c')
endif
deps = [] deps = []
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'

View File

@@ -1,2 +1,4 @@
option('validate_gc', type: 'boolean', value: false, option('validate_gc', type: 'boolean', value: false,
description: 'Enable GC validation checks (stale pointer detection, pre-GC frame validation)') description: 'Enable GC validation checks (stale pointer detection, pre-GC frame validation)')
option('force_gc', type: 'boolean', value: false,
description: 'Force GC on every allocation (makes stale pointer bugs deterministic)')

View File

@@ -570,19 +570,21 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_core_enet_use(JSContext *ctx) JSValue js_core_enet_use(JSContext *ctx)
{ {
JS_FRAME(ctx);
JS_NewClassID(&enet_host_id); JS_NewClassID(&enet_host_id);
JS_NewClass(ctx, enet_host_id, &enet_host); JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx); JS_ROOT(host_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto); JS_SetClassProto(ctx, enet_host_id, host_proto.val);
JS_NewClassID(&enet_peer_class_id); JS_NewClassID(&enet_peer_class_id);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class); JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx); JS_ROOT(peer_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto); JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val);
JSValue export_obj = JS_NewObject(ctx); JS_ROOT(export_obj, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs)); JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));
return export_obj; JS_RETURN(export_obj.val);
} }

View File

@@ -319,9 +319,10 @@ static const JSCFunctionListEntry js_http_funcs[] = {
}; };
JSValue js_core_http_use(JSContext *js) { JSValue js_core_http_use(JSContext *js) {
JS_FRAME(js);
par_easycurl_init(0); // Initialize platform HTTP backend par_easycurl_init(0); // Initialize platform HTTP backend
JSValue obj = JS_NewObject(js); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, obj, js_http_funcs, JS_SetPropertyFunctionList(js, mod.val, js_http_funcs,
sizeof(js_http_funcs)/sizeof(js_http_funcs[0])); sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
return obj; JS_RETURN(mod.val);
} }

View File

@@ -595,26 +595,27 @@ static const JSCFunctionListEntry js_socket_funcs[] = {
}; };
JSValue js_core_socket_use(JSContext *js) { JSValue js_core_socket_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs)); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_socket_funcs, countof(js_socket_funcs));
// Add constants // Add constants
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC)); JS_SetPropertyStr(js, mod.val, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET)); JS_SetPropertyStr(js, mod.val, "AF_INET", JS_NewInt32(js, AF_INET));
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6)); JS_SetPropertyStr(js, mod.val, "AF_INET6", JS_NewInt32(js, AF_INET6));
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX)); JS_SetPropertyStr(js, mod.val, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM)); JS_SetPropertyStr(js, mod.val, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM)); JS_SetPropertyStr(js, mod.val, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE)); JS_SetPropertyStr(js, mod.val, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD)); JS_SetPropertyStr(js, mod.val, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR)); JS_SetPropertyStr(js, mod.val, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR)); JS_SetPropertyStr(js, mod.val, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET)); JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR)); JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
return mod; JS_RETURN(mod.val);
} }

View File

@@ -1,6 +1,11 @@
var parse = function(tokens, src, filename, tokenizer) { var parse = function(tokens, src, filename, tokenizer) {
var _src_len = length(src) var _src_len = length(src)
var template_escape_map = {
n: "\n", t: "\t", r: "\r", "\\": "\\",
"`": "`", "$": "$", "0": character(0)
}
// ============================================================ // ============================================================
// Parser Cursor // Parser Cursor
// ============================================================ // ============================================================
@@ -175,6 +180,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var tc = null var tc = null
var tq = null var tq = null
var esc_ch = null var esc_ch = null
var esc_val = null
var expr_tokens = null var expr_tokens = null
var sub_ast = null var sub_ast = null
var sub_stmt = null var sub_stmt = null
@@ -223,13 +229,8 @@ var parse = function(tokens, src, filename, tokenizer) {
while (tvi < tvlen) { while (tvi < tvlen) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) { if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1] esc_ch = tv[tvi + 1]
if (esc_ch == "n") { push(fmt_parts, "\n") } esc_val = template_escape_map[esc_ch]
else if (esc_ch == "t") { push(fmt_parts, "\t") } if (esc_val != null) { push(fmt_parts, esc_val) }
else if (esc_ch == "r") { push(fmt_parts, "\r") }
else if (esc_ch == "\\") { push(fmt_parts, "\\") }
else if (esc_ch == "`") { push(fmt_parts, "`") }
else if (esc_ch == "$") { push(fmt_parts, "$") }
else if (esc_ch == "0") { push(fmt_parts, character(0)) }
else { push(fmt_parts, esc_ch) } else { push(fmt_parts, esc_ch) }
tvi = tvi + 2 tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") { } else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {

116
prettify_mcode.ce Normal file
View File

@@ -0,0 +1,116 @@
// prettify_mcode.ce — reformat .mcode files to be human-readable
// Usage: ./cell --dev prettify_mcode boot/tokenize.cm.mcode
// ./cell --dev prettify_mcode boot/*.mcode
var fd = use("fd")
var json = use("json")
if (length(args) == 0) {
print("usage: cell prettify_mcode <file.mcode> [...]")
disrupt
}
// Collapse leaf arrays (instruction arrays) onto single lines
var compact_arrays = function(json_text) {
var lines = array(json_text, "\n")
var result = []
var i = 0
var line = null
var trimmed = null
var collecting = false
var collected = null
var indent = null
var is_leaf = null
var j = 0
var inner = null
var parts = null
var trailing = null
var chars = null
var k = 0
while (i < length(lines)) {
line = lines[i]
trimmed = trim(line)
if (collecting == false && trimmed == "[") {
collecting = true
chars = array(line)
k = 0
while (k < length(chars) && chars[k] == " ") {
k = k + 1
}
indent = text(line, 0, k)
collected = []
i = i + 1
continue
}
if (collecting) {
if (trimmed == "]" || trimmed == "],") {
is_leaf = true
j = 0
while (j < length(collected)) {
inner = trim(collected[j])
if (starts_with(inner, "[") || starts_with(inner, "{")) {
is_leaf = false
}
j = j + 1
}
if (is_leaf && length(collected) > 0) {
parts = []
j = 0
while (j < length(collected)) {
inner = trim(collected[j])
if (ends_with(inner, ",")) {
inner = text(inner, 0, length(inner) - 1)
}
parts[] = inner
j = j + 1
}
trailing = ""
if (ends_with(trimmed, ",")) {
trailing = ","
}
result[] = `${indent}[${text(parts, ", ")}]${trailing}`
} else {
result[] = `${indent}[`
j = 0
while (j < length(collected)) {
result[] = collected[j]
j = j + 1
}
result[] = line
}
collecting = false
} else {
collected[] = line
}
i = i + 1
continue
}
result[] = line
i = i + 1
}
return text(result, "\n")
}
var i = 0
var path = null
var raw = null
var obj = null
var pretty = null
var f = null
while (i < length(args)) {
path = args[i]
if (!fd.is_file(path)) {
print(`skip ${path} (not found)`)
i = i + 1
continue
}
raw = text(fd.slurp(path))
obj = json.decode(raw)
pretty = compact_arrays(json.encode(obj, null, 2))
f = fd.open(path, "w")
fd.write(f, pretty)
fd.close(f)
print(`prettified ${path}`)
i = i + 1
}

356
qbe.cm
View File

@@ -98,6 +98,7 @@ var is_text = function(p, v) {
jmp @${p}.done jmp @${p}.done
@${p}.no @${p}.no
%${p} =w copy 0 %${p} =w copy 0
jmp @${p}.done
@${p}.done @${p}.done
` `
} }
@@ -174,6 +175,7 @@ var to_float64 = function(p, v) {
%${p}.fbits =l or %${p}.fs63, %${p}.fe52 %${p}.fbits =l or %${p}.fs63, %${p}.fe52
%${p}.fbits =l or %${p}.fbits, %${p}.fmant %${p}.fbits =l or %${p}.fbits, %${p}.fmant
%${p} =d cast %${p}.fbits %${p} =d cast %${p}.fbits
jmp @${p}.done
@${p}.done @${p}.done
` `
} }
@@ -199,201 +201,37 @@ var new_bool = function(p, b) {
// new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p} // new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
var new_float64 = function(p, ctx, d) { var new_float64 = function(p, ctx, d) {
return ` %${p} =l call $__JS_NewFloat64(l ${ctx}, d ${d}) return ` %${p} =l call $qbe_new_float64(l ${ctx}, d ${d})
` `
} }
// ============================================================ // ============================================================
// Arithmetic — add(p, ctx, a, b) // Arithmetic — add/sub/mul/div/mod(p, ctx, a, b)
// Int fast path inline, text concat and float as C calls. // Simple C call wrappers. Type dispatch is handled in mcode.cm.
// Jumps to @disrupt on type mismatch.
// ============================================================ // ============================================================
var add = function(p, ctx, a, b) { var add = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.sum =l add %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.sum, ${int32_min}
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
@${p}.int_ok
%${p}.rw =w copy %${p}.sum
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.int_overflow
%${p}.fd =d sltof %${p}.sum
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_text =w call $JS_IsText(l ${a})
%${p}.b_is_text =w call $JS_IsText(l ${b})
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
jnz %${p}.both_text, @${p}.text_path, @${p}.chk_num
@${p}.text_path
%${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
jmp @${p}.done
@${p}.chk_num
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
var sub = function(p, ctx, a, b) { var sub = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.diff =l sub %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.diff, ${int32_min}
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
@${p}.int_ok
%${p}.rw =w copy %${p}.diff
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.int_overflow
%${p}.fd =d sltof %${p}.diff
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
var mul = function(p, ctx, a, b) { var mul = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.prod =l mul %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.prod, ${int32_min}
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
@${p}.int_ok
%${p}.rw =w copy %${p}.prod
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.int_overflow
%${p}.fd =d sltof %${p}.prod
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
var div = function(p, ctx, a, b) { var div = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =w copy 0
%${p}.tmp =l sar ${a}, 1
%${p}.ia =w copy %${p}.tmp
%${p}.ib =w copy 0
%${p}.tmp2 =l sar ${b}, 1
%${p}.ib =w copy %${p}.tmp2
%${p}.div0 =w ceqw %${p}.ib, 0
jnz %${p}.div0, @${p}.ret_null, @${p}.chk_exact
@${p}.ret_null
%${p} =l copy ${js_null}
jmp @${p}.done
@${p}.chk_exact
%${p}.rem =w rem %${p}.ia, %${p}.ib
%${p}.exact =w ceqw %${p}.rem, 0
jnz %${p}.exact, @${p}.int_div, @${p}.int_to_float
@${p}.int_div
%${p}.q =w div %${p}.ia, %${p}.ib
%${p}.qext =l extuw %${p}.q
%${p} =l shl %${p}.qext, 1
jmp @${p}.done
@${p}.int_to_float
%${p}.da =d swtof %${p}.ia
%${p}.db =d swtof %${p}.ib
%${p}.dr =d div %${p}.da, %${p}.db
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
var mod = function(p, ctx, a, b) { var mod = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =w copy 0
%${p}.tmp =l sar ${a}, 1
%${p}.ia =w copy %${p}.tmp
%${p}.ib =w copy 0
%${p}.tmp2 =l sar ${b}, 1
%${p}.ib =w copy %${p}.tmp2
%${p}.div0 =w ceqw %${p}.ib, 0
jnz %${p}.div0, @${p}.ret_null, @${p}.do_mod
@${p}.ret_null
%${p} =l copy ${js_null}
jmp @${p}.done
@${p}.do_mod
%${p}.r =w rem %${p}.ia, %${p}.ib
%${p}.rext =l extuw %${p}.r
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
@@ -484,6 +322,7 @@ var cmp = function(p, ctx, a, b) {
jmp @${p}.done jmp @${p}.done
@${p}.mismatch @${p}.mismatch
%${p} =l copy ${mismatch_val} %${p} =l copy ${mismatch_val}
jmp @${p}.done
@${p}.done @${p}.done
` `
} }
@@ -518,90 +357,28 @@ var gt = function(p, ctx, a, b) {
var ge = function(p, ctx, a, b) { var ge = function(p, ctx, a, b) {
_qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true} _qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true}
return cmp(p, ctx, a, b)
} }
// ============================================================ // ============================================================
// Unary Ops // Unary Ops
// ============================================================ // ============================================================
// neg(p, ctx, v) — negate. Int fast path (INT32_MIN edge case), else C call. // neg(p, ctx, v) — negate via C call (type guards in mcode)
var neg = function(p, ctx, v) { var neg = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
@${p}.min_overflow
%${p}.fd =d swtof %${p}.iw
%${p}.fdn =d neg %${p}.fd
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
jmp @${p}.done
@${p}.int_ok
%${p}.ni =w sub 0, %${p}.iw
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
jmp @${p}.done
@${p}.float_path
%${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
@${p}.done
` `
} }
// inc(p, ctx, v) — increment. Int fast path (INT32_MAX edge case), else C call. // inc(p, ctx, v) — increment via C call (type guards in mcode)
var inc = function(p, ctx, v) { var inc = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_max =w ceqw %${p}.iw, ${int32_max}
jnz %${p}.is_max, @${p}.max_overflow, @${p}.int_ok
@${p}.max_overflow
%${p}.fd =d swtof %${p}.iw
%${p}.fd1 =d add %${p}.fd, d_1.0
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
jmp @${p}.done
@${p}.int_ok
%${p}.ni =w add %${p}.iw, 1
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
jmp @${p}.done
@${p}.float_path
%${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
@${p}.done
` `
} }
// dec(p, ctx, v) — decrement. Int fast path (INT32_MIN edge case), else C call. // dec(p, ctx, v) — decrement via C call (type guards in mcode)
var dec = function(p, ctx, v) { var dec = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
@${p}.min_overflow
%${p}.fd =d swtof %${p}.iw
%${p}.fd1 =d sub %${p}.fd, d_1.0
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
jmp @${p}.done
@${p}.int_ok
%${p}.ni =w sub %${p}.iw, 1
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
jmp @${p}.done
@${p}.float_path
%${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
@${p}.done
` `
} }
@@ -615,22 +392,9 @@ var lnot = function(p, ctx, v) {
` `
} }
// bnot(p, ctx, v) — bitwise not. Convert to int32, ~, re-tag. // bnot(p, ctx, v) — bitwise not via C call
var bnot = function(p, ctx, v) { var bnot = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_bnot(l ${ctx}, l ${v})
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.slow_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.nw =w xor %${p}.iw, -1
%${p}.nex =l extuw %${p}.nw
%${p} =l shl %${p}.nex, 1
jmp @${p}.done
@${p}.slow_path
%${p} =l call $qbe_bnot(l ${ctx}, l ${v})
@${p}.done
` `
} }
@@ -639,92 +403,34 @@ var bnot = function(p, ctx, v) {
// Both operands must be numeric. Int fast path, float -> convert to int32. // Both operands must be numeric. Int fast path, float -> convert to int32.
// ============================================================ // ============================================================
// reads _qop from closure var band = function(p, ctx, a, b) {
var bitwise_op = function(p, ctx, a, b) { return ` %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${b})
var qbe_op = _qop
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.iaw =w copy %${p}.ia
%${p}.ib =l sar ${b}, 1
%${p}.ibw =w copy %${p}.ib
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.ibw
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.slow_path
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
@${p}.float_to_int
%${p} =l call $qbe_bitwise_${qbe_op}(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
var band = function(p, ctx, a, b) {
_qop = "and"
return bitwise_op(p, ctx, a, b)
}
var bor = function(p, ctx, a, b) { var bor = function(p, ctx, a, b) {
_qop = "or" return ` %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b})
return bitwise_op(p, ctx, a, b) `
} }
var bxor = function(p, ctx, a, b) { var bxor = function(p, ctx, a, b) {
_qop = "xor" return ` %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b})
return bitwise_op(p, ctx, a, b)
}
// Shift ops: mask shift amount to 5 bits (& 31)
// reads _qop from closure
var shift_op = function(p, ctx, a, b) {
var qbe_op = _qop
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.iaw =w copy %${p}.ia
%${p}.ib =l sar ${b}, 1
%${p}.ibw =w copy %${p}.ib
%${p}.sh =w and %${p}.ibw, 31
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.sh
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.slow_path
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
@${p}.float_to_int
%${p} =l call $qbe_shift_${qbe_op}(l ${ctx}, l ${a}, l ${b})
@${p}.done
` `
} }
var shl = function(p, ctx, a, b) { var shl = function(p, ctx, a, b) {
_qop = "shl" return ` %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
var shr = function(p, ctx, a, b) { var shr = function(p, ctx, a, b) {
_qop = "sar" return ` %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
var ushr = function(p, ctx, a, b) { var ushr = function(p, ctx, a, b) {
_qop = "shr" return ` %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
// ============================================================ // ============================================================

View File

@@ -76,6 +76,7 @@ var qbe_emit = function(ir, qbe, export_name) {
var instrs = fn.instructions var instrs = fn.instructions
var nr_slots = fn.nr_slots var nr_slots = fn.nr_slots
var nr_args = fn.nr_args var nr_args = fn.nr_args
var captured = build_captured(fn)
var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx) var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx)
name = sanitize(name) name = sanitize(name)
var i = 0 var i = 0
@@ -88,6 +89,7 @@ var qbe_emit = function(ir, qbe, export_name) {
var p = null var p = null
var pn = null var pn = null
var sl = null var sl = null
var lbl = null
var fop_id = 0 var fop_id = 0
var nr_elems = 0 var nr_elems = 0
var ei = 0 var ei = 0
@@ -113,22 +115,45 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` storel ${s(slot)}, %p${text(slot)}`) emit(` storel ${s(slot)}, %p${text(slot)}`)
} }
// Reload captured slots from frame (after invoke, closures may have modified them)
var reload_captured = function() {
var ri = 0
while (ri < nr_slots) {
if (captured[text(ri)] == true) {
emit(` ${s(ri)} =l loadl %p${text(ri)}`)
}
ri = ri + 1
}
}
// Walk instructions // Walk instructions
// Slot loads above are not terminators
var last_was_term = false
i = 0 i = 0
while (i < length(instrs)) { while (i < length(instrs)) {
instr = instrs[i] instr = instrs[i]
i = i + 1 i = i + 1
// Labels are plain strings // Labels are plain strings; skip _nop_ur_ pseudo-labels from streamline
if (is_text(instr)) { if (is_text(instr)) {
emit("@" + sanitize(instr)) if (starts_with(instr, "_nop_ur_")) continue
lbl = sanitize(instr)
if (!last_was_term) {
emit(` jmp @${lbl}`)
}
emit("@" + lbl)
last_was_term = false
continue continue
} }
// Skip dead code: non-label instructions after a terminator are unreachable
if (last_was_term) continue
op = instr[0] op = instr[0]
a1 = instr[1] a1 = instr[1]
a2 = instr[2] a2 = instr[2]
a3 = instr[3] a3 = instr[3]
last_was_term = false
// --- Constants --- // --- Constants ---
@@ -157,11 +182,11 @@ var qbe_emit = function(ir, qbe, export_name) {
if (is_integer(a2)) { if (is_integer(a2)) {
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`) emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
} else { } else {
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2)})`) emit(` ${s(a1)} =l call $qbe_new_float64(l %ctx, d d_${text(a2)})`)
} }
} else if (is_text(a2)) { } else if (is_text(a2)) {
sl = intern_str(a2) sl = intern_str(a2)
emit(` ${s(a1)} =l call $JS_NewString(l %ctx, l ${sl})`) emit(` ${s(a1)} =l call $qbe_new_string(l %ctx, l ${sl})`)
} else if (is_object(a2)) { } else if (is_object(a2)) {
if (a2.make == "intrinsic") { if (a2.make == "intrinsic") {
sl = intern_str(a2.name) sl = intern_str(a2.name)
@@ -170,13 +195,13 @@ var qbe_emit = function(ir, qbe, export_name) {
if (a2.number != null && is_integer(a2.number)) { if (a2.number != null && is_integer(a2.number)) {
emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`) emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`)
} else if (a2.number != null) { } else if (a2.number != null) {
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2.number)})`) emit(` ${s(a1)} =l call $qbe_new_float64(l %ctx, d d_${text(a2.number)})`)
} else { } else {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`) emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
} }
} else if (a2.kind == "text") { } else if (a2.kind == "text") {
sl = intern_str(a2.value) sl = intern_str(a2.value)
emit(` ${s(a1)} =l call $JS_NewString(l %ctx, l ${sl})`) emit(` ${s(a1)} =l call $qbe_new_string(l %ctx, l ${sl})`)
} else if (a2.kind == "true") { } else if (a2.kind == "true") {
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`) emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
} else if (a2.kind == "false") { } else if (a2.kind == "false") {
@@ -205,7 +230,7 @@ var qbe_emit = function(ir, qbe, export_name) {
if (op == "add") { if (op == "add") {
p = fresh() p = fresh()
emit(qbe.add(p, "%ctx", s(a2), s(a3))) emit(` %${p} =l call $cell_rt_add(l %ctx, l ${s(a2)}, l ${s(a3)})`)
emit(` ${s(a1)} =l copy %${p}`) emit(` ${s(a1)} =l copy %${p}`)
wb(a1) wb(a1)
continue continue
@@ -246,6 +271,12 @@ var qbe_emit = function(ir, qbe, export_name) {
continue continue
} }
if (op == "pow") {
emit(` ${s(a1)} =l call $qbe_float_pow(l %ctx, l ${s(a2)}, l ${s(a3)})`)
wb(a1)
continue
}
// --- String concat --- // --- String concat ---
if (op == "concat") { if (op == "concat") {
@@ -305,6 +336,46 @@ var qbe_emit = function(ir, qbe, export_name) {
wb(a1) wb(a1)
continue continue
} }
if (op == "is_array") {
p = fresh()
emit(` %${p} =w call $JS_IsArray(l ${s(a2)})`)
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_func") {
p = fresh()
emit(` %${p} =w call $JS_IsFunction(l ${s(a2)})`)
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_record") {
p = fresh()
emit(` %${p} =w call $JS_IsRecord(l ${s(a2)})`)
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_stone") {
p = fresh()
emit(` %${p} =w call $JS_IsStone(l ${s(a2)})`)
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_proxy") {
p = fresh()
emit(` %${p} =w call $cell_rt_is_proxy(l %ctx, l ${s(a2)})`)
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
// --- Comparisons (int path) --- // --- Comparisons (int path) ---
@@ -367,14 +438,30 @@ var qbe_emit = function(ir, qbe, export_name) {
wb(a1) wb(a1)
continue continue
} }
if (op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float") { if (op == "lt_float") {
p = fresh() p = fresh()
fop_id = 0 emit(qbe.lt_float(p, "%ctx", s(a2), s(a3)))
if (op == "lt_float") fop_id = 2 emit(` ${s(a1)} =l copy %${p}`)
else if (op == "le_float") fop_id = 3 wb(a1)
else if (op == "gt_float") fop_id = 4 continue
else if (op == "ge_float") fop_id = 5 }
emit(qbe.cmp_float != null ? qbe.cmp_float(p, "%ctx", s(a2), s(a3), fop_id) : ` %${p} =l call $qbe_float_cmp(l %ctx, w ${text(fop_id)}, l ${s(a2)}, l ${s(a3)})`) if (op == "le_float") {
p = fresh()
emit(qbe.le_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "gt_float") {
p = fresh()
emit(qbe.gt_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ge_float") {
p = fresh()
emit(qbe.ge_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`) emit(` ${s(a1)} =l copy %${p}`)
wb(a1) wb(a1)
continue continue
@@ -494,7 +581,10 @@ var qbe_emit = function(ir, qbe, export_name) {
// --- Property access — runtime calls --- // --- Property access — runtime calls ---
if (op == "load_field") { if (op == "load_field") {
pn = prop_name(a3) pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
if (pn != null) { if (pn != null) {
sl = intern_str(pn) sl = intern_str(pn)
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`) emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
@@ -510,13 +600,28 @@ var qbe_emit = function(ir, qbe, export_name) {
continue continue
} }
if (op == "load_dynamic") { if (op == "load_dynamic") {
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`) pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
if (pn != null) {
sl = intern_str(pn)
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
} else {
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
}
wb(a1) wb(a1)
continue continue
} }
if (op == "store_field") { if (op == "store_field") {
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name) // IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name)
pn = prop_name(a3) pn = null
if (is_text(a3)) {
pn = a3
} else if (is_object(a3)) {
if (a3.name != null) pn = a3.name
else if (a3.value != null) pn = a3.value
}
if (pn != null) { if (pn != null) {
sl = intern_str(pn) sl = intern_str(pn)
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`) emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
@@ -532,19 +637,30 @@ var qbe_emit = function(ir, qbe, export_name) {
} }
if (op == "store_dynamic") { if (op == "store_dynamic") {
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key) // IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`) pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
if (pn != null) {
sl = intern_str(pn)
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
} else {
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
}
continue continue
} }
// --- Closure access --- // --- Closure access ---
if (op == "get") { if (op == "get") {
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a2)}, l ${text(a3)})`) // mcode: get(dest, slot, depth) — a2=slot, a3=depth
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a3)}, l ${text(a2)})`)
wb(a1) wb(a1)
continue continue
} }
if (op == "put") { if (op == "put") {
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a2)}, l ${text(a3)})`) // mcode: put(val, slot, depth) — a2=slot, a3=depth
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a3)}, l ${text(a2)})`)
continue continue
} }
@@ -552,6 +668,7 @@ var qbe_emit = function(ir, qbe, export_name) {
if (op == "jump") { if (op == "jump") {
emit(` jmp @${sanitize(a1)}`) emit(` jmp @${sanitize(a1)}`)
last_was_term = true
continue continue
} }
if (op == "jump_true") { if (op == "jump_true") {
@@ -611,6 +728,13 @@ var qbe_emit = function(ir, qbe, export_name) {
if (op == "invoke") { if (op == "invoke") {
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`) emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
wb(a2) wb(a2)
reload_captured()
continue
}
if (op == "tail_invoke") {
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
wb(a2)
reload_captured()
continue continue
} }
if (op == "goframe") { if (op == "goframe") {
@@ -621,6 +745,7 @@ var qbe_emit = function(ir, qbe, export_name) {
if (op == "goinvoke") { if (op == "goinvoke") {
emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`) emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`)
emit(` ret %_goret`) emit(` ret %_goret`)
last_was_term = true
continue continue
} }
@@ -664,19 +789,38 @@ var qbe_emit = function(ir, qbe, export_name) {
continue continue
} }
// --- Length ---
if (op == "length") {
emit(` ${s(a1)} =l call $JS_CellLength(l %ctx, l ${s(a2)})`)
wb(a1)
continue
}
// --- Misc --- // --- Misc ---
if (op == "return") { if (op == "return") {
emit(` ret ${s(a1)}`) emit(` ret ${s(a1)}`)
last_was_term = true
continue continue
} }
if (op == "disrupt") { if (op == "disrupt") {
emit(` call $cell_rt_disrupt(l %ctx)`) emit(` call $cell_rt_disrupt(l %ctx)`)
emit(` ret ${text(qbe.js_null)}`) emit(` ret ${text(qbe.js_null)}`)
last_was_term = true
continue continue
} }
if (op == "delete") { if (op == "delete") {
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`) pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
if (pn != null) {
sl = intern_str(pn)
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${sl})`)
} else {
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
}
wb(a1) wb(a1)
continue continue
} }
@@ -690,6 +834,14 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` # unknown: ${op}`) emit(` # unknown: ${op}`)
} }
// Emit @disrupt landing pad for arithmetic type-error branches
if (!last_was_term) {
emit(" jmp @disrupt")
}
emit("@disrupt")
emit(` call $cell_rt_disrupt(l %ctx)`)
emit(` ret ${text(qbe.js_null)}`)
emit("}") emit("}")
emit("") emit("")
} }
@@ -698,6 +850,70 @@ var qbe_emit = function(ir, qbe, export_name) {
// Main: compile all functions then main // Main: compile all functions then main
// ============================================================ // ============================================================
// ============================================================
// Pre-scan: find which slots each function has that are modified
// by child closures (via "put" instructions at depth=1).
// Build a map: fn_idx → array of captured slot numbers.
// ============================================================
// For each function, find which fn_idxes it creates via "function" op
var find_children = function(fn_instrs) {
var children = []
var ci = 0
var cinstr = null
while (ci < length(fn_instrs)) {
cinstr = fn_instrs[ci]
ci = ci + 1
if (!is_array(cinstr)) continue
if (cinstr[0] == "function") {
push(children, cinstr[2])
}
}
return children
}
// For a child function, find which parent slots it writes to via put(val, slot, depth=1)
var find_put_slots = function(fn_instrs) {
var slots = []
var pi = 0
var pinstr = null
while (pi < length(fn_instrs)) {
pinstr = fn_instrs[pi]
pi = pi + 1
if (!is_array(pinstr)) continue
// put format: ["put", val, slot, depth]
if (pinstr[0] == "put" && pinstr[3] == 1) {
push(slots, pinstr[2])
}
}
return slots
}
// Build captured_slots for each function (and main)
var build_captured = function(fn) {
var children = find_children(fn.instructions)
var captured = {}
var bi = 0
var child_idx = 0
var child_fn = null
var pslots = null
var si = 0
while (bi < length(children)) {
child_idx = children[bi]
bi = bi + 1
if (child_idx >= 0 && child_idx < length(ir.functions)) {
child_fn = ir.functions[child_idx]
pslots = find_put_slots(child_fn.instructions)
si = 0
while (si < length(pslots)) {
captured[text(pslots[si])] = true
si = si + 1
}
}
}
return captured
}
var fi = 0 var fi = 0
while (fi < length(ir.functions)) { while (fi < length(ir.functions)) {
compile_fn(ir.functions[fi], fi, false) compile_fn(ir.functions[fi], fi, false)

21
qop.c
View File

@@ -457,19 +457,20 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
}; };
JSValue js_core_qop_use(JSContext *js) { JSValue js_core_qop_use(JSContext *js) {
JS_FRAME(js);
JS_NewClassID(&js_qop_archive_class_id); JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js); JS_ROOT(archive_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); JS_SetPropertyFunctionList(js, archive_proto.val, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto); JS_SetClassProto(js, js_qop_archive_class_id, archive_proto.val);
JS_NewClassID(&js_qop_writer_class_id); JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class); JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js); JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs)); JS_SetPropertyFunctionList(js, writer_proto.val, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto); JS_SetClassProto(js, js_qop_writer_class_id, writer_proto.val);
JSValue mod = JS_NewObject(js); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs)); JS_SetPropertyFunctionList(js, mod.val, js_qop_funcs, countof(js_qop_funcs));
return mod; JS_RETURN(mod.val);
} }

View File

@@ -49,7 +49,6 @@ var ast = null
var folded = null var folded = null
var mcode_blob = null var mcode_blob = null
var hash = null var hash = null
var compact_mcode = null
var mach_blob = null var mach_blob = null
var compiled = null var compiled = null
var optimized = null var optimized = null
@@ -59,88 +58,7 @@ var errs = null
var ei = 0 var ei = 0
var e = null var e = null
var had_errors = false var had_errors = false
var compact_mcode = null
// Collapse leaf arrays (instruction arrays) onto single lines
var compact_arrays = function(json_text) {
var lines = array(json_text, "\n")
var result = []
var i = 0
var line = null
var trimmed = null
var collecting = false
var collected = null
var indent = null
var is_leaf = null
var j = 0
var inner = null
var parts = null
var trailing = null
var chars = null
var k = 0
while (i < length(lines)) {
line = lines[i]
trimmed = trim(line)
if (collecting == false && trimmed == "[") {
collecting = true
chars = array(line)
k = 0
while (k < length(chars) && chars[k] == " ") {
k = k + 1
}
indent = text(line, 0, k)
collected = []
i = i + 1
continue
}
if (collecting) {
if (trimmed == "]" || trimmed == "],") {
is_leaf = true
j = 0
while (j < length(collected)) {
inner = trim(collected[j])
if (starts_with(inner, "[") || starts_with(inner, "{")) {
is_leaf = false
}
j = j + 1
}
if (is_leaf && length(collected) > 0) {
parts = []
j = 0
while (j < length(collected)) {
inner = trim(collected[j])
if (ends_with(inner, ",")) {
inner = text(inner, 0, length(inner) - 1)
}
parts[] = inner
j = j + 1
}
trailing = ""
if (ends_with(trimmed, ",")) {
trailing = ","
}
result[] = `${indent}[${text(parts, ", ")}]${trailing}`
} else {
result[] = `${indent}[`
j = 0
while (j < length(collected)) {
result[] = collected[j]
j = j + 1
}
result[] = line
}
collecting = false
} else {
collected[] = line
}
i = i + 1
continue
}
result[] = line
i = i + 1
}
return text(result, "\n")
}
while (i < length(files)) { while (i < length(files)) {
entry = files[i] entry = files[i]
@@ -167,7 +85,7 @@ while (i < length(files)) {
folded = fold(ast) folded = fold(ast)
compiled = mcode(folded) compiled = mcode(folded)
optimized = streamline(compiled) optimized = streamline(compiled)
mcode_text = compact_arrays(json.encode(optimized, null, 2)) mcode_text = json.encode(optimized)
f = fd.open(entry.out, "w") f = fd.open(entry.out, "w")
fd.write(f, mcode_text) fd.write(f, mcode_text)
fd.close(f) fd.close(f)

View File

@@ -1,16 +1,16 @@
// run_native.ce — load a module both interpreted and native, compare speed // run_native.ce — load a module both interpreted and native, compare speed
// //
// Usage: // Usage:
// cell --core . run_native.ce <module> // cell --dev run_native.ce <module>
// //
// Loads <module>.cm via use() (interpreted) and <module>.dylib (native), // Loads <module>.cm via use() (interpreted) and <module>.cm.dylib (native),
// runs both and compares results and timing. // runs both and compares results and timing.
var os = use('os') var os = use('os')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --core . run_native.ce <module>') print('usage: cell --dev run_native.ce <module>')
print(' e.g. cell --core . run_native.ce num_torture') print(' e.g. cell --dev run_native.ce num_torture')
return return
} }
@@ -21,7 +21,7 @@ if (ends_with(name, '.cm')) {
var safe = replace(replace(name, '/', '_'), '-', '_') var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use' var symbol = 'js_' + safe + '_use'
var dylib_path = './' + name + '.dylib' var dylib_path = './' + name + '.cm.dylib'
var fd = use('fd') var fd = use('fd')
// --- Test argument for function-returning modules --- // --- Test argument for function-returning modules ---

78
run_native_seed.ce Normal file
View File

@@ -0,0 +1,78 @@
// run_native_seed.ce — load and run a native .dylib module (seed mode)
// Usage: ./cell --dev --seed run_native_seed benches/fibonacci
var fd = use("fd")
var os = use("os")
if (length(args) < 1) {
print("usage: cell --dev --seed run_native_seed <module>")
disrupt
}
var name = args[0]
if (ends_with(name, ".cm")) {
name = text(name, 0, length(name) - 3)
}
var safe = replace(replace(name, "/", "_"), "-", "_")
var symbol = "js_" + safe + "_use"
var dylib_path = "./" + name + ".cm.dylib"
var test_arg = 30
if (length(args) > 1) {
test_arg = number(args[1])
}
// --- Interpreted run ---
print("--- interpreted ---")
var t1 = os.now()
var mod_interp = use(name)
var t2 = os.now()
var result_interp = null
if (is_function(mod_interp)) {
print("module returns a function, calling with " + text(test_arg))
t1 = os.now()
result_interp = mod_interp(test_arg)
t2 = os.now()
}
result_interp = result_interp != null ? result_interp : mod_interp
var ms_interp = (t2 - t1) / 1000000
print("result: " + text(result_interp))
print("time: " + text(ms_interp) + " ms")
// --- Native run ---
if (!fd.is_file(dylib_path)) {
print("\nno " + dylib_path + " found")
disrupt
}
print("\n--- native ---")
var t3 = os.now()
var lib = os.dylib_open(dylib_path)
var t4 = os.now()
var mod_native = os.dylib_symbol(lib, symbol)
var t5 = os.now()
var result_native = null
if (is_function(mod_native)) {
print("module returns a function, calling with " + text(test_arg))
t4 = os.now()
result_native = mod_native(test_arg)
t5 = os.now()
}
result_native = result_native != null ? result_native : mod_native
var ms_native = (t5 - t3) / 1000000
var ms_exec = (t5 - t4) / 1000000
print("result: " + text(result_native))
print("load: " + text((t4 - t3) / 1000000) + " ms")
print("exec: " + text(ms_exec) + " ms")
print("total: " + text(ms_native) + " ms")
// --- Comparison ---
print("\n--- comparison ---")
print("match: " + text(result_interp == result_native))
if (ms_native > 0) {
print("speedup: " + text(ms_interp / ms_native) + "x (total)")
}
if (ms_exec > 0) {
print("speedup: " + text(ms_interp / ms_exec) + "x (exec only)")
}

View File

@@ -272,32 +272,43 @@ void script_startup(cell_rt *prt)
} }
// Create hidden environment // Create hidden environment
JSValue hidden_env = JS_NewObject(js); // Note: evaluate allocating calls into temporaries before passing to
JS_SetPropertyStr(js, hidden_env, "os", js_core_os_use(js)); // JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it.
JS_SetPropertyStr(js, hidden_env, "json", js_core_json_use(js)); JSGCRef env_ref;
JS_AddGCRef(js, &env_ref);
env_ref.val = JS_NewObject(js);
JSValue tmp;
tmp = js_core_os_use(js);
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
tmp = js_core_json_use(js);
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
crt->actor_sym_ref.val = JS_NewObject(js); crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null) // Always set init (even if null)
if (crt->init_wota) { if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota)); tmp = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", tmp);
free(crt->init_wota); free(crt->init_wota);
crt->init_wota = NULL; crt->init_wota = NULL;
} else { } else {
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL); JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
} }
// Set args to null for actor spawn (not CLI mode) // Set args to null for actor spawn (not CLI mode)
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL); JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL);
if (core_path) if (core_path) {
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); tmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, hidden_env, "shop_path", JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
shop_path ? JS_NewString(js, shop_path) : JS_NULL); }
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
// Stone the environment // Stone the environment
hidden_env = JS_Stone(js, hidden_env); JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_DeleteGCRef(js, &env_ref);
// Run from binary // Run from binary
crt->state = ACTOR_RUNNING; crt->state = ACTOR_RUNNING;
@@ -358,6 +369,7 @@ static void print_usage(const char *prog)
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n"); printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n"); printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
printf(" --dev Dev mode (shop=.cell, core=.)\n"); printf(" --dev Dev mode (shop=.cell, core=.)\n");
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
printf(" --seed Use seed bootstrap (minimal, for regen)\n"); printf(" --seed Use seed bootstrap (minimal, for regen)\n");
printf(" --test [heap_size] Run C test suite\n"); printf(" --test [heap_size] Run C test suite\n");
printf(" -h, --help Show this help message\n"); printf(" -h, --help Show this help message\n");
@@ -392,6 +404,7 @@ int cell_init(int argc, char **argv)
/* Default: run script through bootstrap pipeline */ /* Default: run script through bootstrap pipeline */
int arg_start = 1; int arg_start = 1;
int seed_mode = 0; int seed_mode = 0;
size_t heap_size = 1024 * 1024; /* 1MB default */
const char *shop_override = NULL; const char *shop_override = NULL;
const char *core_override = NULL; const char *core_override = NULL;
@@ -414,6 +427,17 @@ int cell_init(int argc, char **argv)
} else if (strcmp(argv[arg_start], "--seed") == 0) { } else if (strcmp(argv[arg_start], "--seed") == 0) {
seed_mode = 1; seed_mode = 1;
arg_start++; arg_start++;
} else if (strcmp(argv[arg_start], "--heap") == 0) {
if (arg_start + 1 >= argc) {
printf("ERROR: --heap requires a size argument (e.g. 1GB, 256MB, 65536)\n");
return 1;
}
char *end = NULL;
heap_size = strtoull(argv[arg_start + 1], &end, 0);
if (end && (*end == 'G' || *end == 'g')) heap_size *= 1024ULL * 1024 * 1024;
else if (end && (*end == 'M' || *end == 'm')) heap_size *= 1024ULL * 1024;
else if (end && (*end == 'K' || *end == 'k')) heap_size *= 1024ULL;
arg_start += 2;
} else if (strcmp(argv[arg_start], "--dev") == 0) { } else if (strcmp(argv[arg_start], "--dev") == 0) {
shop_override = ".cell"; shop_override = ".cell";
core_override = "."; core_override = ".";
@@ -462,7 +486,7 @@ int cell_init(int argc, char **argv)
free(bin_data); free(bin_data);
return 1; return 1;
} }
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 1024 * 1024); JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
if (!ctx) { if (!ctx) {
printf("Failed to create JS context\n"); printf("Failed to create JS context\n");
free(bin_data); JS_FreeRuntime(g_runtime); free(bin_data); JS_FreeRuntime(g_runtime);
@@ -499,21 +523,31 @@ int cell_init(int argc, char **argv)
JS_FreeValue(ctx, js_core_blob_use(ctx)); JS_FreeValue(ctx, js_core_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx); JSGCRef env_ref;
JS_SetPropertyStr(ctx, hidden_env, "os", js_core_os_use(ctx)); JS_AddGCRef(ctx, &env_ref);
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path)); env_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "shop_path", JSValue tmp;
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL); tmp = js_core_os_use(ctx);
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
JS_SetPropertyStr(ctx, hidden_env, "json", js_core_json_use(ctx)); tmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL); JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
JSValue args_arr = JS_NewArray(ctx); tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
tmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
JSGCRef args_ref;
JS_AddGCRef(ctx, &args_ref);
args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) { for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]); JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str); JS_ArrayPush(ctx, &args_ref.val, str);
} }
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr); JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
hidden_env = JS_Stone(ctx, hidden_env); JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JS_DeleteGCRef(ctx, &env_ref);
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env); JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data); free(bin_data);

View File

@@ -154,6 +154,43 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
#define countof(x) (sizeof(x)/sizeof((x)[0])) #define countof(x) (sizeof(x)/sizeof((x)[0]))
/* GC safety macros for C functions that allocate multiple heap objects.
Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC.
JS_ROOT style: explicit, use .val to access the rooted value.
JS_LOCAL style: transparent, GC updates the C local through a pointer. */
#define JS_FRAME(ctx) \
JSContext *_js_ctx = (ctx); \
JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \
JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx)
#define JS_ROOT(name, init) \
JSGCRef name; \
JS_PushGCRef(_js_ctx, &name); \
name.val = (init)
#define JS_LOCAL(name, init) \
JSValue name = (init); \
JSLocalRef name##__lr; \
name##__lr.ptr = &name; \
JS_PushLocalRef(_js_ctx, &name##__lr)
#define JS_RETURN(val) do { \
JSValue _js_ret = (val); \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return _js_ret; \
} while (0)
#define JS_RETURN_NULL() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_NULL; \
} while (0)
#define JS_RETURN_EX() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_EXCEPTION; \
} while (0)
// Common macros for property access // Common macros for property access
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\ #define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \ JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \

View File

@@ -981,6 +981,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int ret = JS_SetProperty(ctx, obj, key, val); int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt; if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break; break;
} }
@@ -1001,6 +1002,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val); JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt; if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break; break;
} }
@@ -1127,6 +1129,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
result = frame->slots[a]; result = frame->slots[a];
if (JS_IsNull(frame->caller)) goto done; if (JS_IsNull(frame->caller)) goto done;
{ {
#ifdef VALIDATE_GC
const char *callee_name = "?";
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
}
}
#endif
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL; frame->caller = JS_NULL;
frame = caller; frame = caller;
@@ -1143,8 +1156,11 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
void *rp = JS_VALUE_GET_PTR(result); void *rp = JS_VALUE_GET_PTR(result);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) { if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp)) if (!is_ct_ptr(ctx, rp))
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u\n", fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc); ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
callee_name, callee_file,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
} }
} }
#endif #endif
@@ -1180,7 +1196,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
} }
case MACH_NEWARRAY: { case MACH_NEWARRAY: {
JSValue arr = JS_NewArray(ctx); JSValue arr = JS_NewArrayCap(ctx, b);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { goto disrupt; } if (JS_IsException(arr)) { goto disrupt; }
frame->slots[a] = arr; frame->slots[a] = arr;
@@ -1474,6 +1490,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int ret = JS_SetProperty(ctx, obj, key, val); int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt; if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break; break;
} }
case MACH_LOAD_INDEX: { case MACH_LOAD_INDEX: {
@@ -1492,6 +1509,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val); JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt; if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break; break;
} }
case MACH_LOAD_DYNAMIC: { case MACH_LOAD_DYNAMIC: {
@@ -1526,12 +1544,13 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
} }
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt; if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break; break;
} }
/* New record */ /* New record */
case MACH_NEWRECORD: { case MACH_NEWRECORD: {
JSValue obj = JS_NewObject(ctx); JSValue obj = b > 0 ? JS_NewObjectCap(ctx, b) : JS_NewObject(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(obj)) goto disrupt; if (JS_IsException(obj)) goto disrupt;
frame->slots[a] = obj; frame->slots[a] = obj;
@@ -1617,9 +1636,15 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
if (JS_IsPtr(ret)) { if (JS_IsPtr(ret)) {
void *rp = JS_VALUE_GET_PTR(ret); void *rp = JS_VALUE_GET_PTR(ret);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) { if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp)) if (!is_ct_ptr(ctx, rp)) {
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d\n", int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind); void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
magic, cfp,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
} }
} }
#endif #endif
@@ -1983,11 +2008,9 @@ static int mcode_reg_items(cJSON *it, cJSON **out) {
/* record: [1]=dest, [2]=0(const) — no line/col suffix */ /* record: [1]=dest, [2]=0(const) — no line/col suffix */
if (!strcmp(op, "record")) { ADD(1); return c; } if (!strcmp(op, "record")) { ADD(1); return c; }
/* array: [1]=dest, [2]=count(const), [3..]=elements (no line/col suffix) */ /* array: [1]=dest, [2]=count(const)elements added via separate push instrs */
if (!strcmp(op, "array")) { if (!strcmp(op, "array")) {
ADD(1); ADD(1);
int cnt = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
for (int j = 0; j < cnt; j++) ADD(3 + j);
return c; return c;
} }
@@ -2043,8 +2066,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
int pinned = 1 + nr_args; int pinned = 1 + nr_args;
for (int i = 0; i < pinned; i++) { first_ref[i] = 0; last_ref[i] = n; } for (int i = 0; i < pinned; i++) { first_ref[i] = 0; last_ref[i] = n; }
for (int i = 0; i < n; i++) { { cJSON *it = instrs ? instrs->child : NULL;
cJSON *it = cJSON_GetArrayItem(instrs, i); for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue; if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS]; cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs); int rc = mcode_reg_items(it, regs);
@@ -2054,7 +2077,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
if (first_ref[s] < 0) first_ref[s] = i; if (first_ref[s] < 0) first_ref[s] = i;
last_ref[s] = i; last_ref[s] = i;
} }
} } }
/* Step 1a: extend live ranges for closure-captured slots. /* Step 1a: extend live ranges for closure-captured slots.
If a child function captures a parent slot via get/put, that slot must If a child function captures a parent slot via get/put, that slot must
@@ -2076,8 +2099,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
typedef struct { const char *name; int pos; } LabelPos; typedef struct { const char *name; int pos; } LabelPos;
int lbl_cap = 32, lbl_n = 0; int lbl_cap = 32, lbl_n = 0;
LabelPos *lbls = sys_malloc(lbl_cap * sizeof(LabelPos)); LabelPos *lbls = sys_malloc(lbl_cap * sizeof(LabelPos));
for (int i = 0; i < n; i++) { { cJSON *it = instrs ? instrs->child : NULL;
cJSON *it = cJSON_GetArrayItem(instrs, i); for (int i = 0; it; i++, it = it->next) {
if (cJSON_IsString(it)) { if (cJSON_IsString(it)) {
if (lbl_n >= lbl_cap) { if (lbl_n >= lbl_cap) {
lbl_cap *= 2; lbl_cap *= 2;
@@ -2085,23 +2108,23 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
} }
lbls[lbl_n++] = (LabelPos){it->valuestring, i}; lbls[lbl_n++] = (LabelPos){it->valuestring, i};
} }
} } }
/* Find backward jumps and extend live ranges */ /* Find backward jumps and extend live ranges */
int changed = 1; int changed = 1;
while (changed) { while (changed) {
changed = 0; changed = 0;
for (int i = 0; i < n; i++) { cJSON *it = instrs ? instrs->child : NULL;
cJSON *it = cJSON_GetArrayItem(instrs, i); for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue; if (!cJSON_IsArray(it)) continue;
int sz = cJSON_GetArraySize(it); int sz = cJSON_GetArraySize(it);
if (sz < 3) continue; if (sz < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring; const char *op = it->child->valuestring;
const char *target = NULL; const char *target = NULL;
if (!strcmp(op, "jump")) { if (!strcmp(op, "jump")) {
target = cJSON_GetArrayItem(it, 1)->valuestring; target = it->child->next->valuestring;
} else if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") || } else if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") ||
!strcmp(op, "jump_not_null")) { !strcmp(op, "jump_not_null")) {
target = cJSON_GetArrayItem(it, 2)->valuestring; target = it->child->next->next->valuestring;
} }
if (!target) continue; if (!target) continue;
/* Find label position */ /* Find label position */
@@ -2207,8 +2230,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
} }
/* Step 3: apply remap to instructions */ /* Step 3: apply remap to instructions */
for (int i = 0; i < n; i++) { { cJSON *it = instrs ? instrs->child : NULL;
cJSON *it = cJSON_GetArrayItem(instrs, i); for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue; if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS]; cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs); int rc = mcode_reg_items(it, regs);
@@ -2218,7 +2241,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
cJSON_SetNumberValue(regs[j], remap[old]); cJSON_SetNumberValue(regs[j], remap[old]);
} }
} }
} } }
/* Update nr_slots in the JSON */ /* Update nr_slots in the JSON */
cJSON_SetNumberValue(nr_slots_j, new_max); cJSON_SetNumberValue(nr_slots_j, new_max);
@@ -2250,8 +2273,8 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
s.flat_to_pc = sys_malloc((n + 1) * sizeof(int)); s.flat_to_pc = sys_malloc((n + 1) * sizeof(int));
s.flat_count = n; s.flat_count = n;
for (int i = 0; i < n; i++) { { cJSON *it = instrs ? instrs->child : NULL;
cJSON *it = cJSON_GetArrayItem(instrs, i); for (int i = 0; it; i++, it = it->next) {
s.flat_to_pc[i] = s.code_count; s.flat_to_pc[i] = s.code_count;
if (cJSON_IsString(it)) { if (cJSON_IsString(it)) {
ml_label(&s, it->valuestring); ml_label(&s, it->valuestring);
@@ -2450,15 +2473,10 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
} }
/* Array/Object creation */ /* Array/Object creation */
else if (strcmp(op, "array") == 0) { else if (strcmp(op, "array") == 0) {
int dest = A1, count = A2; EM(MACH_ABC(MACH_NEWARRAY, A1, A2, 0));
EM(MACH_ABC(MACH_NEWARRAY, dest, 0, 0));
for (int j = 0; j < count; j++) {
int elem = ml_int(it, 3 + j);
EM(MACH_ABC(MACH_PUSH, dest, elem, 0));
}
} }
else if (strcmp(op, "record") == 0) { else if (strcmp(op, "record") == 0) {
EM(MACH_ABC(MACH_NEWRECORD, A1, 0, 0)); EM(MACH_ABC(MACH_NEWRECORD, A1, A2, 0));
} }
/* Push/Pop */ /* Push/Pop */
else if (strcmp(op, "push") == 0) { else if (strcmp(op, "push") == 0) {
@@ -2551,7 +2569,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
/* Unknown opcode — emit NOP */ /* Unknown opcode — emit NOP */
EM(MACH_ABC(MACH_NOP, 0, 0, 0)); EM(MACH_ABC(MACH_NOP, 0, 0, 0));
} }
} } }
/* Sentinel for flat_to_pc */ /* Sentinel for flat_to_pc */
s.flat_to_pc[n] = s.code_count; s.flat_to_pc[n] = s.code_count;
@@ -2690,34 +2708,32 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
/* Scan main's instructions */ /* Scan main's instructions */
{ {
cJSON *main_instrs = cJSON_GetObjectItemCaseSensitive(main_obj, "instructions"); cJSON *main_instrs = cJSON_GetObjectItemCaseSensitive(main_obj, "instructions");
int mn = main_instrs ? cJSON_GetArraySize(main_instrs) : 0; cJSON *it = main_instrs ? main_instrs->child : NULL;
for (int i = 0; i < mn; i++) { for (; it; it = it->next) {
cJSON *it = cJSON_GetArrayItem(main_instrs, i);
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue; if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring; const char *op = it->child->valuestring;
if (!strcmp(op, "function")) { if (!strcmp(op, "function")) {
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble; int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count) if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = func_count; /* main */ parent_of[child_idx] = func_count; /* main */
} }
} }
} }
/* Scan each function's instructions */ /* Scan each function's instructions */
for (int fi = 0; fi < func_count; fi++) { { cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi); for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions"); cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0; cJSON *it = finstrs ? finstrs->child : NULL;
for (int i = 0; i < fn; i++) { for (; it; it = it->next) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue; if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring; const char *op = it->child->valuestring;
if (!strcmp(op, "function")) { if (!strcmp(op, "function")) {
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble; int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count) if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = fi; parent_of[child_idx] = fi;
} }
} }
} } }
/* Build per-function capture sets: for each function F, which of its slots /* Build per-function capture sets: for each function F, which of its slots
are captured by descendant functions via get/put. Captured slots must are captured by descendant functions via get/put. Captured slots must
@@ -2727,17 +2743,16 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
memset(cap_slots, 0, (func_count + 1) * sizeof(int *)); memset(cap_slots, 0, (func_count + 1) * sizeof(int *));
memset(cap_counts, 0, (func_count + 1) * sizeof(int)); memset(cap_counts, 0, (func_count + 1) * sizeof(int));
for (int fi = 0; fi < func_count; fi++) { { cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi); for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions"); cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0; cJSON *it = finstrs ? finstrs->child : NULL;
for (int i = 0; i < fn; i++) { for (; it; it = it->next) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue; if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring; const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue; if (strcmp(op, "get") && strcmp(op, "put")) continue;
int slot = (int)cJSON_GetArrayItem(it, 2)->valuedouble; int slot = (int)it->child->next->next->valuedouble;
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble; int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain to find the ancestor whose slot is referenced */ /* Walk up parent chain to find the ancestor whose slot is referenced */
int ancestor = fi; int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++) for (int l = 0; l < level && ancestor >= 0; l++)
@@ -2753,7 +2768,7 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
cap_slots[ancestor][cap_counts[ancestor]++] = slot; cap_slots[ancestor][cap_counts[ancestor]++] = slot;
} }
} }
} } }
/* Compress registers for functions that exceed 8-bit slot limits. /* Compress registers for functions that exceed 8-bit slot limits.
Save remap tables so we can fix get/put parent_slot references. */ Save remap tables so we can fix get/put parent_slot references. */
@@ -2761,9 +2776,11 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
int *remap_sizes = sys_malloc((func_count + 1) * sizeof(int)); int *remap_sizes = sys_malloc((func_count + 1) * sizeof(int));
memset(remaps, 0, (func_count + 1) * sizeof(int *)); memset(remaps, 0, (func_count + 1) * sizeof(int *));
for (int i = 0; i < func_count; i++) { cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
remaps[i] = mcode_compress_regs(cJSON_GetArrayItem(funcs_arr, i), for (int i = 0; fobj; i++, fobj = fobj->next)
remaps[i] = mcode_compress_regs(fobj,
&remap_sizes[i], cap_slots[i], cap_counts[i]); &remap_sizes[i], cap_slots[i], cap_counts[i]);
}
/* main is stored at index func_count in our arrays */ /* main is stored at index func_count in our arrays */
remaps[func_count] = mcode_compress_regs(main_obj, remaps[func_count] = mcode_compress_regs(main_obj,
&remap_sizes[func_count], cap_slots[func_count], cap_counts[func_count]); &remap_sizes[func_count], cap_slots[func_count], cap_counts[func_count]);
@@ -2775,16 +2792,15 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
sys_free(cap_counts); sys_free(cap_counts);
/* Fix up get/put parent_slot references using ancestor remap tables */ /* Fix up get/put parent_slot references using ancestor remap tables */
for (int fi = 0; fi < func_count; fi++) { { cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi); for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions"); cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0; cJSON *it = finstrs ? finstrs->child : NULL;
for (int i = 0; i < fn; i++) { for (; it; it = it->next) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue; if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring; const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue; if (strcmp(op, "get") && strcmp(op, "put")) continue;
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble; int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain 'level' times to find ancestor */ /* Walk up parent chain 'level' times to find ancestor */
int ancestor = fi; int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++) { for (int l = 0; l < level && ancestor >= 0; l++) {
@@ -2793,14 +2809,14 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
if (ancestor < 0) continue; /* unknown parent — leave as is */ if (ancestor < 0) continue; /* unknown parent — leave as is */
int *anc_remap = remaps[ancestor]; int *anc_remap = remaps[ancestor];
if (!anc_remap) continue; /* ancestor wasn't compressed */ if (!anc_remap) continue; /* ancestor wasn't compressed */
cJSON *slot_item = cJSON_GetArrayItem(it, 2); cJSON *slot_item = it->child->next->next;
int old_slot = (int)slot_item->valuedouble; int old_slot = (int)slot_item->valuedouble;
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) { if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
int new_slot = anc_remap[old_slot]; int new_slot = anc_remap[old_slot];
cJSON_SetNumberValue(slot_item, new_slot); cJSON_SetNumberValue(slot_item, new_slot);
} }
} }
} } }
/* Free remap tables */ /* Free remap tables */
for (int i = 0; i <= func_count; i++) for (int i = 0; i <= func_count; i++)
@@ -2814,8 +2830,10 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
if (func_count > 0) { if (func_count > 0) {
compiled = sys_malloc(func_count * sizeof(MachCode *)); compiled = sys_malloc(func_count * sizeof(MachCode *));
memset(compiled, 0, func_count * sizeof(MachCode *)); memset(compiled, 0, func_count * sizeof(MachCode *));
for (int i = 0; i < func_count; i++) { cJSON *fobj = funcs_arr->child;
compiled[i] = mcode_lower_func(cJSON_GetArrayItem(funcs_arr, i), filename); for (int i = 0; fobj; i++, fobj = fobj->next)
compiled[i] = mcode_lower_func(fobj, filename);
}
} }
/* Compile main */ /* Compile main */

View File

@@ -9,6 +9,15 @@
#include "quickjs-internal.h" #include "quickjs-internal.h"
#include <math.h> #include <math.h>
/* Non-inline wrappers for static inline functions in quickjs.h */
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) */ /* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
enum { enum {
QBE_CMP_EQ = 0, QBE_CMP_EQ = 0,
@@ -42,6 +51,16 @@ JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_add); 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_ThrowTypeError(ctx, "cannot add incompatible types");
return JS_NULL;
}
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) { JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_sub); return qbe_float_binop(ctx, a, b, op_sub);
} }
@@ -446,6 +465,15 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a != b); return JS_NewBool(ctx, a != b);
} }
/* --- 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;
}
/* --- Disruption --- */ /* --- Disruption --- */
void cell_rt_disrupt(JSContext *ctx) { void cell_rt_disrupt(JSContext *ctx) {

View File

@@ -156,7 +156,8 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
}; };
JSValue js_core_actor_use(JSContext *js) { JSValue js_core_actor_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
} JS_RETURN(mod.val);
}

View File

@@ -845,6 +845,18 @@ static inline objhdr_t *chase(JSValue v) {
return oh; return oh;
} }
/* Resolve a forward pointer in-place. After rec_resize the old record
gets a forward header; any JSValue slot still pointing at it must be
updated to follow the chain to the live copy. */
static inline void mach_resolve_forward(JSValue *slot) {
if (JS_IsPtr(*slot)) {
objhdr_t h = *(objhdr_t *)JS_VALUE_GET_PTR(*slot);
if (objhdr_type(h) == OBJ_FORWARD) {
*slot = JS_MKPTR(objhdr_fwd_ptr(h));
}
}
}
/* Inline type checks — use these in the VM dispatch loop to avoid /* Inline type checks — use these in the VM dispatch loop to avoid
function call overhead. The public API (JS_IsArray etc. in quickjs.h) function call overhead. The public API (JS_IsArray etc. in quickjs.h)
remains non-inline for external callers; those wrappers live in runtime.c. */ remains non-inline for external callers; those wrappers live in runtime.c. */
@@ -1070,6 +1082,11 @@ struct JSContext {
uint8_t *heap_end; /* end of block */ uint8_t *heap_end; /* end of block */
size_t current_block_size; /* current block size (64KB initially) */ size_t current_block_size; /* current block size (64KB initially) */
size_t next_block_size; /* doubles if <10% recovered after GC */ size_t next_block_size; /* doubles if <10% recovered after GC */
int gc_poor_streak; /* consecutive poor-recovery GC cycles */
/* GC stats (lightweight, always on) */
uint64_t gc_count; /* number of GC cycles */
uint64_t gc_bytes_copied; /* total bytes copied across all GCs */
/* Constant text pool — compilation constants */ /* Constant text pool — compilation constants */
uint8_t *ct_base; /* pool base */ uint8_t *ct_base; /* pool base */
@@ -1092,6 +1109,7 @@ struct JSContext {
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */ CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
int class_count; /* size of class_array and class_proto */ int class_count; /* size of class_array and class_proto */
@@ -1185,7 +1203,15 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
/* Helper to check if a pointer is in constant text pool memory */ /* Helper to check if a pointer is in constant text pool memory */
static inline int is_ct_ptr (JSContext *ctx, void *ptr) { static inline int is_ct_ptr (JSContext *ctx, void *ptr) {
return (uint8_t *)ptr >= ctx->ct_base && (uint8_t *)ptr < ctx->ct_end; uint8_t *p = (uint8_t *)ptr;
if (p >= ctx->ct_base && p < ctx->ct_end) return 1;
/* Also check overflow pages */
CTPage *page = (CTPage *)ctx->ct_pages;
while (page) {
if (p >= page->data && p < page->data + page->size) return 1;
page = page->next;
}
return 0;
} }
#ifdef HEAP_CHECK #ifdef HEAP_CHECK

View File

@@ -146,10 +146,22 @@ typedef struct JSGCRef {
struct JSGCRef *prev; struct JSGCRef *prev;
} JSGCRef; } JSGCRef;
/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */
typedef struct JSLocalRef {
JSValue *ptr;
struct JSLocalRef *prev;
} JSLocalRef;
/* stack of JSGCRef */ /* stack of JSGCRef */
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref); JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref); JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref);
/* JS_FRAME/JS_ROOT/JS_LOCAL helpers (for use from cell.h macros) */
JSGCRef *JS_GetGCFrame(JSContext *ctx);
JSLocalRef *JS_GetLocalFrame(JSContext *ctx);
void JS_PushLocalRef(JSContext *ctx, JSLocalRef *ref);
void JS_RestoreFrame(JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame);
#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0) #define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0)
#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref) #define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)
@@ -502,9 +514,11 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto, JSClassID class_i
JSValue JS_NewObjectClass (JSContext *ctx, int class_id); JSValue JS_NewObjectClass (JSContext *ctx, int class_id);
JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto); JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto);
JSValue JS_NewObject (JSContext *ctx); JSValue JS_NewObject (JSContext *ctx);
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n);
JSValue JS_NewArray (JSContext *ctx); JSValue JS_NewArray (JSContext *ctx);
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len); JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len);
JSValue JS_NewArrayCap (JSContext *ctx, uint32_t cap);
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values); JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values);
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val); int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj); JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);

View File

@@ -183,6 +183,25 @@ void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) {
} }
} }
/* JS_FRAME/JS_ROOT/JS_LOCAL helper functions */
JSGCRef *JS_GetGCFrame (JSContext *ctx) {
return ctx->top_gc_ref;
}
JSLocalRef *JS_GetLocalFrame (JSContext *ctx) {
return ctx->top_local_ref;
}
void JS_PushLocalRef (JSContext *ctx, JSLocalRef *ref) {
ref->prev = ctx->top_local_ref;
ctx->top_local_ref = ref;
}
void JS_RestoreFrame (JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame) {
ctx->top_gc_ref = gc_frame;
ctx->top_local_ref = local_frame;
}
void *ct_alloc (JSContext *ctx, size_t bytes, size_t align) { void *ct_alloc (JSContext *ctx, size_t bytes, size_t align) {
/* Align the request */ /* Align the request */
bytes = (bytes + align - 1) & ~(align - 1); bytes = (bytes + align - 1) & ~(align - 1);
@@ -1300,8 +1319,6 @@ JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *f
for (;;) { for (;;) {
void *ptr = JS_VALUE_GET_PTR (v); void *ptr = JS_VALUE_GET_PTR (v);
if (is_ct_ptr (ctx, ptr)) return v;
if (!ptr_in_range (ptr, from_base, from_end)) return v; if (!ptr_in_range (ptr, from_base, from_end)) return v;
objhdr_t *hdr_ptr = (objhdr_t *)ptr; objhdr_t *hdr_ptr = (objhdr_t *)ptr;
@@ -1608,6 +1625,14 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end);
} }
/* Copy JS_LOCAL roots (update C locals through pointers) */
#ifdef DUMP_GC_DETAIL
printf(" roots: top_local_ref\n"); fflush(stdout);
#endif
for (JSLocalRef *ref = ctx->top_local_ref; ref != NULL; ref = ref->prev) {
*ref->ptr = gc_copy_value (ctx, *ref->ptr, from_base, from_end, to_base, &to_free, to_end);
}
/* Copy JS_AddGCRef/JS_DeleteGCRef roots */ /* Copy JS_AddGCRef/JS_DeleteGCRef roots */
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: last_gc_ref\n"); fflush(stdout); printf(" roots: last_gc_ref\n"); fflush(stdout);
@@ -1651,6 +1676,10 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
/* Update context with new block */ /* Update context with new block */
size_t new_used = to_free - to_base; size_t new_used = to_free - to_base;
/* Update GC stats */
ctx->gc_count++;
ctx->gc_bytes_copied += new_used;
size_t recovered = old_used > new_used ? old_used - new_used : 0; size_t recovered = old_used > new_used ? old_used - new_used : 0;
ctx->heap_base = to_base; ctx->heap_base = to_base;
@@ -1670,19 +1699,23 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
} }
#endif #endif
/* If <20% recovered, double next block size for future allocations /* If <40% recovered, grow next block size for future allocations.
But only if allow_grow is set (i.e., GC was triggered due to low space) */ First poor recovery: double. Consecutive poor: quadruple. */
#ifdef DUMP_GC #ifdef DUMP_GC
int will_grow = 0; int will_grow = 0;
#endif #endif
if (allow_grow && recovered > 0 && old_used > 0 && recovered < old_used / 5) { if (allow_grow && recovered > 0 && old_used > 0 && recovered < old_used * 2 / 5) {
size_t doubled = new_size * 2; size_t factor = ctx->gc_poor_streak >= 1 ? 4 : 2;
if (doubled <= buddy_max_block(&ctx->rt->buddy)) { size_t grown = new_size * factor;
ctx->next_block_size = doubled; if (grown <= buddy_max_block(&ctx->rt->buddy)) {
ctx->next_block_size = grown;
#ifdef DUMP_GC #ifdef DUMP_GC
will_grow = 1; will_grow = 1;
#endif #endif
} }
ctx->gc_poor_streak++;
} else {
ctx->gc_poor_streak = 0;
} }
#ifdef DUMP_GC #ifdef DUMP_GC
@@ -1828,6 +1861,20 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
/* Initialize per-context execution state (moved from JSRuntime) */ /* Initialize per-context execution state (moved from JSRuntime) */
ctx->current_exception = JS_NULL; ctx->current_exception = JS_NULL;
/* Initialize constant text pool (avoids overflow pages for common case) */
{
size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */
ctx->ct_base = js_malloc_rt (ct_pool_size);
if (!ctx->ct_base) {
js_free_rt (ctx->class_array);
js_free_rt (ctx->class_proto);
js_free_rt (ctx);
return NULL;
}
ctx->ct_free = ctx->ct_base;
ctx->ct_end = ctx->ct_base + ct_pool_size;
}
/* Initialize constant text intern table */ /* Initialize constant text intern table */
ctx->ct_pages = NULL; ctx->ct_pages = NULL;
ctx->ct_array = NULL; ctx->ct_array = NULL;
@@ -1917,6 +1964,7 @@ void JS_FreeContext (JSContext *ctx) {
/* Free constant text pool and intern table */ /* Free constant text pool and intern table */
ct_free_all (ctx); ct_free_all (ctx);
if (ctx->ct_base) js_free_rt (ctx->ct_base);
js_free_rt (ctx->ct_hash); js_free_rt (ctx->ct_hash);
js_free_rt (ctx->ct_array); js_free_rt (ctx->ct_array);
@@ -2109,8 +2157,24 @@ static JSValue js_sub_string_val (JSContext *ctx, JSValue src, int start, int en
return js_new_string8_len (ctx, buf, len); return js_new_string8_len (ctx, buf, len);
} }
/* Heap string — fast path for short ASCII substrings (avoids heap alloc) */
JSText *p = JS_VALUE_GET_STRING (src);
if (len <= MIST_ASCII_MAX_LEN) {
char buf[MIST_ASCII_MAX_LEN];
int all_ascii = 1;
for (int i = 0; i < len; i++) {
uint32_t c = string_get (p, start + i);
if (c >= 0x80) { all_ascii = 0; break; }
buf[i] = (char)c;
}
if (all_ascii) {
JSValue imm = MIST_TryNewImmediateASCII (buf, len);
if (!JS_IsNull (imm)) return imm;
}
}
/* Heap string — delegate to existing js_sub_string */ /* Heap string — delegate to existing js_sub_string */
return js_sub_string (ctx, JS_VALUE_GET_STRING (src), start, end); return js_sub_string (ctx, p, start, end);
} }
/* Allocate a new pretext (mutable JSText) with initial capacity */ /* Allocate a new pretext (mutable JSText) with initial capacity */
@@ -2619,11 +2683,51 @@ JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) {
JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); } JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); }
/* Create array with pre-allocated capacity but len=0 (for push-fill patterns) */
JSValue JS_NewArrayCap (JSContext *ctx, uint32_t cap) {
if (cap == 0) cap = JS_ARRAY_INITIAL_SIZE;
size_t values_size = sizeof (JSValue) * cap;
size_t total_size = sizeof (JSArray) + values_size;
JSArray *arr = js_malloc (ctx, total_size);
if (!arr) return JS_EXCEPTION;
arr->mist_hdr = objhdr_make (cap, OBJ_ARRAY, false, false, false, false);
arr->len = 0;
for (uint32_t i = 0; i < cap; i++)
arr->values[i] = JS_NULL;
return JS_MKPTR (arr);
}
JSValue JS_NewObject (JSContext *ctx) { JSValue JS_NewObject (JSContext *ctx) {
/* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */
return JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); return JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT);
} }
/* Create object with pre-allocated hash table for n properties */
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n) {
/* slot 0 is reserved, so need n+1 slots minimum.
Hash table needs ~2x entries for good load factor.
mask must be power-of-2 minus 1. */
uint32_t need = (n + 1) * 2;
uint32_t mask = JS_RECORD_INITIAL_MASK;
while (mask + 1 < need) mask = (mask << 1) | 1;
JSGCRef proto_ref;
JS_PushGCRef (ctx, &proto_ref);
proto_ref.val = ctx->class_proto[JS_CLASS_OBJECT];
JSRecord *rec = js_new_record_class (ctx, mask, JS_CLASS_OBJECT);
JSValue proto_val = proto_ref.val;
JS_PopGCRef (ctx, &proto_ref);
if (!rec) return JS_EXCEPTION;
if (JS_IsRecord (proto_val))
rec->proto = proto_val;
return JS_MKPTR (rec);
}
/* Note: at least 'length' arguments will be readable in 'argv' */ /* Note: at least 'length' arguments will be readable in 'argv' */
static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) {
@@ -4383,6 +4487,10 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj,
ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
} }
#ifdef VALIDATE_GC
uint8_t *pre_heap_base = ctx->heap_base;
#endif
switch (cproto) { switch (cproto) {
case JS_CFUNC_generic: case JS_CFUNC_generic:
ret_val = func.generic (ctx, this_obj, argc, arg_copy); ret_val = func.generic (ctx, this_obj, argc, arg_copy);
@@ -4449,6 +4557,21 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj,
abort (); abort ();
} }
#ifdef VALIDATE_GC
if (ctx->heap_base != pre_heap_base && JS_IsPtr (ret_val)) {
void *rp = JS_VALUE_GET_PTR (ret_val);
if (!is_ct_ptr (ctx, rp) &&
((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free)) {
/* Note: f is stale after GC (func_obj was passed by value), so we
cannot read f->name here. Just report the pointer. */
fprintf (stderr, "VALIDATE_GC: C function returned stale ptr=%p "
"heap=[%p,%p) after GC\n", rp,
(void *)ctx->heap_base, (void *)ctx->heap_free);
fflush (stderr);
}
}
#endif
ctx->c_call_root = root.prev; ctx->c_call_root = root.prev;
if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
@@ -5356,19 +5479,27 @@ static JSValue cjson_to_jsvalue (JSContext *ctx, const cJSON *item) {
if (cJSON_IsString (item)) return JS_NewString (ctx, item->valuestring); if (cJSON_IsString (item)) return JS_NewString (ctx, item->valuestring);
if (cJSON_IsArray (item)) { if (cJSON_IsArray (item)) {
int n = cJSON_GetArraySize (item); int n = cJSON_GetArraySize (item);
JSValue arr = JS_NewArrayLen (ctx,n); JSGCRef arr_ref;
JS_AddGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen (ctx, n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
cJSON *child = cJSON_GetArrayItem (item, i); cJSON *child = cJSON_GetArrayItem (item, i);
JS_SetPropertyNumber (ctx, arr, i, cjson_to_jsvalue (ctx, child)); JS_SetPropertyNumber (ctx, arr_ref.val, i, cjson_to_jsvalue (ctx, child));
} }
return arr; JSValue result = arr_ref.val;
JS_DeleteGCRef (ctx, &arr_ref);
return result;
} }
if (cJSON_IsObject (item)) { if (cJSON_IsObject (item)) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_AddGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
for (cJSON *child = item->child; child; child = child->next) { for (cJSON *child = item->child; child; child = child->next) {
JS_SetPropertyStr (ctx, obj, child->string, cjson_to_jsvalue (ctx, child)); JS_SetPropertyStr (ctx, obj_ref.val, child->string, cjson_to_jsvalue (ctx, child));
} }
return obj; JSValue result = obj_ref.val;
JS_DeleteGCRef (ctx, &obj_ref);
return result;
} }
return JS_NULL; return JS_NULL;
} }
@@ -7857,8 +7988,8 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
JSGCRef result_ref; JSGCRef result_ref;
JS_PushGCRef (ctx, &result_ref); /* Push first - sets val to JS_NULL */ JS_PushGCRef (ctx, &result_ref);
result_ref.val = JS_NewArray (ctx); /* Then assign */ result_ref.val = JS_NewArrayLen (ctx, len);
if (JS_IsException (result_ref.val)) { if (JS_IsException (result_ref.val)) {
JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg1_ref);
@@ -7866,17 +7997,23 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
return result_ref.val; return result_ref.val;
} }
int out_idx = 0;
#define MAP_STORE(val) do { \
JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val); \
out->values[out_idx++] = (val); \
} while(0)
#define MAP_ERR() do { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } while(0)
if (arity >= 2) { if (arity >= 2) {
if (reverse) { if (reverse) {
for (int i = len - 1; i >= 0; i--) { for (int i = len - 1; i >= 0; i--) {
/* Re-chase input array each iteration */
arr = JS_VALUE_GET_ARRAY (arg0_ref.val); arr = JS_VALUE_GET_ARRAY (arg0_ref.val);
if (i >= (int)arr->len) continue; /* array may have shrunk */ if (i >= (int)arr->len) continue;
JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) };
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } MAP_STORE (val);
} }
} else { } else {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@@ -7884,9 +8021,9 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
if (i >= (int)arr->len) break; if (i >= (int)arr->len) break;
JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) };
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } MAP_STORE (val);
} }
} }
} else { } else {
@@ -7896,9 +8033,9 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
if (i >= (int)arr->len) continue; if (i >= (int)arr->len) continue;
JSValue item = arr->values[i]; JSValue item = arr->values[i];
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } MAP_STORE (val);
} }
} else { } else {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@@ -7906,12 +8043,17 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
if (i >= (int)arr->len) break; if (i >= (int)arr->len) break;
JSValue item = arr->values[i]; JSValue item = arr->values[i];
JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0);
if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (JS_IsException (val)) { MAP_ERR (); }
if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break;
if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } MAP_STORE (val);
} }
} }
} }
#undef MAP_STORE
#undef MAP_ERR
/* Truncate if early exit produced fewer elements */
JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val);
out->len = out_idx;
JSValue result = result_ref.val; JSValue result = result_ref.val;
JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg1_ref);
@@ -8018,7 +8160,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
JS_PushGCRef (ctx, &arr_ref); JS_PushGCRef (ctx, &arr_ref);
JS_PushGCRef (ctx, &str_ref); JS_PushGCRef (ctx, &str_ref);
str_ref.val = arg; str_ref.val = arg;
arr_ref.val = JS_NewArray (ctx); arr_ref.val = JS_NewArrayLen (ctx, len);
if (JS_IsException (arr_ref.val)) { if (JS_IsException (arr_ref.val)) {
JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &str_ref);
JS_PopGCRef (ctx, &arr_ref); JS_PopGCRef (ctx, &arr_ref);
@@ -8031,7 +8173,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
JS_PopGCRef (ctx, &arr_ref); JS_PopGCRef (ctx, &arr_ref);
return JS_EXCEPTION; return JS_EXCEPTION;
} }
JS_ArrayPush (ctx, &arr_ref.val, ch); JS_SetPropertyNumber (ctx, arr_ref.val, i, ch);
} }
JSValue result = arr_ref.val; JSValue result = arr_ref.val;
JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &str_ref);
@@ -9636,6 +9778,22 @@ static JSValue js_mach_dump_mcode (JSContext *ctx, JSValue this_val, int argc, J
return JS_NULL; return JS_NULL;
} }
/* gc_stats() — return {count, bytes_copied, heap_size, ct_pages} and reset counters */
static JSValue js_gc_stats (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
JSValue obj = JS_NewObject (ctx);
if (JS_IsException (obj)) return obj;
JS_SetPropertyStr (ctx, obj, "count", JS_NewInt64 (ctx, (int64_t)ctx->gc_count));
JS_SetPropertyStr (ctx, obj, "bytes_copied", JS_NewInt64 (ctx, (int64_t)ctx->gc_bytes_copied));
JS_SetPropertyStr (ctx, obj, "heap_size", JS_NewInt64 (ctx, (int64_t)ctx->current_block_size));
/* Count CT overflow pages */
int ct_page_count = 0;
for (CTPage *p = (CTPage *)ctx->ct_pages; p; p = p->next) ct_page_count++;
JS_SetPropertyStr (ctx, obj, "ct_pages", JS_NewInt32 (ctx, ct_page_count));
ctx->gc_count = 0;
ctx->gc_bytes_copied = 0;
return obj;
}
/* mach_compile_mcode_bin(name, mcode_json) - compile mcode IR to serialized binary blob */ /* mach_compile_mcode_bin(name, mcode_json) - compile mcode IR to serialized binary blob */
static JSValue js_mach_compile_mcode_bin (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { static JSValue js_mach_compile_mcode_bin (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1])) if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1]))
@@ -10128,16 +10286,13 @@ JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) { JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) {
JSGCRef arr_ref; JSGCRef arr_ref;
JS_PushGCRef (ctx, &arr_ref); JS_PushGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArray (ctx); arr_ref.val = JS_NewArrayLen (ctx, count);
if (JS_IsException (arr_ref.val)) { if (JS_IsException (arr_ref.val)) {
JS_PopGCRef (ctx, &arr_ref); JS_PopGCRef (ctx, &arr_ref);
return JS_EXCEPTION; return JS_EXCEPTION;
} }
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
if (JS_ArrayPush (ctx, &arr_ref.val, values[i]) < 0) { JS_SetPropertyNumber (ctx, arr_ref.val, i, values[i]);
JS_PopGCRef (ctx, &arr_ref);
return JS_EXCEPTION;
}
} }
JSValue result = arr_ref.val; JSValue result = arr_ref.val;
JS_PopGCRef (ctx, &arr_ref); JS_PopGCRef (ctx, &arr_ref);
@@ -10731,6 +10886,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
js_set_global_cfunc(ctx, "mach_eval_mcode", js_mach_eval_mcode, 3); js_set_global_cfunc(ctx, "mach_eval_mcode", js_mach_eval_mcode, 3);
js_set_global_cfunc(ctx, "mach_dump_mcode", js_mach_dump_mcode, 3); js_set_global_cfunc(ctx, "mach_dump_mcode", js_mach_dump_mcode, 3);
js_set_global_cfunc(ctx, "mach_compile_mcode_bin", js_mach_compile_mcode_bin, 2); js_set_global_cfunc(ctx, "mach_compile_mcode_bin", js_mach_compile_mcode_bin, 2);
js_set_global_cfunc(ctx, "gc_stats", js_gc_stats, 0);
js_set_global_cfunc(ctx, "stone", js_cell_stone, 1); js_set_global_cfunc(ctx, "stone", js_cell_stone, 1);
js_set_global_cfunc(ctx, "length", js_cell_length, 1); js_set_global_cfunc(ctx, "length", js_cell_length, 1);
js_set_global_cfunc(ctx, "call", js_cell_call, 3); js_set_global_cfunc(ctx, "call", js_cell_call, 3);
@@ -11009,7 +11165,7 @@ static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue
break; break;
case NOTA_ARR: case NOTA_ARR:
nota = nota_read_array (&n, nota); nota = nota_read_array (&n, nota);
*tmp = JS_NewArray (js); *tmp = JS_NewArrayLen (js, n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver); nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver);
JS_SetPropertyNumber (js, *tmp, i, ret2); JS_SetPropertyNumber (js, *tmp, i, ret2);
@@ -11879,9 +12035,13 @@ static const JSCFunctionListEntry js_math_radians_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) }; JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_radians_use (JSContext *ctx) { JSValue js_core_math_radians_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs)); JS_PushGCRef (ctx, &obj_ref);
return obj; obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_radians_funcs, countof (js_math_radians_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
} }
/* ============================================================================ /* ============================================================================
@@ -11945,9 +12105,13 @@ static const JSCFunctionListEntry js_math_degrees_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) }; JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_degrees_use (JSContext *ctx) { JSValue js_core_math_degrees_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs)); JS_PushGCRef (ctx, &obj_ref);
return obj; obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_degrees_funcs, countof (js_math_degrees_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
} }
/* ============================================================================ /* ============================================================================
@@ -12010,9 +12174,13 @@ static const JSCFunctionListEntry js_math_cycles_funcs[]
JS_CFUNC_DEF ("e", 1, js_math_e) }; JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_cycles_use (JSContext *ctx) { JSValue js_core_math_cycles_use (JSContext *ctx) {
JSValue obj = JS_NewObject (ctx); JSGCRef obj_ref;
JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs)); JS_PushGCRef (ctx, &obj_ref);
return obj; obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_cycles_funcs, countof (js_math_cycles_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
} }
/* Public API: get stack trace as cJSON array */ /* Public API: get stack trace as cJSON array */
cJSON *JS_GetStack(JSContext *ctx) { cJSON *JS_GetStack(JSContext *ctx) {

View File

@@ -60,6 +60,26 @@ var streamline = function(ir, log) {
is_record: T_RECORD is_record: T_RECORD
} }
// simplify_algebra dispatch tables
var self_true_ops = {
eq_int: true, eq_float: true, eq_text: true, eq_bool: true,
is_identical: true,
le_int: true, le_float: true, le_text: true,
ge_int: true, ge_float: true, ge_text: true
}
var self_false_ops = {
ne_int: true, ne_float: true, ne_text: true, ne_bool: true,
lt_int: true, lt_float: true, lt_text: true,
gt_int: true, gt_float: true, gt_text: true
}
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,
return: true, disrupt: true,
store_field: true, store_index: true, store_dynamic: true,
push: true, setarg: true, invoke: true, tail_invoke: true
}
// --- Logging support --- // --- Logging support ---
var ir_stats = null var ir_stats = null
@@ -119,49 +139,30 @@ var streamline = function(ir, log) {
return T_UNKNOWN return T_UNKNOWN
} }
// track_types reuses write_rules table; move handled specially
var track_types = function(slot_types, instr) { var track_types = function(slot_types, instr) {
var op = instr[0] var op = instr[0]
var rule = null
var src_type = null var src_type = null
if (op == "access") { var typ = null
slot_types[text(instr[1])] = access_value_type(instr[2]) if (op == "move") {
} else if (op == "int") { src_type = slot_types[instr[2]]
slot_types[text(instr[1])] = T_INT slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
} else if (op == "true" || op == "false") { return null
slot_types[text(instr[1])] = T_BOOL }
} else if (op == "null") { rule = write_rules[op]
slot_types[text(instr[1])] = T_NULL if (rule != null) {
} else if (op == "move") { typ = rule[1]
src_type = slot_types[text(instr[2])] if (typ == null) {
slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN typ = access_value_type(instr[2])
} else if (op == "concat") { }
slot_types[text(instr[1])] = T_TEXT slot_types[instr[rule[0]]] = typ
} else if (bool_result_ops[op] == true) {
slot_types[text(instr[1])] = T_BOOL
} else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "invoke" || op == "tail_invoke") {
slot_types[text(instr[2])] = T_UNKNOWN
} else if (op == "pop" || op == "get") {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "array") {
slot_types[text(instr[1])] = T_ARRAY
} else if (op == "record") {
slot_types[text(instr[1])] = T_RECORD
} else if (op == "function") {
slot_types[text(instr[1])] = T_FUNCTION
} else if (op == "length") {
slot_types[text(instr[1])] = T_INT
} else if (op == "negate" || numeric_ops[op] == true) {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "bitnot" || op == "bitand" || op == "bitor" ||
op == "bitxor" || op == "shl" || op == "shr" || op == "ushr") {
slot_types[text(instr[1])] = T_INT
} }
return null return null
} }
var slot_is = function(slot_types, slot, typ) { var slot_is = function(slot_types, slot, typ) {
var known = slot_types[text(slot)] var known = slot_types[slot]
if (known == null) { if (known == null) {
return false return false
} }
@@ -175,24 +176,22 @@ var streamline = function(ir, log) {
} }
var merge_backward = function(backward_types, slot, typ) { var merge_backward = function(backward_types, slot, typ) {
var sk = null
var existing = null var existing = null
if (!is_number(slot)) { if (!is_number(slot)) {
return null return null
} }
sk = text(slot) existing = backward_types[slot]
existing = backward_types[sk]
if (existing == null) { if (existing == null) {
backward_types[sk] = typ backward_types[slot] = typ
} else if (existing != typ && existing != T_UNKNOWN) { } else if (existing != typ && existing != T_UNKNOWN) {
if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) { if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) {
// Keep more specific // Keep more specific
} else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) { } else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) {
backward_types[sk] = typ backward_types[slot] = typ
} else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) { } else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) {
backward_types[sk] = T_NUM backward_types[slot] = T_NUM
} else { } else {
backward_types[sk] = T_UNKNOWN backward_types[slot] = T_UNKNOWN
} }
} }
return null return null
@@ -201,8 +200,8 @@ var streamline = function(ir, log) {
var seed_params = function(slot_types, param_types, nr_args) { var seed_params = function(slot_types, param_types, nr_args) {
var j = 1 var j = 1
while (j <= nr_args) { while (j <= nr_args) {
if (param_types[text(j)] != null) { if (param_types[j] != null) {
slot_types[text(j)] = param_types[text(j)] slot_types[j] = param_types[j]
} }
j = j + 1 j = j + 1
} }
@@ -210,10 +209,11 @@ var streamline = function(ir, log) {
} }
var seed_writes = function(slot_types, write_types) { var seed_writes = function(slot_types, write_types) {
var keys = array(write_types)
var k = 0 var k = 0
while (k < length(keys)) { while (k < length(write_types)) {
slot_types[keys[k]] = write_types[keys[k]] if (write_types[k] != null) {
slot_types[k] = write_types[k]
}
k = k + 1 k = k + 1
} }
return null return null
@@ -222,7 +222,35 @@ var streamline = function(ir, log) {
// ========================================================= // =========================================================
// Pass: infer_param_types — backward type inference // Pass: infer_param_types — backward type inference
// Scans typed operators to infer immutable parameter types. // Scans typed operators to infer immutable parameter types.
// Uses data-driven dispatch: each rule is [pos1, type1] or
// [pos1, type1, pos2, type2] for operand positions to merge.
// ========================================================= // =========================================================
var param_rules = {
subtract: [2, T_NUM, 3, T_NUM], multiply: [2, T_NUM, 3, T_NUM],
divide: [2, T_NUM, 3, T_NUM], modulo: [2, T_NUM, 3, T_NUM],
pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM],
eq_int: [2, T_INT, 3, T_INT], ne_int: [2, T_INT, 3, T_INT],
lt_int: [2, T_INT, 3, T_INT], gt_int: [2, T_INT, 3, T_INT],
le_int: [2, T_INT, 3, T_INT], ge_int: [2, T_INT, 3, T_INT],
bitand: [2, T_INT, 3, T_INT], bitor: [2, T_INT, 3, T_INT],
bitxor: [2, T_INT, 3, T_INT], shl: [2, T_INT, 3, T_INT],
shr: [2, T_INT, 3, T_INT], ushr: [2, T_INT, 3, T_INT],
bitnot: [2, T_INT],
eq_float: [2, T_FLOAT, 3, T_FLOAT], ne_float: [2, T_FLOAT, 3, T_FLOAT],
lt_float: [2, T_FLOAT, 3, T_FLOAT], gt_float: [2, T_FLOAT, 3, T_FLOAT],
le_float: [2, T_FLOAT, 3, T_FLOAT], ge_float: [2, T_FLOAT, 3, T_FLOAT],
concat: [2, T_TEXT, 3, T_TEXT],
eq_text: [2, T_TEXT, 3, T_TEXT], ne_text: [2, T_TEXT, 3, T_TEXT],
lt_text: [2, T_TEXT, 3, T_TEXT], gt_text: [2, T_TEXT, 3, T_TEXT],
le_text: [2, T_TEXT, 3, T_TEXT], ge_text: [2, T_TEXT, 3, T_TEXT],
eq_bool: [2, T_BOOL, 3, T_BOOL], ne_bool: [2, T_BOOL, 3, T_BOOL],
not: [2, T_BOOL], and: [2, T_BOOL, 3, T_BOOL], or: [2, T_BOOL, 3, T_BOOL],
store_index: [1, T_ARRAY, 2, T_INT], store_field: [1, T_RECORD],
push: [1, T_ARRAY],
load_index: [2, T_ARRAY, 3, T_INT], load_field: [2, T_RECORD],
pop: [2, T_ARRAY]
}
var infer_param_types = function(func) { var infer_param_types = function(func) {
var instructions = func.instructions var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_args = func.nr_args != null ? func.nr_args : 0
@@ -232,76 +260,36 @@ var streamline = function(ir, log) {
var i = 0 var i = 0
var j = 0 var j = 0
var instr = null var instr = null
var op = null
var bt = null var bt = null
var rule = null
if (instructions == null || nr_args == 0) { if (instructions == null || nr_args == 0) {
return {} return array(func.nr_slots)
} }
num_instr = length(instructions) num_instr = length(instructions)
backward_types = {} backward_types = array(func.nr_slots)
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (is_array(instr)) { if (is_array(instr)) {
op = instr[0] rule = param_rules[instr[0]]
if (op == "subtract" || op == "multiply" || if (rule != null) {
op == "divide" || op == "modulo" || op == "pow") { merge_backward(backward_types, instr[rule[0]], rule[1])
merge_backward(backward_types, instr[2], T_NUM) if (length(rule) > 2) {
merge_backward(backward_types, instr[3], T_NUM) merge_backward(backward_types, instr[rule[2]], rule[3])
} else if (op == "negate") { }
merge_backward(backward_types, instr[2], T_NUM)
} else if (op == "eq_int" || op == "ne_int" || op == "lt_int" ||
op == "gt_int" || op == "le_int" || op == "ge_int" ||
op == "bitand" || op == "bitor" || op == "bitxor" ||
op == "shl" || op == "shr" || op == "ushr") {
merge_backward(backward_types, instr[2], T_INT)
merge_backward(backward_types, instr[3], T_INT)
} else if (op == "bitnot") {
merge_backward(backward_types, instr[2], T_INT)
} else if (op == "eq_float" || op == "ne_float" || op == "lt_float" ||
op == "gt_float" || op == "le_float" || op == "ge_float") {
merge_backward(backward_types, instr[2], T_FLOAT)
merge_backward(backward_types, instr[3], T_FLOAT)
} else if (op == "concat" ||
op == "eq_text" || op == "ne_text" || op == "lt_text" ||
op == "gt_text" || op == "le_text" || op == "ge_text") {
merge_backward(backward_types, instr[2], T_TEXT)
merge_backward(backward_types, instr[3], T_TEXT)
} else if (op == "eq_bool" || op == "ne_bool") {
merge_backward(backward_types, instr[2], T_BOOL)
merge_backward(backward_types, instr[3], T_BOOL)
} else if (op == "not") {
merge_backward(backward_types, instr[2], T_BOOL)
} else if (op == "and" || op == "or") {
merge_backward(backward_types, instr[2], T_BOOL)
merge_backward(backward_types, instr[3], T_BOOL)
} else if (op == "store_index") {
merge_backward(backward_types, instr[1], T_ARRAY)
merge_backward(backward_types, instr[2], T_INT)
} else if (op == "store_field") {
merge_backward(backward_types, instr[1], T_RECORD)
} else if (op == "push") {
merge_backward(backward_types, instr[1], T_ARRAY)
} else if (op == "load_index") {
merge_backward(backward_types, instr[2], T_ARRAY)
merge_backward(backward_types, instr[3], T_INT)
} else if (op == "load_field") {
merge_backward(backward_types, instr[2], T_RECORD)
} else if (op == "pop") {
merge_backward(backward_types, instr[2], T_ARRAY)
} }
} }
i = i + 1 i = i + 1
} }
param_types = {} param_types = array(func.nr_slots)
j = 1 j = 1
while (j <= nr_args) { while (j <= nr_args) {
bt = backward_types[text(j)] bt = backward_types[j]
if (bt != null && bt != T_UNKNOWN) { if (bt != null && bt != T_UNKNOWN) {
param_types[text(j)] = bt param_types[j] = bt
} }
j = j + 1 j = j + 1
} }
@@ -313,114 +301,85 @@ var streamline = function(ir, log) {
// Scans all instructions to find non-parameter slots where // Scans all instructions to find non-parameter slots where
// every write produces the same type. These types persist // every write produces the same type. These types persist
// across label join points. // across label join points.
// Uses data-driven dispatch: each rule is [dest_pos, type].
// ========================================================= // =========================================================
var write_rules = {
int: [1, T_INT], true: [1, T_BOOL], false: [1, T_BOOL],
null: [1, T_NULL], access: [1, null],
array: [1, T_ARRAY], record: [1, T_RECORD],
function: [1, T_FUNCTION], length: [1, T_INT],
bitnot: [1, T_INT], bitand: [1, T_INT], bitor: [1, T_INT],
bitxor: [1, T_INT], shl: [1, T_INT], shr: [1, T_INT], ushr: [1, T_INT],
negate: [1, T_UNKNOWN], concat: [1, T_TEXT],
eq: [1, T_BOOL], ne: [1, T_BOOL], lt: [1, T_BOOL],
le: [1, T_BOOL], gt: [1, T_BOOL], ge: [1, T_BOOL], in: [1, T_BOOL],
add: [1, T_UNKNOWN], subtract: [1, T_UNKNOWN], multiply: [1, T_UNKNOWN],
divide: [1, T_UNKNOWN], modulo: [1, T_UNKNOWN], pow: [1, T_UNKNOWN],
move: [1, T_UNKNOWN], load_field: [1, T_UNKNOWN],
load_index: [1, T_UNKNOWN], load_dynamic: [1, T_UNKNOWN],
pop: [1, T_UNKNOWN], get: [1, T_UNKNOWN],
invoke: [2, T_UNKNOWN], tail_invoke: [2, T_UNKNOWN],
eq_int: [1, T_BOOL], ne_int: [1, T_BOOL], lt_int: [1, T_BOOL],
gt_int: [1, T_BOOL], le_int: [1, T_BOOL], ge_int: [1, T_BOOL],
eq_float: [1, T_BOOL], ne_float: [1, T_BOOL], lt_float: [1, T_BOOL],
gt_float: [1, T_BOOL], le_float: [1, T_BOOL], ge_float: [1, T_BOOL],
eq_text: [1, T_BOOL], ne_text: [1, T_BOOL], lt_text: [1, T_BOOL],
gt_text: [1, T_BOOL], le_text: [1, T_BOOL], ge_text: [1, T_BOOL],
eq_bool: [1, T_BOOL], ne_bool: [1, T_BOOL],
eq_tol: [1, T_BOOL], ne_tol: [1, T_BOOL],
not: [1, T_BOOL], and: [1, T_BOOL], or: [1, T_BOOL],
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]
}
var infer_slot_write_types = function(func) { var infer_slot_write_types = function(func) {
var instructions = func.instructions var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_args = func.nr_args != null ? func.nr_args : 0
var num_instr = 0 var num_instr = 0
var write_types = null var write_types = null
var result = null
var keys = null
var i = 0 var i = 0
var k = 0 var k = 0
var instr = null var instr = null
var op = null
var slot = 0 var slot = 0
var typ = null var typ = null
var wt = null var rule = null
if (instructions == null) { if (instructions == null) {
return {} return array(func.nr_slots)
} }
num_instr = length(instructions) num_instr = length(instructions)
write_types = {} write_types = array(func.nr_slots)
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (!is_array(instr)) { if (is_array(instr)) {
i = i + 1 rule = write_rules[instr[0]]
continue if (rule != null) {
slot = instr[rule[0]]
typ = rule[1]
if (typ == null) {
typ = access_value_type(instr[2])
}
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ)
}
}
} }
op = instr[0]
slot = -1
typ = null
if (op == "int") {
slot = instr[1]
typ = T_INT
} else if (op == "true" || op == "false") {
slot = instr[1]
typ = T_BOOL
} else if (op == "null") {
slot = instr[1]
typ = T_NULL
} else if (op == "access") {
slot = instr[1]
typ = access_value_type(instr[2])
} else if (op == "array") {
slot = instr[1]
typ = T_ARRAY
} else if (op == "record") {
slot = instr[1]
typ = T_RECORD
} else if (op == "function") {
slot = instr[1]
typ = T_FUNCTION
} else if (op == "length") {
slot = instr[1]
typ = T_INT
} else if (op == "bitnot" || op == "bitand" ||
op == "bitor" || op == "bitxor" || op == "shl" ||
op == "shr" || op == "ushr") {
slot = instr[1]
typ = T_INT
} else if (op == "negate") {
slot = instr[1]
typ = T_UNKNOWN
} else if (op == "concat") {
slot = instr[1]
typ = T_TEXT
} else if (bool_result_ops[op] == true) {
slot = instr[1]
typ = T_BOOL
} else if (op == "eq" || op == "ne" || op == "lt" ||
op == "le" || op == "gt" || op == "ge" || op == "in") {
slot = instr[1]
typ = T_BOOL
} else if (op == "add" || op == "subtract" || op == "multiply" ||
op == "divide" || op == "modulo" || op == "pow") {
slot = instr[1]
typ = T_UNKNOWN
} else if (op == "move" || op == "load_field" || op == "load_index" ||
op == "load_dynamic" || op == "pop" || op == "get") {
slot = instr[1]
typ = T_UNKNOWN
} else if (op == "invoke" || op == "tail_invoke") {
slot = instr[2]
typ = T_UNKNOWN
}
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ != null ? typ : T_UNKNOWN)
}
i = i + 1 i = i + 1
} }
// Filter to only slots with known (non-unknown) types // Filter to only slots with known (non-unknown) types
result = {}
keys = array(write_types)
k = 0 k = 0
while (k < length(keys)) { while (k < length(write_types)) {
wt = write_types[keys[k]] if (write_types[k] == T_UNKNOWN) {
if (wt != null && wt != T_UNKNOWN) { write_types[k] = null
result[keys[k]] = wt
} }
k = k + 1 k = k + 1
} }
return result return write_types
} }
// ========================================================= // =========================================================
@@ -431,9 +390,8 @@ var streamline = function(ir, log) {
var eliminate_type_checks = function(func, param_types, write_types, log) { var eliminate_type_checks = function(func, param_types, write_types, log) {
var instructions = func.instructions var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_args = func.nr_args != null ? func.nr_args : 0
var has_params = false
var has_writes = false
var num_instr = 0 var num_instr = 0
var base_types = null
var slot_types = null var slot_types = null
var nc = 0 var nc = 0
var i = 0 var i = 0
@@ -460,35 +418,32 @@ var streamline = function(ir, log) {
} }
num_instr = length(instructions) num_instr = length(instructions)
// Pre-compute base types: params + write-invariant types
base_types = array(func.nr_slots)
j = 1 j = 1
while (j <= nr_args) { while (j <= nr_args) {
if (param_types[text(j)] != null) { if (param_types[j] != null) {
has_params = true base_types[j] = param_types[j]
}
j = j + 1
}
j = 0
while (j < length(write_types)) {
if (write_types[j] != null) {
base_types[j] = write_types[j]
} }
j = j + 1 j = j + 1
} }
has_writes = length(array(write_types)) > 0
slot_types = {} slot_types = array(base_types)
if (has_params) {
seed_params(slot_types, param_types, nr_args)
}
if (has_writes) {
seed_writes(slot_types, write_types)
}
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (is_text(instr)) { if (is_text(instr)) {
slot_types = {} slot_types = array(base_types)
if (has_params) {
seed_params(slot_types, param_types, nr_args)
}
if (has_writes) {
seed_writes(slot_types, write_types)
}
i = i + 1 i = i + 1
continue continue
} }
@@ -525,14 +480,14 @@ var streamline = function(ir, log) {
at: i, at: i,
before: [instr, next], before: [instr, next],
after: [instructions[i], instructions[i + 1]], after: [instructions[i], instructions[i + 1]],
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type} why: {slot: src, known_type: slot_types[src], checked_type: checked_type}
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
src_known = slot_types[text(src)] src_known = slot_types[src]
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) { if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) { if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
nc = nc + 1 nc = nc + 1
@@ -550,7 +505,7 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} why: {slot: src, known_type: src_known, checked_type: checked_type}
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
@@ -569,12 +524,12 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} why: {slot: src, known_type: src_known, checked_type: checked_type}
} }
} }
slot_types[text(dest)] = T_UNKNOWN slot_types[dest] = T_UNKNOWN
i = i + 2 i = i + 2
continue continue
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
slot_types[text(src)] = checked_type slot_types[src] = checked_type
i = i + 2 i = i + 2
continue continue
} }
@@ -594,14 +549,14 @@ var streamline = function(ir, log) {
at: i, at: i,
before: [instr, next], before: [instr, next],
after: [instructions[i], instructions[i + 1]], after: [instructions[i], instructions[i + 1]],
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type} why: {slot: src, known_type: slot_types[src], checked_type: checked_type}
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
src_known = slot_types[text(src)] src_known = slot_types[src]
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) { if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) { if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
nc = nc + 1 nc = nc + 1
@@ -619,7 +574,7 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} why: {slot: src, known_type: src_known, checked_type: checked_type}
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
@@ -638,17 +593,17 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} why: {slot: src, known_type: src_known, checked_type: checked_type}
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 1 i = i + 1
continue continue
} }
@@ -664,7 +619,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_field", rule: "dynamic_to_field",
at: i, before: old_op, after: instr[0], at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]} why: {slot: instr[3], known_type: slot_types[instr[3]]}
} }
} }
} else if (slot_is(slot_types, instr[3], T_INT)) { } else if (slot_is(slot_types, instr[3], T_INT)) {
@@ -675,11 +630,11 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_index", rule: "dynamic_to_index",
at: i, before: old_op, after: instr[0], at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]} why: {slot: instr[3], known_type: slot_types[instr[3]]}
} }
} }
} }
slot_types[text(instr[1])] = T_UNKNOWN slot_types[instr[1]] = T_UNKNOWN
i = i + 1 i = i + 1
continue continue
} }
@@ -693,7 +648,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_field", rule: "dynamic_to_field",
at: i, before: old_op, after: instr[0], at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]} why: {slot: instr[3], known_type: slot_types[instr[3]]}
} }
} }
} else if (slot_is(slot_types, instr[3], T_INT)) { } else if (slot_is(slot_types, instr[3], T_INT)) {
@@ -704,7 +659,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_index", rule: "dynamic_to_index",
at: i, before: old_op, after: instr[0], at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]} why: {slot: instr[3], known_type: slot_types[instr[3]]}
} }
} }
} }
@@ -745,14 +700,14 @@ var streamline = function(ir, log) {
} }
num_instr = length(instructions) num_instr = length(instructions)
slot_values = {} slot_values = array(func.nr_slots)
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (is_text(instr)) { if (is_text(instr)) {
slot_values = {} slot_values = array(func.nr_slots)
i = i + 1 i = i + 1
continue continue
} }
@@ -766,28 +721,25 @@ var streamline = function(ir, log) {
// Track known constant values // Track known constant values
if (op == "int") { if (op == "int") {
slot_values[text(instr[1])] = instr[2] slot_values[instr[1]] = instr[2]
} else if (op == "access" && is_number(instr[2])) { } else if (op == "access" && is_number(instr[2])) {
slot_values[text(instr[1])] = instr[2] slot_values[instr[1]] = instr[2]
} else if (op == "true") { } else if (op == "true") {
slot_values[text(instr[1])] = true slot_values[instr[1]] = true
} else if (op == "false") { } else if (op == "false") {
slot_values[text(instr[1])] = false slot_values[instr[1]] = false
} else if (op == "move") { } else if (op == "move") {
sv = slot_values[text(instr[2])] sv = slot_values[instr[2]]
if (sv != null) { if (sv != null) {
slot_values[text(instr[1])] = sv slot_values[instr[1]] = sv
} else { } else {
slot_values[text(instr[1])] = null slot_values[instr[1]] = null
} }
} }
// Same-slot comparisons // Same-slot comparisons
if (is_number(instr[2]) && instr[2] == instr[3]) { if (is_number(instr[2]) && instr[2] == instr[3]) {
if (op == "eq_int" || op == "eq_float" || op == "eq_text" || if (self_true_ops[op] == true) {
op == "eq_bool" || op == "is_identical" ||
op == "le_int" || op == "le_float" || op == "le_text" ||
op == "ge_int" || op == "ge_float" || op == "ge_text") {
instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]] instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) { if (events != null) {
events[] = { events[] = {
@@ -797,14 +749,11 @@ var streamline = function(ir, log) {
why: {op: op, slot: instr[2]} why: {op: op, slot: instr[2]}
} }
} }
slot_values[text(instr[1])] = true slot_values[instr[1]] = true
i = i + 1 i = i + 1
continue continue
} }
if (op == "ne_int" || op == "ne_float" || op == "ne_text" || if (self_false_ops[op] == true) {
op == "ne_bool" ||
op == "lt_int" || op == "lt_float" || op == "lt_text" ||
op == "gt_int" || op == "gt_float" || op == "gt_text") {
instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]] instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) { if (events != null) {
events[] = { events[] = {
@@ -814,7 +763,7 @@ var streamline = function(ir, log) {
why: {op: op, slot: instr[2]} why: {op: op, slot: instr[2]}
} }
} }
slot_values[text(instr[1])] = false slot_values[instr[1]] = false
i = i + 1 i = i + 1
continue continue
} }
@@ -822,15 +771,10 @@ var streamline = function(ir, log) {
// Clear value tracking for dest-producing ops (not reads-only) // Clear value tracking for dest-producing ops (not reads-only)
if (op == "invoke" || op == "tail_invoke") { if (op == "invoke" || op == "tail_invoke") {
slot_values[text(instr[2])] = null slot_values[instr[2]] = null
} else if (op != "int" && op != "access" && op != "true" && } else if (no_clear_ops[op] != true) {
op != "false" && op != "move" && op != "null" &&
op != "jump" && op != "jump_true" && op != "jump_false" &&
op != "jump_not_null" && op != "return" && op != "disrupt" &&
op != "store_field" && op != "store_index" &&
op != "store_dynamic" && op != "push" && op != "setarg") {
if (is_number(instr[1])) { if (is_number(instr[1])) {
slot_values[text(instr[1])] = null slot_values[instr[1]] = null
} }
} }

View File

@@ -5,6 +5,9 @@ var now = time.now
var computer_zone = time.computer_zone var computer_zone = time.computer_zone
var computer_dst = time.computer_dst var computer_dst = time.computer_dst
//delete time.now
//delete time.computer_zone
//delete time.computer_dst
time.second = 1 time.second = 1
time.minute = 60 time.minute = 60

View File

@@ -1,72 +1,11 @@
var tokenize = function(src, filename) { var tokenize = function(src, filename) {
var len = length(src) var len = length(src)
var cp = array(array(src), codepoint)
var pos = 0 var pos = 0
var row = 0 var row = 0
var col = 0 var col = 0
var tokens = [] var tokens = []
// Codepoint constants
def CP_LF = 10
def CP_CR = 13
def CP_TAB = 9
def CP_SPACE = 32
def CP_BANG = 33
def CP_DQUOTE = 34
def CP_HASH = 35
def CP_DOLLAR = 36
def CP_PERCENT = 37
def CP_AMP = 38
def CP_SQUOTE = 39
def CP_LPAREN = 40
def CP_RPAREN = 41
def CP_STAR = 42
def CP_PLUS = 43
def CP_COMMA = 44
def CP_MINUS = 45
def CP_DOT = 46
def CP_SLASH = 47
def CP_0 = 48
def CP_1 = 49
def CP_7 = 55
def CP_9 = 57
def CP_COLON = 58
def CP_SEMI = 59
def CP_LT = 60
def CP_EQ = 61
def CP_GT = 62
def CP_QMARK = 63
def CP_AT = 64
def CP_A = 65
def CP_B = 66
def CP_E = 69
def CP_F = 70
def CP_O = 79
def CP_X = 88
def CP_Z = 90
def CP_LBRACKET = 91
def CP_BSLASH = 92
def CP_RBRACKET = 93
def CP_CARET = 94
def CP_UNDERSCORE = 95
def CP_BACKTICK = 96
def CP_a = 97
def CP_b = 98
def CP_e = 101
def CP_f = 102
def CP_n = 110
def CP_o = 111
def CP_r = 114
def CP_t = 116
def CP_u = 117
def CP_x = 120
def CP_z = 122
def CP_LBRACE = 123
def CP_PIPE = 124
def CP_RBRACE = 125
def CP_TILDE = 126
// Keywords lookup // Keywords lookup
var keywords = { var keywords = {
if: "if", in: "in", do: "do", go: "go", if: "if", in: "in", do: "do", go: "go",
@@ -78,21 +17,27 @@ var tokenize = function(src, filename) {
disruption: "disruption" disruption: "disruption"
} }
var escape_map = {
n: "\n", t: "\t", r: "\r", "\\": "\\",
"'": "'", "\"": "\"", "`": "`",
"0": character(0)
}
var pk = function() { var pk = function() {
if (pos >= len) return -1 if (pos >= len) return null
return cp[pos] return src[pos]
} }
var pk_at = function(n) { var pk_at = function(n) {
var idx = pos + n var idx = pos + n
if (idx >= len) return -1 if (idx >= len) return null
return cp[idx] return src[idx]
} }
var adv = function() { var adv = function() {
var c = cp[pos] var c = src[pos]
pos = pos + 1 pos = pos + 1
if (c == CP_LF) { if (c == "\n") {
row = row + 1 row = row + 1
col = 0 col = 0
} else { } else {
@@ -102,17 +47,17 @@ var tokenize = function(src, filename) {
} }
var is_digit = function(c) { var is_digit = function(c) {
return c >= CP_0 && c <= CP_9 return c >= "0" && c <= "9"
} }
var is_hex = function(c) { var is_hex = function(c) {
return (c >= CP_0 && c <= CP_9) || (c >= CP_a && c <= CP_f) || (c >= CP_A && c <= CP_F) return (c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F")
} }
var hex_val = function(c) { var hex_val = function(c) {
if (c >= CP_0 && c <= CP_9) return c - CP_0 if (c >= "0" && c <= "9") return codepoint(c) - codepoint("0")
if (c >= CP_a && c <= CP_f) return c - CP_a + 10 if (c >= "a" && c <= "f") return codepoint(c) - codepoint("a") + 10
if (c >= CP_A && c <= CP_F) return c - CP_A + 10 if (c >= "A" && c <= "F") return codepoint(c) - codepoint("A") + 10
return 0 return 0
} }
@@ -127,7 +72,7 @@ var tokenize = function(src, filename) {
} }
var is_alpha = function(c) { var is_alpha = function(c) {
return (c >= CP_a && c <= CP_z) || (c >= CP_A && c <= CP_Z) return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z")
} }
var is_alnum = function(c) { var is_alnum = function(c) {
@@ -135,41 +80,36 @@ var tokenize = function(src, filename) {
} }
var is_ident_start = function(c) { var is_ident_start = function(c) {
return is_alpha(c) || c == CP_UNDERSCORE || c == CP_DOLLAR return is_alpha(c) || c == "_" || c == "$"
} }
var is_ident_char = function(c) { var is_ident_char = function(c) {
return is_alnum(c) || c == CP_UNDERSCORE || c == CP_DOLLAR || c == CP_QMARK || c == CP_BANG return is_alnum(c) || c == "_" || c == "$" || c == "?" || c == "!"
} }
var substr = function(start, end) { var substr = function(start, end) {
return text(src, start, end) return text(src, start, end)
} }
var read_string = function(quote_cp) { var read_string = function(quote) {
var start = pos var start = pos
var start_row = row var start_row = row
var start_col = col var start_col = col
var parts = [] var parts = []
var run_start = 0 var run_start = 0
var esc = 0 var esc = null
var esc_val = null
adv() // skip opening quote adv() // skip opening quote
run_start = pos run_start = pos
while (pos < len && pk() != quote_cp) { while (pos < len && pk() != quote) {
if (pk() == CP_BSLASH) { if (pk() == "\\") {
if (pos > run_start) push(parts, text(src, run_start, pos)) if (pos > run_start) push(parts, text(src, run_start, pos))
adv() adv()
esc = adv() esc = adv()
if (esc == CP_n) { push(parts, "\n") } esc_val = escape_map[esc]
else if (esc == CP_t) { push(parts, "\t") } if (esc_val != null) { push(parts, esc_val) }
else if (esc == CP_r) { push(parts, "\r") } else if (esc == "u") { push(parts, read_unicode_escape()) }
else if (esc == CP_BSLASH) { push(parts, "\\") } else { push(parts, esc) }
else if (esc == CP_SQUOTE) { push(parts, "'") }
else if (esc == CP_DQUOTE) { push(parts, "\"") }
else if (esc == CP_0) { push(parts, character(0)) }
else if (esc == CP_BACKTICK) { push(parts, "`") }
else if (esc == CP_u) { push(parts, read_unicode_escape()) }
else { push(parts, character(esc)) }
run_start = pos run_start = pos
} else { } else {
adv() adv()
@@ -192,33 +132,33 @@ var tokenize = function(src, filename) {
var parts = [] var parts = []
var run_start = 0 var run_start = 0
var depth = 0 var depth = 0
var tc = 0 var tc = null
var q = 0 var q = null
var interp_start = 0 var interp_start = 0
adv() // skip opening backtick adv() // skip opening backtick
run_start = pos run_start = pos
while (pos < len && pk() != CP_BACKTICK) { while (pos < len && pk() != "`") {
if (pk() == CP_BSLASH && pos + 1 < len) { if (pk() == "\\" && pos + 1 < len) {
if (pos > run_start) push(parts, text(src, run_start, pos)) if (pos > run_start) push(parts, text(src, run_start, pos))
push(parts, text(src, pos, pos + 2)) push(parts, text(src, pos, pos + 2))
adv(); adv() adv(); adv()
run_start = pos run_start = pos
} else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) { } else if (pk() == "$" && pos + 1 < len && pk_at(1) == "{") {
if (pos > run_start) push(parts, text(src, run_start, pos)) if (pos > run_start) push(parts, text(src, run_start, pos))
interp_start = pos interp_start = pos
adv(); adv() // $ { adv(); adv() // $ {
depth = 1 depth = 1
while (pos < len && depth > 0) { while (pos < len && depth > 0) {
tc = pk() tc = pk()
if (tc == CP_LBRACE) { depth = depth + 1; adv() } if (tc == "{") { depth = depth + 1; adv() }
else if (tc == CP_RBRACE) { else if (tc == "}") {
depth = depth - 1 depth = depth - 1
adv() adv()
} }
else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) { else if (tc == "'" || tc == "\"" || tc == "`") {
q = adv() q = adv()
while (pos < len && pk() != q) { while (pos < len && pk() != q) {
if (pk() == CP_BSLASH && pos + 1 < len) adv() if (pk() == "\\" && pos + 1 < len) adv()
adv() adv()
} }
if (pos < len) adv() if (pos < len) adv()
@@ -245,24 +185,24 @@ var tokenize = function(src, filename) {
var start_row = row var start_row = row
var start_col = col var start_col = col
var raw = "" var raw = ""
if (pk() == CP_0 && (pk_at(1) == CP_x || pk_at(1) == CP_X)) { if (pk() == "0" && (pk_at(1) == "x" || pk_at(1) == "X")) {
adv(); adv() adv(); adv()
while (pos < len && (is_hex(pk()) || pk() == CP_UNDERSCORE)) adv() while (pos < len && (is_hex(pk()) || pk() == "_")) adv()
} else if (pk() == CP_0 && (pk_at(1) == CP_b || pk_at(1) == CP_B)) { } else if (pk() == "0" && (pk_at(1) == "b" || pk_at(1) == "B")) {
adv(); adv() adv(); adv()
while (pos < len && (pk() == CP_0 || pk() == CP_1 || pk() == CP_UNDERSCORE)) adv() while (pos < len && (pk() == "0" || pk() == "1" || pk() == "_")) adv()
} else if (pk() == CP_0 && (pk_at(1) == CP_o || pk_at(1) == CP_O)) { } else if (pk() == "0" && (pk_at(1) == "o" || pk_at(1) == "O")) {
adv(); adv() adv(); adv()
while (pos < len && pk() >= CP_0 && pk() <= CP_7) adv() while (pos < len && pk() >= "0" && pk() <= "7") adv()
} else { } else {
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv() while (pos < len && (is_digit(pk()) || pk() == "_")) adv()
if (pos < len && pk() == CP_DOT) { if (pos < len && pk() == ".") {
adv() adv()
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv() while (pos < len && (is_digit(pk()) || pk() == "_")) adv()
} }
if (pos < len && (pk() == CP_e || pk() == CP_E)) { if (pos < len && (pk() == "e" || pk() == "E")) {
adv() adv()
if (pos < len && (pk() == CP_PLUS || pk() == CP_MINUS)) adv() if (pos < len && (pk() == "+" || pk() == "-")) adv()
while (pos < len && is_digit(pk())) adv() while (pos < len && is_digit(pk())) adv()
} }
} }
@@ -305,12 +245,12 @@ var tokenize = function(src, filename) {
var start_row = row var start_row = row
var start_col = col var start_col = col
var raw = "" var raw = ""
if (pk_at(1) == CP_SLASH) { if (pk_at(1) == "/") {
while (pos < len && pk() != CP_LF && pk() != CP_CR) adv() while (pos < len && pk() != "\n" && pk() != "\r") adv()
} else { } else {
adv(); adv() // skip /* adv(); adv() // skip /*
while (pos < len) { while (pos < len) {
if (pk() == CP_STAR && pk_at(1) == CP_SLASH) { if (pk() == "*" && pk_at(1) == "/") {
adv(); adv() adv(); adv()
break break
} }
@@ -359,144 +299,144 @@ var tokenize = function(src, filename) {
var start_row = 0 var start_row = 0
var start_col = 0 var start_col = 0
var raw = "" var raw = ""
if (c == -1) return false if (c == null) return false
if (c == CP_LF) { if (c == "\n") {
start = pos; start_row = row; start_col = col start = pos; start_row = row; start_col = col
adv() adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" }) push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
return true return true
} }
if (c == CP_CR) { if (c == "\r") {
start = pos; start_row = row; start_col = col start = pos; start_row = row; start_col = col
adv() adv()
if (pos < len && pk() == CP_LF) adv() if (pos < len && pk() == "\n") adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" }) push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
return true return true
} }
if (c == CP_SPACE || c == CP_TAB) { if (c == " " || c == "\t") {
start = pos; start_row = row; start_col = col start = pos; start_row = row; start_col = col
while (pos < len && (pk() == CP_SPACE || pk() == CP_TAB)) adv() while (pos < len && (pk() == " " || pk() == "\t")) adv()
raw = substr(start, pos) raw = substr(start, pos)
push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw }) push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw })
return true return true
} }
if (c == CP_SQUOTE || c == CP_DQUOTE) { read_string(c); return true } if (c == "'" || c == "\"") { read_string(c); return true }
if (c == CP_BACKTICK) { read_template(); return true } if (c == "`") { read_template(); return true }
if (is_digit(c)) { read_number(); return true } if (is_digit(c)) { read_number(); return true }
if (c == CP_DOT && is_digit(pk_at(1))) { read_number(); return true } if (c == "." && is_digit(pk_at(1))) { read_number(); return true }
if (is_ident_start(c)) { read_name(); return true } if (is_ident_start(c)) { read_name(); return true }
if (c == CP_SLASH) { if (c == "/") {
if (pk_at(1) == CP_SLASH || pk_at(1) == CP_STAR) { read_comment(); return true } if (pk_at(1) == "/" || pk_at(1) == "*") { read_comment(); return true }
if (pk_at(1) == CP_EQ) { emit_op("/=", 2); return true } if (pk_at(1) == "=") { emit_op("/=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("/", 1); return true emit_op("/", 1); return true
} }
if (c == CP_STAR) { if (c == "*") {
if (pk_at(1) == CP_STAR) { if (pk_at(1) == "*") {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("**=", 3); return true } if (pk_at(2) == "=") { emit_op("**=", 3); return true }
emit_op("**", 2); return true emit_op("**", 2); return true
} }
if (pk_at(1) == CP_EQ) { emit_op("*=", 2); return true } if (pk_at(1) == "=") { emit_op("*=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("*", 1); return true emit_op("*", 1); return true
} }
if (c == CP_PERCENT) { if (c == "%") {
if (pk_at(1) == CP_EQ) { emit_op("%=", 2); return true } if (pk_at(1) == "=") { emit_op("%=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("%", 1); return true emit_op("%", 1); return true
} }
if (c == CP_PLUS) { if (c == "+") {
if (pk_at(1) == CP_EQ) { emit_op("+=", 2); return true } if (pk_at(1) == "=") { emit_op("+=", 2); return true }
if (pk_at(1) == CP_PLUS) { emit_op("++", 2); return true } if (pk_at(1) == "+") { emit_op("++", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("+", 1); return true emit_op("+", 1); return true
} }
if (c == CP_MINUS) { if (c == "-") {
if (pk_at(1) == CP_EQ) { emit_op("-=", 2); return true } if (pk_at(1) == "=") { emit_op("-=", 2); return true }
if (pk_at(1) == CP_MINUS) { emit_op("--", 2); return true } if (pk_at(1) == "-") { emit_op("--", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("-", 1); return true emit_op("-", 1); return true
} }
if (c == CP_LT) { if (c == "<") {
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(1) == "=" && pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(1) == CP_EQ) { emit_op("<=", 2); return true } if (pk_at(1) == "=") { emit_op("<=", 2); return true }
if (pk_at(1) == CP_LT) { if (pk_at(1) == "<") {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("<<=", 3); return true } if (pk_at(2) == "=") { emit_op("<<=", 3); return true }
emit_op("<<", 2); return true emit_op("<<", 2); return true
} }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("<", 1); return true emit_op("<", 1); return true
} }
if (c == CP_GT) { if (c == ">") {
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(1) == "=" && pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(1) == CP_EQ) { emit_op(">=", 2); return true } if (pk_at(1) == "=") { emit_op(">=", 2); return true }
if (pk_at(1) == CP_GT) { if (pk_at(1) == ">") {
if (pk_at(2) == CP_GT) { if (pk_at(2) == ">") {
if (pk_at(3) == CP_BANG) { emit_ident(4); return true } if (pk_at(3) == "!") { emit_ident(4); return true }
if (pk_at(3) == CP_EQ) { emit_op(">>>=", 4); return true } if (pk_at(3) == "=") { emit_op(">>>=", 4); return true }
emit_op(">>>", 3); return true emit_op(">>>", 3); return true
} }
if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op(">>=", 3); return true } if (pk_at(2) == "=") { emit_op(">>=", 3); return true }
emit_op(">>", 2); return true emit_op(">>", 2); return true
} }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op(">", 1); return true emit_op(">", 1); return true
} }
if (c == CP_EQ) { if (c == "=") {
if (pk_at(1) == CP_EQ) { if (pk_at(1) == "=") {
if (pk_at(2) == CP_EQ) { emit_op("===", 3); return true } if (pk_at(2) == "=") { emit_op("===", 3); return true }
emit_op("==", 2); return true emit_op("==", 2); return true
} }
if (pk_at(1) == CP_GT) { emit_op("=>", 2); return true } if (pk_at(1) == ">") { emit_op("=>", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("=", 1); return true emit_op("=", 1); return true
} }
if (c == CP_BANG) { if (c == "!") {
if (pk_at(1) == CP_EQ) { if (pk_at(1) == "=") {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("!==", 3); return true } if (pk_at(2) == "=") { emit_op("!==", 3); return true }
emit_op("!=", 2); return true emit_op("!=", 2); return true
} }
emit_op("!", 1); return true emit_op("!", 1); return true
} }
if (c == CP_AMP) { if (c == "&") {
if (pk_at(1) == CP_AMP) { if (pk_at(1) == "&") {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("&&=", 3); return true } if (pk_at(2) == "=") { emit_op("&&=", 3); return true }
emit_op("&&", 2); return true emit_op("&&", 2); return true
} }
if (pk_at(1) == CP_EQ) { emit_op("&=", 2); return true } if (pk_at(1) == "=") { emit_op("&=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("&", 1); return true emit_op("&", 1); return true
} }
if (c == CP_PIPE) { if (c == "|") {
if (pk_at(1) == CP_PIPE) { if (pk_at(1) == "|") {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("||=", 3); return true } if (pk_at(2) == "=") { emit_op("||=", 3); return true }
emit_op("||", 2); return true emit_op("||", 2); return true
} }
if (pk_at(1) == CP_EQ) { emit_op("|=", 2); return true } if (pk_at(1) == "=") { emit_op("|=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("|", 1); return true emit_op("|", 1); return true
} }
if (c == CP_CARET) { if (c == "^") {
if (pk_at(1) == CP_EQ) { emit_op("^=", 2); return true } if (pk_at(1) == "=") { emit_op("^=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("^", 1); return true emit_op("^", 1); return true
} }
if (c == CP_LBRACKET) { if (c == "[") {
if (pk_at(1) == CP_RBRACKET && pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(1) == "]" && pk_at(2) == "!") { emit_ident(3); return true }
emit_op("[", 1); return true emit_op("[", 1); return true
} }
if (c == CP_TILDE) { if (c == "~") {
if (pk_at(1) == CP_BANG) { emit_ident(2); return true } if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("~", 1); return true emit_op("~", 1); return true
} }
emit_op(character(c), 1) emit_op(c, 1)
return true return true
} }
@@ -508,7 +448,7 @@ var tokenize = function(src, filename) {
// EOF token // EOF token
push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col }) push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col })
return {filename: filename, tokens: tokens, cp: cp} return {filename: filename, tokens: tokens}
} }
return tokenize return tokenize

View File

@@ -29,7 +29,8 @@ static const JSCFunctionListEntry js_wildstar_funcs[] = {
}; };
JSValue js_core_wildstar_use(JSContext *js) { JSValue js_core_wildstar_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_wildstar_funcs, countof(js_wildstar_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs));
JS_RETURN(mod.val);
} }