This commit is contained in:
2025-12-31 12:45:32 -06:00
parent 334f3a789b
commit 7c3cce1ce2
3 changed files with 130 additions and 44 deletions

View File

@@ -56,7 +56,7 @@ static:
# Bootstrap: build cell from scratch using meson (only needed once)
# Also installs core scripts to ~/.cell/core
bootstrap:
meson setup build_bootstrap -Dbuildtype=debug
meson setup build_bootstrap -Dbuildtype=debugoptimized
meson compile -C build_bootstrap
cp build_bootstrap/cell .
cp build_bootstrap/libcell_runtime.dylib .

View File

@@ -88,7 +88,7 @@
64: dump compute_stack_size
*/
//#define DUMP_BYTECODE (1)
#define DEBUG_VM
//#define DEBUG_VM
/* dump the occurence of the automatic GC */
//#define DUMP_GC
/* dump objects freed by the garbage collector */
@@ -341,6 +341,13 @@ struct VMFrame {
int arg_count;
int js_mode;
int stack_size_allocated; /* total size allocated for this frame */
/* Constructor support */
int is_constructor; /* 1 if this is a constructor call */
JSValue ctor_this_obj; /* the 'this' object created for non-derived constructor */
/* Exception handling */
int pending_exception; /* 1 if a call returned exception, need to check catch */
};
/* Execution state returned by vm_execute_frame */
@@ -938,6 +945,8 @@ static JSValue JS_CallConstructorInternal(JSContext *ctx,
JSValueConst func_obj,
JSValueConst new_target,
int argc, JSValue *argv, int flags);
static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor,
int class_id);
static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj,
int argc, JSValueConst *argv);
static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom,
@@ -12901,6 +12910,13 @@ static struct VMFrame *vm_push_frame(JSContext *ctx,
frame->call_argc = call_argc;
frame->call_has_this = call_has_this;
/* Constructor support - initialized by caller if needed */
frame->is_constructor = 0;
frame->ctor_this_obj = JS_NULL;
/* Exception handling */
frame->pending_exception = 0;
/* Bump value stack top */
ctx->value_stack_top += total_slots;
@@ -12939,6 +12955,12 @@ static void vm_pop_frame(JSContext *ctx)
JS_FreeValue(ctx, frame->this_obj);
JS_FreeValue(ctx, frame->new_target);
/* Free constructor this object if it wasn't consumed */
if (!JS_IsNull(frame->ctor_this_obj)) {
JS_FreeValue(ctx, frame->ctor_this_obj);
frame->ctor_this_obj = JS_NULL;
}
/* Pop frame and value stack */
ctx->value_stack_top -= frame->stack_size_allocated;
ctx->frame_stack_top--;
@@ -13014,24 +13036,50 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj,
switch (state) {
case VM_EXEC_RETURN: {
/* 1. Capture continuation info before popping */
/* 1. Capture continuation and constructor info before popping */
const uint8_t *ret_pc = frame->ret_pc;
int ret_sp_offset = frame->ret_sp_offset;
int was_constructor = frame->is_constructor;
JSValue ctor_this_obj = frame->ctor_this_obj;
/* 2. Pop the frame */
/* Clear ctor_this_obj in frame so vm_pop_frame doesn't free it */
frame->ctor_this_obj = JS_NULL;
/* 2. Handle constructor return value semantics */
if (was_constructor) {
/* JS constructor semantics:
- If return value is an object, use it
- Otherwise, use the constructed 'this' object */
if (JS_VALUE_GET_TAG(ret_val) == JS_TAG_OBJECT) {
/* Return value is object, free the constructed this */
JS_FreeValue(caller_ctx, ctor_this_obj);
#ifdef DEBUG_VM
printf("[CTOR-RET] constructor returned object, using ret_val tag=%d\n",
JS_VALUE_GET_TAG(ret_val));
#endif
} else {
/* Return value is not an object, use constructed this */
JS_FreeValue(caller_ctx, ret_val);
ret_val = ctor_this_obj;
#ifdef DEBUG_VM
printf("[CTOR-RET] constructor returned non-object (tag=%d), using ctor_this_obj\n",
JS_VALUE_GET_TAG(ret_val));
#endif
}
}
/* 3. Pop the frame */
vm_pop_frame(caller_ctx);
/* 3. Check if finished */
/* 4. Check if finished */
if (caller_ctx->frame_stack_top < initial_frame_top) {
return ret_val;
}
/* 4. Resume caller */
/* 5. Resume caller */
frame = &caller_ctx->frame_stack[caller_ctx->frame_stack_top];
/* 5. Clean up caller stack (pop Func, This, Args) using the saved offset */
/* 6. Clean up caller stack (pop Func, This, Args) using the saved offset */
JSValue *stack_base = &caller_ctx->value_stack[frame->value_stack_base];
JSValue *current_sp = stack_base + frame->sp_offset;
JSValue *target_sp = stack_base + ret_sp_offset;
@@ -13040,32 +13088,36 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj,
JS_FreeValue(caller_ctx, *--current_sp);
}
/* 6. Push return value */
/* 7. Push return value */
*current_sp++ = ret_val;
/* 7. Restore state */
/* 8. Restore state */
frame->sp_offset = current_sp - stack_base;
#ifdef DEBUG_VM
const uint8_t *bc = frame->b->byte_code_buf;
const uint8_t *end = bc + frame->b->byte_code_len;
if (ret_pc < bc || ret_pc >= end) {
printf("BAD ret_pc: caller=%s ret_pc=%p not in [%p,%p)\n",
"dbg", ret_pc, bc, end);
abort();
#ifdef DEBUG_VM
{
const uint8_t *bc = frame->b->byte_code_buf;
const uint8_t *end = bc + frame->b->byte_code_len;
if (ret_pc < bc || ret_pc >= end) {
printf("BAD ret_pc: caller=%s ret_pc=%p not in [%p,%p)\n",
frame->b->func_name ? JS_AtomToCString(caller_ctx, frame->b->func_name) : "<anon>",
ret_pc, bc, end);
abort();
}
uint8_t op = *ret_pc;
if (op == 0 || op == OP_invalid) {
ptrdiff_t off = ret_pc - bc;
printf("BAD resume opcode: caller=%s off=%td op=0x%02x\n",
frame->b->func_name ? JS_AtomToCString(caller_ctx, frame->b->func_name) : "<anon>",
off, op);
/* dump a few bytes */
for (int di = -8; di < 16; di++)
printf("%02x ", ret_pc[di]);
printf("\n");
abort();
}
}
uint8_t op = *ret_pc;
if (op == 0 || op == OP_invalid) {
ptrdiff_t off = ret_pc - bc;
printf("BAD resume opcode: caller=%s off=%td op=0x%02x\n",
"dbg", off, op);
/* dump a few bytes */
for (int i = -8; i < 16; i++)
printf("%02x ", ret_pc[i]);
printf("\n");
abort();
}
#endif
#endif
frame->pc = ret_pc;
break;
@@ -13286,20 +13338,59 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj,
for (int i = 0; i < call_info.argc; i++) JS_FreeValue(caller_ctx, saved_argv[i]);
js_free(caller_ctx, saved_argv);
} else {
/* Regular call */
// frame->ret_pc = call_info.ret_pc;
// frame->ret_sp_offset = call_info.ret_sp_offset;
/* Regular call (including constructor calls) */
JSValue this_for_call = call_info.this_obj;
JSValue ctor_this_obj = JS_NULL;
/* Handle bytecode constructor: create 'this' object */
if (call_info.is_constructor) {
JSObject *callee_obj = JS_VALUE_GET_OBJ(call_info.func_obj);
JSFunctionBytecode *callee_b = callee_obj->u.func.function_bytecode;
if (callee_b->is_derived_class_constructor) {
/* Derived class: this is null until super() is called */
this_for_call = JS_NULL;
#ifdef DEBUG_VM
printf("[CTOR-BC] derived class constructor, this=NULL until super()\n");
#endif
} else {
/* Non-derived: create 'this' object from prototype */
ctor_this_obj = js_create_from_ctor(caller_ctx, call_info.new_target, JS_CLASS_OBJECT);
if (JS_IsException(ctor_this_obj)) {
#ifdef DEBUG_VM
printf("[CTOR-BC] js_create_from_ctor FAILED\n");
#endif
goto exception_unwind;
}
this_for_call = ctor_this_obj;
#ifdef DEBUG_VM
printf("[CTOR-BC] created this object: tag=%d ptr=%p new_target_tag=%d\n",
JS_VALUE_GET_TAG(ctor_this_obj),
JS_VALUE_GET_PTR(ctor_this_obj),
JS_VALUE_GET_TAG(call_info.new_target));
#endif
}
}
frame = vm_push_frame(caller_ctx, call_info.func_obj,
call_info.this_obj, call_info.new_target,
this_for_call, call_info.new_target,
call_info.argc, call_info.argv,
JS_CALL_FLAG_COPY_ARGV,
call_info.ret_pc, call_info.ret_sp_offset,
call_info.call_argc, call_info.call_has_this);
}
if (!frame) {
JS_ThrowStackOverflow(caller_ctx);
goto exception_unwind;
if (!frame) {
if (!JS_IsNull(ctor_this_obj))
JS_FreeValue(caller_ctx, ctor_this_obj);
JS_ThrowStackOverflow(caller_ctx);
goto exception_unwind;
}
/* Store constructor info in the frame for return handling */
if (call_info.is_constructor) {
frame->is_constructor = 1;
frame->ctor_this_obj = ctor_this_obj; /* ownership transferred to frame */
}
}
break;
}

View File

@@ -5,11 +5,6 @@ var time = use('time')
var json = use('json')
var blob = use('blob')
var newblob = new blob(100)
for (var i in newblob) log.console(i)
newblob.write_bit(true)
log.console(newblob.length)
if (!args) args = []
var target_pkg = null // null = current package