Merge branch 'master' into optimize_mcode
This commit is contained in:
@@ -1294,6 +1294,10 @@ function get_module(path, package_context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shop.use = function use(path, _pkg_ctx) {
|
Shop.use = function use(path, _pkg_ctx) {
|
||||||
|
if (!is_text(path)) {
|
||||||
|
log.error("use() expects a text module path, but received a non-text value")
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
var package_context = is_core_dir(_pkg_ctx) ? 'core' : _pkg_ctx
|
var package_context = is_core_dir(_pkg_ctx) ? 'core' : _pkg_ctx
|
||||||
// Check for embedded module (static builds)
|
// Check for embedded module (static builds)
|
||||||
var embed_key = 'embedded:' + path
|
var embed_key = 'embedded:' + path
|
||||||
|
|||||||
8
mcode.cm
8
mcode.cm
@@ -2837,6 +2837,7 @@ var mcode = function(ast) {
|
|||||||
var result = null
|
var result = null
|
||||||
var saved_label = 0
|
var saved_label = 0
|
||||||
var saved_func = 0
|
var saved_func = 0
|
||||||
|
var captured_this = 0
|
||||||
|
|
||||||
push(parent_states, saved)
|
push(parent_states, saved)
|
||||||
|
|
||||||
@@ -2889,6 +2890,13 @@ var mcode = function(ast) {
|
|||||||
s_max_slot = s_next_temp_slot
|
s_max_slot = s_next_temp_slot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Arrow functions capture the enclosing this via closure
|
||||||
|
if (is_arrow) {
|
||||||
|
captured_this = alloc_slot()
|
||||||
|
emit_3("get", captured_this, saved.this_slot, 1)
|
||||||
|
s_this_slot = captured_this
|
||||||
|
}
|
||||||
|
|
||||||
// Default parameter initialization
|
// Default parameter initialization
|
||||||
ps = 1
|
ps = 1
|
||||||
_i = 0
|
_i = 0
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ static int run_test_suite(size_t heap_size);
|
|||||||
|
|
||||||
cell_rt *root_cell = NULL;
|
cell_rt *root_cell = NULL;
|
||||||
static char *shop_path = NULL;
|
static char *shop_path = NULL;
|
||||||
|
volatile JSContext *g_crash_ctx = NULL;
|
||||||
static char *core_path = NULL;
|
static char *core_path = NULL;
|
||||||
static int native_mode = 0;
|
static int native_mode = 0;
|
||||||
static int warn_mode = 1;
|
static int warn_mode = 1;
|
||||||
@@ -408,6 +409,13 @@ static void signal_handler(int sig)
|
|||||||
#endif
|
#endif
|
||||||
if (!str) return;
|
if (!str) return;
|
||||||
|
|
||||||
|
/* Reset handler to default so a double-fault terminates immediately */
|
||||||
|
signal(sig, SIG_DFL);
|
||||||
|
|
||||||
|
/* Try to print the JS stack (best-effort, signal-safe) */
|
||||||
|
if (g_crash_ctx)
|
||||||
|
JS_CrashPrintStack((JSContext *)g_crash_ctx);
|
||||||
|
|
||||||
exit_handler();
|
exit_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,7 +697,9 @@ int cell_init(int argc, char **argv)
|
|||||||
JS_DeleteGCRef(ctx, &args_ref);
|
JS_DeleteGCRef(ctx, &args_ref);
|
||||||
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
||||||
|
|
||||||
|
g_crash_ctx = ctx;
|
||||||
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);
|
||||||
|
g_crash_ctx = NULL;
|
||||||
JS_DeleteGCRef(ctx, &env_ref);
|
JS_DeleteGCRef(ctx, &env_ref);
|
||||||
free(bin_data);
|
free(bin_data);
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ typedef struct cell_rt {
|
|||||||
cell_hook trace_hook;
|
cell_hook trace_hook;
|
||||||
} cell_rt;
|
} cell_rt;
|
||||||
|
|
||||||
|
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
||||||
|
Read by signal_handler to print JS stack on crash. */
|
||||||
|
extern volatile JSContext *g_crash_ctx;
|
||||||
|
|
||||||
cell_rt *create_actor(void *wota);
|
cell_rt *create_actor(void *wota);
|
||||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||||
void actor_disrupt(cell_rt *actor);
|
void actor_disrupt(cell_rt *actor);
|
||||||
|
|||||||
@@ -496,6 +496,37 @@ static int mach_check_call_arity(JSContext *ctx, JSFunction *fn, int argc) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scan backwards from pc to find what loaded the callee register.
|
||||||
|
Returns the name in buf, or NULL if unknown. */
|
||||||
|
static const char *mach_callee_name(JSContext *ctx, JSCodeRegister *code,
|
||||||
|
uint32_t pc, int reg,
|
||||||
|
char *buf, size_t buf_size) {
|
||||||
|
int hops = 4; /* limit move-chain depth */
|
||||||
|
for (int i = (int)pc - 2; i >= 0 && hops > 0; i--) {
|
||||||
|
MachInstr32 prev = code->instructions[i];
|
||||||
|
int prev_op = MACH_GET_OP(prev);
|
||||||
|
int prev_a = MACH_GET_A(prev);
|
||||||
|
if (prev_a != reg) continue;
|
||||||
|
if (prev_op == MACH_GETENV || prev_op == MACH_GETINTRINSIC) {
|
||||||
|
int bx = MACH_GET_Bx(prev);
|
||||||
|
if ((uint32_t)bx < code->cpool_count && JS_IsText(code->cpool[bx]))
|
||||||
|
return JS_KeyGetStr(ctx, buf, buf_size, code->cpool[bx]);
|
||||||
|
}
|
||||||
|
if (prev_op == MACH_LOAD_FIELD) {
|
||||||
|
int ci = MACH_GET_C(prev);
|
||||||
|
if ((uint32_t)ci < code->cpool_count && JS_IsText(code->cpool[ci]))
|
||||||
|
return JS_KeyGetStr(ctx, buf, buf_size, code->cpool[ci]);
|
||||||
|
}
|
||||||
|
if (prev_op == MACH_MOVE) {
|
||||||
|
reg = MACH_GET_B(prev);
|
||||||
|
hops--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break; /* some other op wrote to this reg — give up */
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
|
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
|
||||||
|
|
||||||
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
|
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
|
||||||
@@ -2600,6 +2631,11 @@ vm_dispatch:
|
|||||||
/* A=frame_slot, B=func_reg, C=argc */
|
/* A=frame_slot, B=func_reg, C=argc */
|
||||||
JSValue func_val = frame->slots[b];
|
JSValue func_val = frame->slots[b];
|
||||||
if (!mist_is_function(func_val)) {
|
if (!mist_is_function(func_val)) {
|
||||||
|
char nbuf[KEY_GET_STR_BUF_SIZE];
|
||||||
|
const char *name = mach_callee_name(ctx, code, pc, b, nbuf, sizeof(nbuf));
|
||||||
|
if (name)
|
||||||
|
JS_RaiseDisrupt(ctx, "%s is not a function", name);
|
||||||
|
else
|
||||||
JS_RaiseDisrupt(ctx, "not a function");
|
JS_RaiseDisrupt(ctx, "not a function");
|
||||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
goto disrupt;
|
goto disrupt;
|
||||||
|
|||||||
@@ -1054,6 +1054,11 @@ MachCode *mach_compile_mcode(struct cJSON *mcode_json);
|
|||||||
Does NOT clear reg_current_frame — caller is responsible if needed. */
|
Does NOT clear reg_current_frame — caller is responsible if needed. */
|
||||||
JSValue JS_GetStack (JSContext *ctx);
|
JSValue JS_GetStack (JSContext *ctx);
|
||||||
|
|
||||||
|
/* Crash-safe stack printer: walks JS frames and writes to stderr.
|
||||||
|
Uses only write() (async-signal-safe). No GC allocations.
|
||||||
|
Safe to call from signal handlers. */
|
||||||
|
void JS_CrashPrintStack(JSContext *ctx);
|
||||||
|
|
||||||
#undef js_unlikely
|
#undef js_unlikely
|
||||||
#undef inline
|
#undef inline
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#define BLOB_IMPLEMENTATION
|
#define BLOB_IMPLEMENTATION
|
||||||
#include "quickjs-internal.h"
|
#include "quickjs-internal.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
// #define DUMP_BUDDY
|
// #define DUMP_BUDDY
|
||||||
#ifdef DUMP_BUDDY
|
#ifdef DUMP_BUDDY
|
||||||
@@ -11718,10 +11719,10 @@ JSValue JS_GetStack(JSContext *ctx) {
|
|||||||
JSGCRef item_ref;
|
JSGCRef item_ref;
|
||||||
JS_PushGCRef(ctx, &item_ref);
|
JS_PushGCRef(ctx, &item_ref);
|
||||||
item_ref.val = JS_NewObject(ctx);
|
item_ref.val = JS_NewObject(ctx);
|
||||||
JS_SetPropertyStr(ctx, item_ref.val, "fn",
|
JSValue fn_str = JS_NewString(ctx, frames[i].fn ? frames[i].fn : "<anonymous>");
|
||||||
JS_NewString(ctx, frames[i].fn ? frames[i].fn : "<anonymous>"));
|
JS_SetPropertyStr(ctx, item_ref.val, "fn", fn_str);
|
||||||
JS_SetPropertyStr(ctx, item_ref.val, "file",
|
JSValue file_str = JS_NewString(ctx, frames[i].file ? frames[i].file : "<unknown>");
|
||||||
JS_NewString(ctx, frames[i].file ? frames[i].file : "<unknown>"));
|
JS_SetPropertyStr(ctx, item_ref.val, "file", file_str);
|
||||||
JS_SetPropertyStr(ctx, item_ref.val, "line", JS_NewInt32(ctx, frames[i].line));
|
JS_SetPropertyStr(ctx, item_ref.val, "line", JS_NewInt32(ctx, frames[i].line));
|
||||||
JS_SetPropertyStr(ctx, item_ref.val, "col", JS_NewInt32(ctx, frames[i].col));
|
JS_SetPropertyStr(ctx, item_ref.val, "col", JS_NewInt32(ctx, frames[i].col));
|
||||||
JS_SetPropertyNumber(ctx, arr_ref.val, i, item_ref.val);
|
JS_SetPropertyNumber(ctx, arr_ref.val, i, item_ref.val);
|
||||||
@@ -11731,3 +11732,65 @@ JSValue JS_GetStack(JSContext *ctx) {
|
|||||||
JS_PopGCRef(ctx, &arr_ref);
|
JS_PopGCRef(ctx, &arr_ref);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Crash-safe stack printer: walks JS frames and writes to stderr
|
||||||
|
using only write() (async-signal-safe). No GC allocations.
|
||||||
|
Best-effort: if the heap is corrupted, we may double-fault. */
|
||||||
|
void JS_CrashPrintStack(JSContext *ctx) {
|
||||||
|
if (!ctx) return;
|
||||||
|
if (JS_IsNull(ctx->reg_current_frame)) return;
|
||||||
|
|
||||||
|
static const char hdr[] = "\n--- JS Stack at crash ---\n";
|
||||||
|
write(STDERR_FILENO, hdr, sizeof(hdr) - 1);
|
||||||
|
|
||||||
|
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
|
||||||
|
uint32_t cur_pc = ctx->current_register_pc;
|
||||||
|
int is_first = 1;
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
while (frame && depth < 32) {
|
||||||
|
if (!JS_IsFunction(frame->function)) break;
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
|
||||||
|
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
|
||||||
|
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||||
|
uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
|
||||||
|
|
||||||
|
const char *name = code->name_cstr ? code->name_cstr : "<anonymous>";
|
||||||
|
const char *file = code->filename_cstr ? code->filename_cstr : "<unknown>";
|
||||||
|
int line = 0;
|
||||||
|
if (code->line_table && pc < code->instr_count)
|
||||||
|
line = code->line_table[pc].line;
|
||||||
|
|
||||||
|
/* Format: " at name (file:line)\n" using only write() */
|
||||||
|
char buf[512];
|
||||||
|
int pos = 0;
|
||||||
|
buf[pos++] = ' '; buf[pos++] = ' '; buf[pos++] = ' '; buf[pos++] = ' ';
|
||||||
|
buf[pos++] = 'a'; buf[pos++] = 't'; buf[pos++] = ' ';
|
||||||
|
for (const char *p = name; *p && pos < 200; p++) buf[pos++] = *p;
|
||||||
|
buf[pos++] = ' '; buf[pos++] = '(';
|
||||||
|
for (const char *p = file; *p && pos < 440; p++) buf[pos++] = *p;
|
||||||
|
buf[pos++] = ':';
|
||||||
|
/* itoa for line number */
|
||||||
|
if (line == 0) {
|
||||||
|
buf[pos++] = '0';
|
||||||
|
} else {
|
||||||
|
char digits[12];
|
||||||
|
int d = 0;
|
||||||
|
int n = line;
|
||||||
|
while (n > 0) { digits[d++] = '0' + (n % 10); n /= 10; }
|
||||||
|
for (int i = d - 1; i >= 0; i--) buf[pos++] = digits[i];
|
||||||
|
}
|
||||||
|
buf[pos++] = ')'; buf[pos++] = '\n';
|
||||||
|
write(STDERR_FILENO, buf, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JS_IsNull(frame->caller)) break;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||||
|
is_first = 0;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char ftr[] = "--- End JS Stack ---\n";
|
||||||
|
write(STDERR_FILENO, ftr, sizeof(ftr) - 1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -716,6 +716,7 @@ void actor_turn(cell_rt *actor)
|
|||||||
|
|
||||||
if (actor->vm_suspended) {
|
if (actor->vm_suspended) {
|
||||||
/* RESUME path: continue suspended turn under kill timer only */
|
/* RESUME path: continue suspended turn under kill timer only */
|
||||||
|
g_crash_ctx = actor->context;
|
||||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||||
JS_SetPauseFlag(actor->context, 0);
|
JS_SetPauseFlag(actor->context, 0);
|
||||||
actor->turn_start_ns = cell_ns();
|
actor->turn_start_ns = cell_ns();
|
||||||
@@ -783,6 +784,8 @@ void actor_turn(cell_rt *actor)
|
|||||||
pthread_cond_signal(&engine.timer_cond);
|
pthread_cond_signal(&engine.timer_cond);
|
||||||
pthread_mutex_unlock(&engine.lock);
|
pthread_mutex_unlock(&engine.lock);
|
||||||
|
|
||||||
|
g_crash_ctx = actor->context;
|
||||||
|
|
||||||
if (l.type == LETTER_BLOB) {
|
if (l.type == LETTER_BLOB) {
|
||||||
size_t size = blob_length(l.blob_data) / 8;
|
size_t size = blob_length(l.blob_data) / 8;
|
||||||
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
||||||
@@ -816,6 +819,7 @@ void actor_turn(cell_rt *actor)
|
|||||||
actor->slow_strikes = 0; /* completed within fast timer */
|
actor->slow_strikes = 0; /* completed within fast timer */
|
||||||
|
|
||||||
ENDTURN:
|
ENDTURN:
|
||||||
|
g_crash_ctx = NULL;
|
||||||
/* Invalidate any outstanding pause/kill timers for this turn */
|
/* Invalidate any outstanding pause/kill timers for this turn */
|
||||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||||
actor->state = ACTOR_IDLE;
|
actor->state = ACTOR_IDLE;
|
||||||
@@ -842,6 +846,7 @@ ENDTURN:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ENDTURN_SLOW:
|
ENDTURN_SLOW:
|
||||||
|
g_crash_ctx = NULL;
|
||||||
#ifdef ACTOR_TRACE
|
#ifdef ACTOR_TRACE
|
||||||
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
|
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
|
||||||
actor->name ? actor->name : actor->id);
|
actor->name ? actor->name : actor->id);
|
||||||
|
|||||||
@@ -1888,6 +1888,8 @@ var streamline = function(ir, log) {
|
|||||||
var found = false
|
var found = false
|
||||||
var anc_remap = null
|
var anc_remap = null
|
||||||
var old_slot = 0
|
var old_slot = 0
|
||||||
|
var max_close = null
|
||||||
|
var needed = 0
|
||||||
var fi = 0
|
var fi = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
var j = 0
|
var j = 0
|
||||||
@@ -1993,6 +1995,8 @@ var streamline = function(ir, log) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix get/put parent_slot references using ancestor remap tables
|
// Fix get/put parent_slot references using ancestor remap tables
|
||||||
|
// and track the max close slot per ancestor for nr_close_slots update
|
||||||
|
max_close = array(func_count + 1, -1)
|
||||||
fi = 0
|
fi = 0
|
||||||
while (fi < func_count) {
|
while (fi < func_count) {
|
||||||
instrs = functions[fi].instructions
|
instrs = functions[fi].instructions
|
||||||
@@ -2015,6 +2019,9 @@ var streamline = function(ir, log) {
|
|||||||
instr[2] = anc_remap[old_slot]
|
instr[2] = anc_remap[old_slot]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ancestor >= 0 && instr[2] > max_close[ancestor]) {
|
||||||
|
max_close[ancestor] = instr[2]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
@@ -2022,6 +2029,26 @@ var streamline = function(ir, log) {
|
|||||||
fi = fi + 1
|
fi = fi + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update nr_close_slots for functions whose close slots were remapped.
|
||||||
|
// Frame shortening keeps 1 + nr_args + nr_close_slots slots, so
|
||||||
|
// nr_close_slots must cover the highest-numbered close slot.
|
||||||
|
fi = 0
|
||||||
|
while (fi < func_count) {
|
||||||
|
if (max_close[fi] >= 0) {
|
||||||
|
needed = max_close[fi] - (functions[fi].nr_args != null ? functions[fi].nr_args : 0)
|
||||||
|
if (needed > functions[fi].nr_close_slots) {
|
||||||
|
functions[fi].nr_close_slots = needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fi = fi + 1
|
||||||
|
}
|
||||||
|
if (max_close[func_count] >= 0 && ir.main != null) {
|
||||||
|
needed = max_close[func_count] - (ir.main.nr_args != null ? ir.main.nr_args : 0)
|
||||||
|
if (needed > ir.main.nr_close_slots) {
|
||||||
|
ir.main.nr_close_slots = needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3498,4 +3498,59 @@ return {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ARROW FUNCTION LEXICAL THIS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test_arrow_captures_this: function() {
|
||||||
|
var obj = {
|
||||||
|
value: 42,
|
||||||
|
getIt: function() {
|
||||||
|
var arrow = () => this.value
|
||||||
|
return arrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obj.getIt() != 42) return "arrow should capture enclosing this"
|
||||||
|
},
|
||||||
|
|
||||||
|
test_arrow_captures_this_nested: function() {
|
||||||
|
var obj = {
|
||||||
|
value: 99,
|
||||||
|
getIt: function() {
|
||||||
|
var outer = () => {
|
||||||
|
var inner = () => this.value
|
||||||
|
return inner()
|
||||||
|
}
|
||||||
|
return outer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obj.getIt() != 99) return "nested arrows should capture enclosing this"
|
||||||
|
},
|
||||||
|
|
||||||
|
test_arrow_this_not_rebound_by_method_call: function() {
|
||||||
|
var obj = {
|
||||||
|
value: 10,
|
||||||
|
getIt: function() {
|
||||||
|
var arrow = () => this.value
|
||||||
|
return arrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var other = { value: 20, stolen: obj.getIt }
|
||||||
|
if (other.stolen() != 20) return "setup failed"
|
||||||
|
if (obj.getIt() != 10) return "arrow this should come from enclosing method"
|
||||||
|
},
|
||||||
|
|
||||||
|
test_arrow_this_with_regular_function_this: function() {
|
||||||
|
var obj = {
|
||||||
|
value: 7,
|
||||||
|
getIt: function() {
|
||||||
|
var arrow = () => this.value
|
||||||
|
var regular = function() { return this }
|
||||||
|
if (regular() != null) return "regular fn direct call should have null this"
|
||||||
|
return arrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obj.getIt() != 7) return "arrow should capture this while regular fn does not"
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
151
vm_suite.ce
151
vm_suite.ce
@@ -5562,6 +5562,157 @@ run("gc blob forward pointer chase", function() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GC CLOSURE FRAME SHORTENING
|
||||||
|
// Verify that closure-captured variables survive GC collection, particularly
|
||||||
|
// when the streamline optimizer remaps close slots to different positions.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
var force_gc = function() {
|
||||||
|
var _g = 0
|
||||||
|
var _gx = null
|
||||||
|
for (_g = 0; _g < 200; _g = _g + 1) {
|
||||||
|
_gx = {a: _g, b: [1, 2, 3], c: "garbage"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run("gc closure basic - captured function survives gc", function() {
|
||||||
|
var make = function() {
|
||||||
|
function helper() { return 42 }
|
||||||
|
var obj = { call() { return helper() } }
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.call(), 42, "captured function should survive GC")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - captured variable survives gc", function() {
|
||||||
|
var make = function() {
|
||||||
|
var val = 99
|
||||||
|
var obj = { get() { return val } }
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.get(), 99, "captured variable should survive GC")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - multiple captured variables survive gc", function() {
|
||||||
|
var make = function() {
|
||||||
|
var a = 10
|
||||||
|
var b = 20
|
||||||
|
var c = 30
|
||||||
|
var obj = {
|
||||||
|
sum() { return a + b + c }
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.sum(), 60, "all captured vars should survive GC")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - captured function and var survive gc", function() {
|
||||||
|
var make = function() {
|
||||||
|
function double(x) { return x * 2 }
|
||||||
|
var base = 5
|
||||||
|
var obj = { compute() { return double(base) } }
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.compute(), 10, "captured fn and var should survive GC")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - nested closure chain survives gc", function() {
|
||||||
|
var outer = function() {
|
||||||
|
var x = 7
|
||||||
|
var mid = function() {
|
||||||
|
var y = 3
|
||||||
|
var inner = function() { return x + y }
|
||||||
|
return inner
|
||||||
|
}
|
||||||
|
return mid()
|
||||||
|
}
|
||||||
|
var fn = outer()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(fn(), 10, "nested closure chain should survive GC")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - multiple methods share captured frame", function() {
|
||||||
|
var make = function() {
|
||||||
|
var count = 0
|
||||||
|
function inc() { count = count + 1 }
|
||||||
|
function get() { return count }
|
||||||
|
var obj = {
|
||||||
|
increment() { inc() },
|
||||||
|
value() { return get() }
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
obj.increment()
|
||||||
|
obj.increment()
|
||||||
|
force_gc()
|
||||||
|
obj.increment()
|
||||||
|
assert_eq(obj.value(), 3, "shared closure frame should survive GC")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - closure survives repeated gc cycles", function() {
|
||||||
|
var make = function() {
|
||||||
|
var val = 123
|
||||||
|
var obj = { get() { return val } }
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
force_gc()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.get(), 123, "closure should survive repeated GC cycles")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - object literal method with temp slot reuse", function() {
|
||||||
|
var make = function() {
|
||||||
|
function helper() { return "ok" }
|
||||||
|
var temp = [1, 2, 3]
|
||||||
|
var unused = {x: temp}
|
||||||
|
var obj = { call() { return helper() } }
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.call(), "ok", "closure should work after temp slots discarded")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - closure array survives gc", function() {
|
||||||
|
var make = function() {
|
||||||
|
var items = [10, 20, 30]
|
||||||
|
var obj = {
|
||||||
|
first() { return items[0] },
|
||||||
|
last() { return items[2] }
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var obj = make()
|
||||||
|
force_gc()
|
||||||
|
assert_eq(obj.first(), 10, "captured array first element")
|
||||||
|
assert_eq(obj.last(), 30, "captured array last element")
|
||||||
|
})
|
||||||
|
|
||||||
|
run("gc closure - factory pattern survives gc", function() {
|
||||||
|
var factory = function(name) {
|
||||||
|
function greet() { return "hello " + name }
|
||||||
|
var obj = { say() { return greet() } }
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
var a = factory("alice")
|
||||||
|
var b = factory("bob")
|
||||||
|
force_gc()
|
||||||
|
assert_eq(a.say(), "hello alice", "first factory closure")
|
||||||
|
assert_eq(b.say(), "hello bob", "second factory closure")
|
||||||
|
})
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// SUMMARY
|
// SUMMARY
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user