From 41eb4bf6f71da885c28368c5e7921a5d55c5e754 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 30 Dec 2025 22:56:31 -0600 Subject: [PATCH] bench --- Makefile | 2 +- source/quickjs.c | 2551 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 2513 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index e4a7a819..47d6067c 100755 --- a/Makefile +++ b/Makefile @@ -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=debugoptimized + meson setup build_bootstrap -Dbuildtype=debug meson compile -C build_bootstrap cp build_bootstrap/cell . cp build_bootstrap/libcell_runtime.dylib . diff --git a/source/quickjs.c b/source/quickjs.c index f8bc4fe3..b997622d 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -6325,9 +6325,9 @@ static const char *get_func_name(JSContext *ctx, JSValueConst func) val = pr->u.value; if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) return NULL; - // char *buf = js_malloc(ctx->rt, 128); // return JS_AtomGetStr(ctx, buf, 128, prs->atom); + return NULL; } #define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) @@ -12508,6 +12508,19 @@ static int js_op_define_class(JSContext *ctx, JSValue *sp, return -1; } +/* Transfer var refs from src list to dst list (for trampoline suspension) */ +static void transfer_var_refs(struct list_head *dst, struct list_head *src) +{ + struct list_head *el, *el1; + JSVarRef *var_ref; + + list_for_each_safe(el, el1, src) { + var_ref = list_entry(el, JSVarRef, var_ref_link); + list_del(&var_ref->var_ref_link); + list_add_tail(&var_ref->var_ref_link, dst); + } +} + static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) { struct list_head *el, *el1; @@ -12907,19 +12920,18 @@ static void vm_pop_frame(JSContext *ctx) frame = &ctx->frame_stack[ctx->frame_stack_top]; stack_base = &ctx->value_stack[frame->value_stack_base]; - /* Close variable references */ - if (!list_empty(&frame->var_ref_list)) { - list_for_each_safe(el, el1, &frame->var_ref_list) { - struct JSVarRef *var_ref = list_entry(el, struct JSVarRef, var_ref_link); - var_ref->value = JS_DupValue(ctx, *var_ref->pvalue); - var_ref->pvalue = &var_ref->value; - var_ref->is_detached = TRUE; - list_del(&var_ref->var_ref_link); - } - } + /* Note: var refs are closed in vm_execute_frame's done: label via close_var_refs(). + We don't process frame->var_ref_list here because refs were either: + 1. Never transferred (stayed in local sf->var_ref_list and got closed), or + 2. Transferred but already closed when the frame eventually hit done: - /* Free all values in this frame's value stack region */ - for (i = 0; i < frame->stack_size_allocated; i++) + TODO: If we implement proper suspension/resumption where a frame can be + suspended multiple times, we need to properly track which refs belong + to which execution segment. For now, var refs are handled per-execution. */ + + /* Free only live values: args + vars + operand stack (up to sp). + sp_offset indicates the current stack pointer position. */ + for (i = 0; i < frame->sp_offset; i++) JS_FreeValue(ctx, stack_base[i]); /* Free frame values */ @@ -12987,9 +12999,10 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, (JSValueConst *)argv, flags); } - /* Push initial frame (entry point, no continuation) */ + /* Push initial frame (entry point, no continuation) + Always copy args - we can't alias into external memory */ frame = vm_push_frame(caller_ctx, func_obj, this_obj, new_target, - argc, argv, flags, + argc, argv, flags | JS_CALL_FLAG_COPY_ARGV, NULL, 0, 0, 0); if (!frame) return JS_ThrowStackOverflow(caller_ctx); @@ -13086,26 +13099,59 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, callee_b = callee_p->u.func.function_bytecode; if (call_info.is_tail_call) { - /* Tail call: replace current frame */ - /* First pop current frame without pushing return value */ + /* Tail call: replace current frame. + IMPORTANT: We must dup the args BEFORE popping the frame, + since argv points into the frame's stack which gets freed. */ + JSValue *saved_argv = NULL; + JSValue func_obj_saved, this_obj_saved, new_target_saved; + int i; + + /* Dup all call operands before they get freed */ + func_obj_saved = JS_DupValue(caller_ctx, call_info.func_obj); + this_obj_saved = JS_DupValue(caller_ctx, call_info.this_obj); + new_target_saved = JS_DupValue(caller_ctx, call_info.new_target); + if (call_info.argc > 0) { + saved_argv = js_malloc(caller_ctx, sizeof(JSValue) * call_info.argc); + if (!saved_argv) { + JS_FreeValue(caller_ctx, func_obj_saved); + JS_FreeValue(caller_ctx, this_obj_saved); + JS_FreeValue(caller_ctx, new_target_saved); + JS_ThrowOutOfMemory(caller_ctx); + goto exception_unwind; + } + for (i = 0; i < call_info.argc; i++) + saved_argv[i] = JS_DupValue(caller_ctx, call_info.argv[i]); + } + + /* Now safe to pop current frame */ vm_pop_frame(caller_ctx); /* If we've popped past entry, this was a tail call at top level */ if (caller_ctx->frame_stack_top < initial_frame_top) { /* Push the tail-called function as a new frame */ - frame = vm_push_frame(caller_ctx, call_info.func_obj, - call_info.this_obj, call_info.new_target, - call_info.argc, call_info.argv, 0, + frame = vm_push_frame(caller_ctx, func_obj_saved, + this_obj_saved, new_target_saved, + call_info.argc, saved_argv, + JS_CALL_FLAG_COPY_ARGV, NULL, 0, 0, 0); } else { /* Get parent frame's continuation info */ struct VMFrame *parent = &caller_ctx->frame_stack[caller_ctx->frame_stack_top]; - frame = vm_push_frame(caller_ctx, call_info.func_obj, - call_info.this_obj, call_info.new_target, - call_info.argc, call_info.argv, 0, + frame = vm_push_frame(caller_ctx, func_obj_saved, + this_obj_saved, new_target_saved, + call_info.argc, saved_argv, + JS_CALL_FLAG_COPY_ARGV, parent->ret_pc, parent->ret_sp_offset, parent->call_argc, parent->call_has_this); } + + /* Free our temporary copies (vm_push_frame duped them again) */ + JS_FreeValue(caller_ctx, func_obj_saved); + JS_FreeValue(caller_ctx, this_obj_saved); + JS_FreeValue(caller_ctx, new_target_saved); + for (i = 0; i < call_info.argc; i++) + JS_FreeValue(caller_ctx, saved_argv[i]); + js_free(caller_ctx, saved_argv); } else { /* Regular call: save continuation and push new frame */ frame->ret_pc = call_info.ret_pc; @@ -13115,7 +13161,8 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, frame = vm_push_frame(caller_ctx, call_info.func_obj, call_info.this_obj, call_info.new_target, - call_info.argc, call_info.argv, 0, + 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); } @@ -13149,29 +13196,2455 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, return ret_val; } -/* Execute a single frame - runs bytecode until call/return/exception */ -static VMExecState vm_execute_frame(JSContext *ctx, struct VMFrame *frame, - JSValue *ret_val, VMCallInfo *call_info) +/* Execute a single frame - runs bytecode until call/return/exception. + This is the core bytecode interpreter, using VMFrame's value_stack + instead of alloca. When a bytecode-to-bytecode call is needed, it returns + VM_EXEC_CALL; the trampoline dispatcher then pushes a new frame. +*/ +static VMExecState vm_execute_frame(JSContext *caller_ctx, struct VMFrame *frame, + JSValue *return_value, VMCallInfo *call_info) { - JSValue *arg_buf; + JSRuntime *rt = caller_ctx->rt; + JSContext *ctx; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + const uint8_t *pc; + int opcode, i; + JSValue *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; + JSVarRef **var_refs; + JSValue this_obj, new_target; + + /* Load execution state from VMFrame */ + b = frame->b; + ctx = frame->ctx; + pc = frame->pc; + var_refs = frame->var_refs; + this_obj = frame->this_obj; + new_target = frame->new_target; + + /* Set up pointers into the value stack (already allocated by vm_push_frame) */ + stack_buf = &caller_ctx->value_stack[frame->value_stack_base]; - /* Handle aliased args: get from frame if allocated, otherwise use stored pointer */ if (frame->arg_buf_offset >= 0) { - arg_buf = &ctx->value_stack[frame->value_stack_base + frame->arg_buf_offset]; + arg_buf = stack_buf + frame->arg_buf_offset; } else { - /* Aliased args - shouldn't happen with current vm_push_frame */ - arg_buf = NULL; + arg_buf = stack_buf; /* Aliased - fallback */ } - /* For now, delegate to old implementation while we transform. - The OLD implementation handles its own stack and exceptions, - so we just pass through the result. */ - *ret_val = JS_CallInternal_OLD(ctx, frame->cur_func, frame->this_obj, - frame->new_target, frame->arg_count, - arg_buf, 0); - if (JS_IsException(*ret_val)) + var_buf = stack_buf + frame->var_buf_offset; + sp = stack_buf + frame->sp_offset; + + /* Set up temporary JSStackFrame for closure/backtrace compatibility */ + sf->js_mode = frame->js_mode; + sf->cur_func = frame->cur_func; + sf->arg_count = frame->arg_count; + sf->var_buf = var_buf; + sf->arg_buf = arg_buf; + sf->cur_pc = pc; + /* Initialize our own var_ref_list - closures will add refs here. + We'll transfer any refs to frame->var_ref_list when saving state. */ + init_list_head(&sf->var_ref_list); + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + +#define SWITCH(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break + + /* Main bytecode dispatch loop */ +restart: + for (;;) { + int call_argc; + JSValue *call_argv; + +#ifdef DUMP_PROFILE + b->profile.self_ticks++; + profile_try_sample(rt, b, pc); +#endif + + SWITCH(pc) { + *sp++ = JS_NewInt32(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_const): + *sp++ = JS_DupValue(ctx, b->cpool[get_u32(pc)]); + pc += 4; + BREAK; +#if SHORT_OPCODES + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *sp++ = JS_NewInt32(ctx, opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *sp++ = JS_NewInt32(ctx, get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *sp++ = JS_NewInt32(ctx, get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_const8): + *sp++ = JS_DupValue(ctx, b->cpool[*pc++]); + BREAK; + CASE(OP_fclosure8): + *sp++ = js_closure(ctx, JS_DupValue(ctx, b->cpool[*pc++]), var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_push_empty_string): + *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); + BREAK; + CASE(OP_get_length): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; +#endif + CASE(OP_push_atom_value): + *sp++ = JS_AtomToValue(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_null): + *sp++ = JS_NULL; + BREAK; + CASE(OP_push_this): + /* OP_push_this is only called at the start of a function */ + { + JSValue val = JS_DupValue(ctx, this_obj); + *sp++ = val; + } + BREAK; + CASE(OP_push_false): + *sp++ = JS_FALSE; + BREAK; + CASE(OP_push_true): + *sp++ = JS_TRUE; + BREAK; + CASE(OP_object): + *sp++ = JS_NewObject(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_special_object): + { + int arg = *pc++; + switch(arg) { + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = JS_DupValue(ctx, sf->cur_func); + break; + case OP_SPECIAL_OBJECT_NEW_TARGET: + *sp++ = JS_DupValue(ctx, new_target); + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + default: + abort(); + } + } + BREAK; + CASE(OP_rest): + { + int first = get_u16(pc); + pc += 2; + *sp++ = js_build_rest(ctx, first, frame->arg_count, (JSValueConst *)arg_buf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + + CASE(OP_drop): + JS_FreeValue(ctx, sp[-1]); + sp--; + BREAK; + CASE(OP_nip): + JS_FreeValue(ctx, sp[-2]); + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + JS_FreeValue(ctx, sp[-3]); + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_dup): + sp[0] = JS_DupValue(ctx, sp[-1]); + sp++; + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + sp[0] = JS_DupValue(ctx, sp[-2]); + sp[1] = JS_DupValue(ctx, sp[-1]); + sp += 2; + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + sp[0] = JS_DupValue(ctx, sp[-3]); + sp[1] = JS_DupValue(ctx, sp[-2]); + sp[2] = JS_DupValue(ctx, sp[-1]); + sp += 3; + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = JS_DupValue(ctx, sp[-2]); + sp++; + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot4l): /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot5l): /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot3r): /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_swap2): /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE(OP_fclosure): + { + JSValue bfunc = JS_DupValue(ctx, b->cpool[get_u32(pc)]); + pc += 4; + *sp++ = js_closure(ctx, bfunc, var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE(OP_call0): + CASE(OP_call1): + CASE(OP_call2): + CASE(OP_call3): + call_argc = opcode - OP_call0; + goto has_call_argc; +#endif + CASE(OP_call): + CASE(OP_tail_call): + { + call_argc = get_u16(pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; +#ifdef DUMP_PROFILE + profile_record_call_site(rt, b, (uint32_t)(pc - b->byte_code_buf)); +#endif + /* Check if target is a bytecode function - use trampoline */ + if (JS_VALUE_GET_TAG(call_argv[-1]) == JS_TAG_OBJECT) { + JSObject *call_p = JS_VALUE_GET_OBJ(call_argv[-1]); + if (call_p->class_id == JS_CLASS_BYTECODE_FUNCTION) { + /* Save state and return to trampoline */ + frame->pc = pc; + frame->sp_offset = sp - stack_buf; + + call_info->func_obj = call_argv[-1]; + call_info->this_obj = JS_NULL; + call_info->new_target = JS_NULL; + call_info->argc = call_argc; + call_info->argv = call_argv; + call_info->ret_pc = pc; + call_info->ret_sp_offset = (call_argv - 1) - stack_buf; + call_info->call_argc = call_argc; + call_info->call_has_this = 0; + call_info->is_tail_call = (opcode == OP_tail_call); + + rt->current_stack_frame = sf->prev_frame; + return VM_EXEC_CALL; + } + } + /* C function or other callable - call directly */ + { + JSValue call_ret = JS_CallInternal(ctx, call_argv[-1], JS_NULL, + JS_NULL, call_argc, call_argv, 0); + if (unlikely(JS_IsException(call_ret))) + goto exception; + if (opcode == OP_tail_call) { + ret_val = call_ret; + goto done; + } + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = call_ret; + } + } + BREAK; + CASE(OP_call_constructor): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; +#ifdef DUMP_PROFILE + /* Record call site */ + profile_record_call_site(rt, b, (uint32_t)(pc - b->byte_code_buf)); +#endif + ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], + call_argv[-1], + call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_method): + CASE(OP_tail_call_method): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; +#ifdef DUMP_PROFILE + profile_record_call_site(rt, b, (uint32_t)(pc - b->byte_code_buf)); +#endif + /* Check if target is a bytecode function - use trampoline */ + if (JS_VALUE_GET_TAG(call_argv[-1]) == JS_TAG_OBJECT) { + JSObject *call_p = JS_VALUE_GET_OBJ(call_argv[-1]); + if (call_p->class_id == JS_CLASS_BYTECODE_FUNCTION) { + /* Save state and return to trampoline */ + frame->pc = pc; + frame->sp_offset = sp - stack_buf; + + call_info->func_obj = call_argv[-1]; + call_info->this_obj = call_argv[-2]; + call_info->new_target = JS_NULL; + call_info->argc = call_argc; + call_info->argv = call_argv; + call_info->ret_pc = pc; + call_info->ret_sp_offset = (call_argv - 2) - stack_buf; + call_info->call_argc = call_argc; + call_info->call_has_this = 1; + call_info->is_tail_call = (opcode == OP_tail_call_method); + + rt->current_stack_frame = sf->prev_frame; + return VM_EXEC_CALL; + } + } + /* C function or other callable - call directly */ + { + JSValue call_ret = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], + JS_NULL, call_argc, call_argv, 0); + if (unlikely(JS_IsException(call_ret))) + goto exception; + if (opcode == OP_tail_call_method) { + ret_val = call_ret; + goto done; + } + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = call_ret; + } + } + BREAK; + CASE(OP_array_from): + { + int i, ret; + + call_argc = get_u16(pc); + pc += 2; + ret_val = JS_NewArray(ctx); + if (unlikely(JS_IsException(ret_val))) + goto exception; + call_argv = sp - call_argc; + for(i = 0; i < call_argc; i++) { + ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i], + JS_PROP_C_W_E | JS_PROP_THROW); + call_argv[i] = JS_NULL; + if (ret < 0) { + JS_FreeValue(ctx, ret_val); + goto exception; + } + } + sp -= call_argc; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_apply): + { + int magic; + magic = get_u16(pc); + pc += 2; + sf->cur_pc = pc; + + ret_val = js_function_apply(ctx, sp[-3], 2, (JSValueConst *)&sp[-2], magic); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + *sp++ = ret_val; + } + BREAK; + CASE(OP_return): + ret_val = *--sp; + goto done; + CASE(OP_return_undef): + ret_val = JS_NULL; + goto done; + + CASE(OP_throw): + JS_Throw(ctx, *--sp); + goto exception; + + CASE(OP_throw_error): +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSAtom atom; + int type; + atom = get_u32(pc); + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_RO) + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); + else + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); + else + if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized(ctx, atom); + else + if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError(ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError(ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE(OP_eval): + { + JSValueConst obj; + int scope_idx; + call_argc = get_u16(pc); + scope_idx = get_u16(pc + 2) + ARG_SCOPE_END; + pc += 4; + call_argv = sp - call_argc; + sf->cur_pc = pc; + if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { + if (call_argc >= 1) + obj = call_argv[0]; + else + obj = JS_NULL; + ret_val = JS_EvalObject(ctx, JS_NULL, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_NULL, + JS_NULL, call_argc, call_argv, 0); + } + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + /* could merge with OP_apply */ + CASE(OP_apply_eval): + { + int scope_idx; + uint32_t len; + JSValue *tab; + JSValueConst obj; + + scope_idx = get_u16(pc) + ARG_SCOPE_END; + pc += 2; + sf->cur_pc = pc; + tab = build_arg_list(ctx, &len, sp[-1]); + if (!tab) + goto exception; + if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { + if (len >= 1) + obj = tab[0]; + else + obj = JS_NULL; + ret_val = JS_EvalObject(ctx, JS_NULL, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_Call(ctx, sp[-2], JS_NULL, len, + (JSValueConst *)tab); + } + free_arg_list(ctx, tab, len); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_regexp): + { + sp[-2] = js_regexp_constructor_internal(ctx, JS_NULL, + sp[-2], sp[-1]); + sp--; + } + BREAK; + + CASE(OP_check_var): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_CheckGlobalVar(ctx, atom); + if (ret < 0) + goto exception; + *sp++ = JS_NewBool(ctx, ret); + } + BREAK; + + CASE(OP_get_var_undef): + CASE(OP_get_var): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_var): + CASE(OP_put_var_init): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_var_strict): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + /* sp[-2] is JS_TRUE or JS_FALSE */ + if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_check_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + sf->cur_pc = pc; + if (JS_CheckDefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + sf->cur_pc = pc; + if (JS_DefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_func): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + sf->cur_pc = pc; + if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_DupValue(ctx, sp[-1])); + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = JS_DupValue(ctx, arg_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], JS_DupValue(ctx, sp[-1])); + } + BREAK; + +#if SHORT_OPCODES + CASE(OP_get_loc8): *sp++ = JS_DupValue(ctx, var_buf[*pc++]); BREAK; + CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; + CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], JS_DupValue(ctx, sp[-1])); BREAK; + + CASE(OP_get_loc0): *sp++ = JS_DupValue(ctx, var_buf[0]); BREAK; + CASE(OP_get_loc1): *sp++ = JS_DupValue(ctx, var_buf[1]); BREAK; + CASE(OP_get_loc2): *sp++ = JS_DupValue(ctx, var_buf[2]); BREAK; + CASE(OP_get_loc3): *sp++ = JS_DupValue(ctx, var_buf[3]); BREAK; + CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; + CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; + CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; + CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; + CASE(OP_set_loc0): set_value(ctx, &var_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc1): set_value(ctx, &var_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc2): set_value(ctx, &var_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc3): set_value(ctx, &var_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_get_arg0): *sp++ = JS_DupValue(ctx, arg_buf[0]); BREAK; + CASE(OP_get_arg1): *sp++ = JS_DupValue(ctx, arg_buf[1]); BREAK; + CASE(OP_get_arg2): *sp++ = JS_DupValue(ctx, arg_buf[2]); BREAK; + CASE(OP_get_arg3): *sp++ = JS_DupValue(ctx, arg_buf[3]); BREAK; + CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; + CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; + CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; + CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; + CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_get_var_ref0): *sp++ = JS_DupValue(ctx, *var_refs[0]->pvalue); BREAK; + CASE(OP_get_var_ref1): *sp++ = JS_DupValue(ctx, *var_refs[1]->pvalue); BREAK; + CASE(OP_get_var_ref2): *sp++ = JS_DupValue(ctx, *var_refs[2]->pvalue); BREAK; + CASE(OP_get_var_ref3): *sp++ = JS_DupValue(ctx, *var_refs[3]->pvalue); BREAK; + CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; + CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; +#endif + + CASE(OP_get_var_ref): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + sp[0] = JS_DupValue(ctx, val); + sp++; + } + BREAK; + CASE(OP_put_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, JS_DupValue(ctx, sp[-1])); + } + BREAK; + CASE(OP_get_var_ref_check): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + sp[0] = JS_DupValue(ctx, val); + sp++; + } + BREAK; + CASE(OP_put_var_ref_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_var_ref_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc_uninitialized): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE(OP_get_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); + goto exception; + } + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_get_loc_checkthis): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, FALSE); + goto exception; + } + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_loc_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceError(ctx, "'this' can be initialized only once"); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_close_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + close_lexical_var(ctx, sf, idx); + } + BREAK; + + CASE(OP_make_loc_ref): + CASE(OP_make_arg_ref): + CASE(OP_make_var_ref_ref): + { + JSVarRef *var_ref; + JSProperty *pr; + JSAtom atom; + int idx; + atom = get_u32(pc); + idx = get_u16(pc + 4); + pc += 6; + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + if (opcode == OP_make_var_ref_ref) { + var_ref = var_refs[idx]; + var_ref->header.ref_count++; + } else { + var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); + if (!var_ref) + goto exception; + } + pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, + JS_PROP_WRITABLE | JS_PROP_VARREF); + if (!pr) { + free_var_ref(rt, var_ref); + goto exception; + } + pr->u.var_ref = var_ref; + *sp++ = JS_AtomToValue(ctx, atom); + } + BREAK; + CASE(OP_make_var_ref): + { + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + if (JS_GetGlobalVarRef(ctx, atom, sp)) + goto exception; + sp += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; +#if SHORT_OPCODES + CASE(OP_goto16): + pc += (int16_t)get_u16(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto8): + pc += (int8_t)pc[0]; + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; +#endif + CASE(OP_if_true): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and JS_TAG_UNDEFINED */ + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE(OP_if_true8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; +#endif + CASE(OP_catch): + { + int32_t diff; + diff = get_u32(pc); + sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE(OP_gosub): + { + int32_t diff; + diff = get_u32(pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = JS_NewInt32(ctx, pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) + goto ret_fail; + pos = JS_VALUE_GET_INT(op1); + if (unlikely(pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError(ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE(OP_for_in_start): + sf->cur_pc = pc; + if (js_for_in_start(ctx, sp)) + goto exception; + BREAK; + CASE(OP_for_in_next): + sf->cur_pc = pc; + if (js_for_in_next(ctx, sp)) + goto exception; + sp += 2; + BREAK; + CASE(OP_for_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_for_of_next): + { + int offset = -3 - pc[0]; + pc += 1; + sf->cur_pc = pc; + if (js_for_of_next(ctx, sp, offset)) + goto exception; + sp += 2; + } + BREAK; + CASE(OP_iterator_get_value_done): + sf->cur_pc = pc; + if (js_iterator_get_value_done(ctx, sp)) + goto exception; + sp += 1; + BREAK; + CASE(OP_iterator_check_object): + if (unlikely(!JS_IsObject(sp[-1]))) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto exception; + } + BREAK; + + CASE(OP_iterator_close): + /* iter_obj next catch_offset -> */ + sp--; /* drop the catch offset to avoid getting caught by exception */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + if (!JS_IsNull(sp[-1])) { + sf->cur_pc = pc; + if (JS_IteratorClose(ctx, sp[-1], FALSE)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + } + sp--; + BREAK; + CASE(OP_nip_catch): + { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf && + JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { + JS_FreeValue(ctx, *--sp); + } + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); + JS_FreeValue(ctx, ret_val); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_iterator_next): + /* stack: iter_obj next catch_offset val */ + { + JSValue ret; + sf->cur_pc = pc; + ret = JS_Call(ctx, sp[-3], sp[-4], + 1, (JSValueConst *)(sp - 1)); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + } + BREAK; + + CASE(OP_iterator_call): + /* stack: iter_obj next catch_offset val */ + { + JSValue method, ret; + BOOL ret_flag; + int flags; + flags = *pc++; + sf->cur_pc = pc; + method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? + JS_ATOM_throw : JS_ATOM_return); + if (JS_IsException(method)) + goto exception; + if (JS_IsNull(method) || JS_IsNull(method)) { + ret_flag = TRUE; + } else { + if (flags & 2) { + /* no argument */ + ret = JS_CallFree(ctx, method, sp[-4], + 0, NULL); + } else { + ret = JS_CallFree(ctx, method, sp[-4], + 1, (JSValueConst *)(sp - 1)); + } + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + ret_flag = FALSE; + } + sp[0] = JS_NewBool(ctx, ret_flag); + sp += 1; + } + BREAK; + + CASE(OP_lnot): + { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT(op1) != 0; + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp[-1] = JS_NewBool(ctx, !res); + } + BREAK; + + CASE(OP_get_field): + { + JSValue val; + JSAtom atom; + JSValue obj; + const uint8_t *ic_pc; + + ic_pc = pc - 1; /* PC of opcode, before consuming operands */ + atom = get_u32(pc); + pc += 4; + + sf->cur_pc = pc; +#ifdef DUMP_PROFILE + /* Record property access site */ + profile_record_prop_site(rt, b, (uint32_t)(pc - b->byte_code_buf), atom); +#endif + obj = sp[-1]; + + /* Monomorphic IC fast path: shape-guarded own-property lookup */ + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSShape *sh = p->shape; + + /* Simple thread-local IC cache using PC as key */ + static __thread struct { + const uint8_t *pc; + JSShape *shape; + uint32_t prop_idx; + JSAtom atom; + } ic_cache[256]; + + uint32_t cache_idx = ((uintptr_t)ic_pc >> 3) & 255; + struct { const uint8_t *pc; JSShape *shape; uint32_t prop_idx; JSAtom atom; } *slot = &ic_cache[cache_idx]; + + /* IC hit: shape guard passed */ + if (likely(slot->pc == ic_pc && slot->shape == sh && slot->atom == atom)) { + JSProperty *pr = &p->prop[slot->prop_idx]; + JSShapeProperty *prs = &get_shape_prop(sh)[slot->prop_idx]; + + /* Double-check it's still a normal data property */ + if (likely((prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL)) { + val = JS_DupValue(ctx, pr->u.value); + JS_FreeValue(ctx, obj); + sp[-1] = val; + goto get_field_done; + } + } + + /* IC miss: do lookup and populate cache if it's an own data property */ + { + JSProperty *pr; + JSShapeProperty *prs = find_own_property(&pr, p, atom); + + if (prs && (prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL) { + /* Cache this for next time */ + uint32_t prop_idx = prs - get_shape_prop(sh); + slot->pc = ic_pc; + slot->shape = sh; + slot->prop_idx = prop_idx; + slot->atom = atom; + + val = JS_DupValue(ctx, pr->u.value); + JS_FreeValue(ctx, obj); + sp[-1] = val; + goto get_field_done; + } + } + } + + /* Slow path: proto chain, getters, non-objects, etc. */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + get_field_done: + ; + } + BREAK; + + CASE(OP_get_field2): + { + JSValue val; + JSAtom atom; + JSValue obj; + const uint8_t *ic_pc; + + ic_pc = pc - 1; + atom = get_u32(pc); + pc += 4; + + sf->cur_pc = pc; +#ifdef DUMP_PROFILE + /* Record property access site */ + profile_record_prop_site(rt, b, (uint32_t)(pc - b->byte_code_buf), atom); +#endif + obj = sp[-1]; + + /* Monomorphic IC fast path */ + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSShape *sh = p->shape; + + static __thread struct { + const uint8_t *pc; + JSShape *shape; + uint32_t prop_idx; + JSAtom atom; + } ic_cache2[256]; + + uint32_t cache_idx = ((uintptr_t)ic_pc >> 3) & 255; + struct { const uint8_t *pc; JSShape *shape; uint32_t prop_idx; JSAtom atom; } *slot = &ic_cache2[cache_idx]; + + if (likely(slot->pc == ic_pc && slot->shape == sh && slot->atom == atom)) { + JSProperty *pr = &p->prop[slot->prop_idx]; + JSShapeProperty *prs = &get_shape_prop(sh)[slot->prop_idx]; + + if (likely((prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL)) { + val = JS_DupValue(ctx, pr->u.value); + *sp++ = val; + goto get_field2_done; + } + } + + { + JSProperty *pr; + JSShapeProperty *prs = find_own_property(&pr, p, atom); + + if (prs && (prs->flags & JS_PROP_TMASK) == JS_PROP_NORMAL) { + uint32_t prop_idx = prs - get_shape_prop(sh); + slot->pc = ic_pc; + slot->shape = sh; + slot->prop_idx = prop_idx; + slot->atom = atom; + + val = JS_DupValue(ctx, pr->u.value); + *sp++ = val; + goto get_field2_done; + } + } + } + + /* Slow path */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + get_field2_done: + ; + } + BREAK; + + CASE(OP_put_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + +#ifdef DUMP_PROFILE + /* Record property access site */ + profile_record_prop_site(rt, b, (uint32_t)(pc - b->byte_code_buf), atom); +#endif + ret = JS_SetPropertyInternal(ctx, sp[-2], atom, sp[-1], sp[-2], + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_set_name): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_name_computed): + { + int ret; + ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_proto): + { + JSValue proto; + sf->cur_pc = pc; + proto = sp[-1]; + if (JS_IsObject(proto) || JS_IsNull(proto)) { + if (JS_SetPrototypeInternal(ctx, sp[-2], proto) < 0) + goto exception; + } + JS_FreeValue(ctx, proto); + sp--; + } + BREAK; + CASE(OP_define_method): + CASE(OP_define_method_computed): + { + JSValue getter, setter, value; + JSValueConst obj; + JSAtom atom; + int flags, ret, op_flags; + BOOL is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + opcode += OP_define_method - OP_define_method_computed; + } else { + atom = get_u32(pc); + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; + if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) + flags |= JS_PROP_ENUMERABLE; + op_flags &= 3; + value = JS_NULL; + getter = JS_NULL; + setter = JS_NULL; + if (op_flags == OP_DEFINE_METHOD_METHOD) { + value = sp[-1]; + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + } else if (op_flags == OP_DEFINE_METHOD_GETTER) { + getter = sp[-1]; + flags |= JS_PROP_HAS_GET; + } else { + setter = sp[-1]; + flags |= JS_PROP_HAS_SET; + } + ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); + if (ret >= 0) { + ret = JS_DefineProperty(ctx, obj, atom, value, + getter, setter, flags); + } + JS_FreeValue(ctx, sp[-1]); + if (is_computed) { + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + } + sp -= 1 + is_computed; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_class): + CASE(OP_define_class_computed): + { + int class_flags; + JSAtom atom; + + atom = get_u32(pc); + class_flags = pc[4]; + pc += 5; + if (js_op_define_class(ctx, sp, atom, class_flags, + var_refs, sf, + (opcode == OP_define_class_computed)) < 0) + goto exception; + } + BREAK; + + CASE(OP_get_array_el): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_array_el2): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + sp[-1] = val; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_array_el3): + { + JSValue val; + + switch (JS_VALUE_GET_TAG(sp[-2])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + /* undefined and null are tested in JS_GetPropertyValue() */ + break; + default: + /* must be tested nefore JS_ToPropertyKey */ + if (unlikely(JS_IsNull(sp[-2]) || JS_IsNull(sp[-2]))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], JS_DupValue(ctx, sp[-1])); + *sp++ = val; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_ref_value): + { + JSValue val; + JSAtom atom; + int ret; + + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (atom == JS_ATOM_NULL) + goto exception; + if (unlikely(JS_IsNull(sp[-2]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + ret = JS_HasProperty(ctx, sp[-2], atom); + if (unlikely(ret <= 0)) { + if (ret < 0) { + JS_FreeAtom(ctx, atom); + goto exception; + } + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + val = JS_NULL; + } else { + val = JS_GetProperty(ctx, sp[-2], atom); + } + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + sp++; + } + BREAK; + + CASE(OP_put_array_el): + { + int ret; + + sf->cur_pc = pc; + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_ref_value): + { + int ret; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + if (unlikely(JS_IsNull(sp[-3]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + ret = JS_HasProperty(ctx, sp[-3], atom); + if (unlikely(ret <= 0)) { + if (unlikely(ret < 0)) { + JS_FreeAtom(ctx, atom); + goto exception; + } + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + ret = JS_SetPropertyInternal(ctx, sp[-3], atom, sp[-1], sp[-3], JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_array_el): + { + int ret; + ret = JS_DefinePropertyValueValue(ctx, sp[-3], JS_DupValue(ctx, sp[-2]), sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp -= 1; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_append): /* array pos enumobj -- array pos */ + { + sf->cur_pc = pc; + if (js_append_enumerate(ctx, sp)) + goto exception; + JS_FreeValue(ctx, *--sp); + } + BREAK; + + CASE(OP_copy_data_properties): /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + sf->cur_pc = pc; + if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], + sp[-1 - ((mask >> 2) & 7)], + sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE(OP_add): + CASE(OP_add_float): + { + JSValue op1 = sp[-2], op2 = sp[-1], res; + int tag1 = JS_VALUE_GET_NORM_TAG(op1); + int tag2 = JS_VALUE_GET_NORM_TAG(op2); + + /* 1) both ints? keep fast int path with overflow check */ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t tmp = (int64_t)JS_VALUE_GET_INT(op1) + + JS_VALUE_GET_INT(op2); + if (likely((int)tmp == tmp)) { + res = JS_NewInt32(ctx, (int)tmp); + } else { + res = __JS_NewFloat64(ctx, (double)JS_VALUE_GET_INT(op1) + (double)JS_VALUE_GET_INT(op2)); + } + } + /* 2) both floats? */ + else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + res = __JS_NewFloat64(ctx, + JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); + } + /* 3) both strings? */ + else if (JS_IsString(op1) && JS_IsString(op2)) { + res = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(res)) + goto exception; + } + /* 4) mixed int/float? promote to float */ + // TODO: Seems slow + else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || + (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { + double a, b; + if(tag1 == JS_TAG_INT) + a = (double)JS_VALUE_GET_INT(op1); + else + a = JS_VALUE_GET_FLOAT64(op1); + if(tag2 == JS_TAG_INT) + b = (double)JS_VALUE_GET_INT(op2); + else + b = JS_VALUE_GET_FLOAT64(op2); + res = __JS_NewFloat64(ctx, a + b); + } else if (tag1 == JS_TAG_NULL || tag2 == JS_TAG_NULL) { + res = JS_NULL; + } + /* 5) anything else → throw */ + else { + JS_ThrowTypeError(ctx, "cannot concatenate with string"); + goto exception; + } + + sp[-2] = res; + sp--; + } + BREAK; + CASE(OP_add_loc): + { + int idx = *pc++; + JSValue rhs = sp[-1]; + JSValue lhs = var_buf[idx]; + JSValue res; + int tag1 = JS_VALUE_GET_NORM_TAG(lhs); + int tag2 = JS_VALUE_GET_NORM_TAG(rhs); + + /* 1) both ints? fast path, overflow → float64 */ + if (likely(JS_VALUE_IS_BOTH_INT(lhs, rhs))) { + int a_i = JS_VALUE_GET_INT(lhs); + int b_i = JS_VALUE_GET_INT(rhs); + int64_t tmp = (int64_t)a_i + b_i; + if ((int)tmp == tmp) + res = JS_NewInt32(ctx, (int)tmp); + else + res = __JS_NewFloat64(ctx, (double)a_i + (double)b_i); + } + /* 2) both floats? */ + else if (JS_VALUE_IS_BOTH_FLOAT(lhs, rhs)) { + res = __JS_NewFloat64(ctx, + JS_VALUE_GET_FLOAT64(lhs) + + JS_VALUE_GET_FLOAT64(rhs)); + } + /* 3) both strings? */ + else if (JS_IsString(lhs) && JS_IsString(rhs)) { + res = JS_ConcatString(ctx, lhs, rhs); + if (JS_IsException(res)) + goto exception; + } + /* 4) mixed int/float? promote to float64 */ + else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || + (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { + double a = tag1 == JS_TAG_INT + ? (double)JS_VALUE_GET_INT(lhs) + : JS_VALUE_GET_FLOAT64(lhs); + double b = tag2 == JS_TAG_INT + ? (double)JS_VALUE_GET_INT(rhs) + : JS_VALUE_GET_FLOAT64(rhs); + res = __JS_NewFloat64(ctx, a + b); + } + /* 5) anything else → throw */ + else { + JS_ThrowTypeError(ctx, "cannot concatenate with string"); + goto exception; + } + + var_buf[idx] = res; + sp--; + } + BREAK; + CASE(OP_sub): + CASE(OP_sub_float): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto binary_arith_slow; + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mul): + CASE(OP_mul_float): + { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + r = (int64_t)v1 * v2; + if (unlikely((int)r != r)) { + d = (double)r; + goto mul_fp_res; + } + /* need to test zero case for -0 result */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + d = -0.0; + goto mul_fp_res; + } + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + mul_fp_res: + sp[-2] = __JS_NewFloat64(ctx, d); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_div): + CASE(OP_div_float): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + sf->cur_pc = pc; + if (js_binary_arith_slow(ctx, sp, opcode)) + goto exception; + sp--; + BREAK; + + CASE(OP_plus): + { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + /* Note: -0 cannot be expressed as integer */ + if (unlikely(val == 0)) { + d = -0.0; + goto neg_fp_res; + } + if (unlikely(val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = JS_NewInt32(ctx, -val); + } else if (JS_TAG_IS_FLOAT64(tag)) { + d = -JS_VALUE_GET_FLOAT64(op1); + neg_fp_res: + sp[-1] = __JS_NewFloat64(ctx, d); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_slow; + sp[-1] = JS_NewInt32(ctx, val + 1); + } else { + inc_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_slow; + sp[-1] = JS_NewInt32(ctx, val - 1); + } else { + dec_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_post_inc): + CASE(OP_post_dec): + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + sp++; + BREAK; + CASE(OP_inc_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_loc_slow; + var_buf[idx] = JS_NewInt32(ctx, val + 1); + } else { + inc_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = JS_DupValue(ctx, op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_dec_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_loc_slow; + var_buf[idx] = JS_NewInt32(ctx, val - 1); + } else { + dec_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = JS_DupValue(ctx, op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_not): + { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + sp[-1] = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1)); + } else { + sf->cur_pc = pc; + if (js_not_slow(ctx, sp)) + goto exception; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32(ctx, v1 << v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || + (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || + (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); + v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32(ctx, v1 << v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = JS_NewUint32(ctx, + (uint32_t)JS_VALUE_GET_INT(op1) >> + v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || + (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || + (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); + v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); + v2 &= 0x1f; + sp[-2] = JS_NewUint32(ctx, v1 >> v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32(ctx, + (int)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || + (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || + (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { + int32_t v1; + uint32_t v2; + v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); + v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32(ctx, v1 >> v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) & + JS_VALUE_GET_INT(op2)); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || + (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || + (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { + int32_t v1, v2; + v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); + v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); + sp[-2] = JS_NewInt32(ctx, v1 & v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) | + JS_VALUE_GET_INT(op2)); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || + (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || + (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { + int32_t v1, v2; + v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); + v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); + sp[-2] = JS_NewInt32(ctx, v1 | v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) ^ + JS_VALUE_GET_INT(op2)); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2) || + (JS_VALUE_GET_TAG(op1) == JS_TAG_INT && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) || + (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) && JS_VALUE_GET_TAG(op2) == JS_TAG_INT)) { + int32_t v1, v2; + v1 = JS_VALUE_GET_TAG(op1) == JS_TAG_INT ? JS_VALUE_GET_INT(op1) : (int32_t)JS_VALUE_GET_FLOAT64(op1); + v2 = JS_VALUE_GET_TAG(op2) == JS_TAG_INT ? JS_VALUE_GET_INT(op2) : (int32_t)JS_VALUE_GET_FLOAT64(op2); + sp[-2] = JS_NewInt32(ctx, v1 ^ v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp--; \ + } else { \ + sf->cur_pc = pc; \ + if (slow_call) \ + goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + + CASE(OP_in): + sf->cur_pc = pc; + if (js_operator_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_instanceof): + sf->cur_pc = pc; + if (js_operator_instanceof(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_typeof): + { + JSValue op1; + JSAtom atom; + + op1 = sp[-1]; + atom = js_operator_typeof(ctx, op1); + JS_FreeValue(ctx, op1); + sp[-1] = JS_AtomToString(ctx, atom); + } + BREAK; + CASE(OP_delete): + sf->cur_pc = pc; + if (js_operator_delete(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_delete_var): + { + JSAtom atom; + int ret; + + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_DeleteGlobalVar(ctx, atom); + if (unlikely(ret < 0)) + goto exception; + *sp++ = JS_NewBool(ctx, ret); + } + BREAK; + + CASE(OP_to_object): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { + sf->cur_pc = pc; + ret_val = JS_ToObject(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_to_propkey): + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + + CASE(OP_nop): + BREAK; + CASE(OP_is_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } +#if SHORT_OPCODES + CASE(OP_typeof_is_function): + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + free_and_set_true: + JS_FreeValue(ctx, sp[-1]); +#endif + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_FALSE; + BREAK; + CASE(OP_invalid): + DEFAULT: + JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } + +exception: + /* Save state for backtrace */ + sf->cur_pc = pc; + frame->pc = pc; + frame->sp_offset = sp - stack_buf; + + if (is_backtrace_needed(ctx, rt->current_exception)) { + build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0); + } + + /* Scan stack for exception handlers */ + if (!rt->current_exception_is_uncatchable) { + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT(val); + if (pos == 0) { + /* enumerator: close it with a throw */ + JS_FreeValue(ctx, sp[-1]); + sp--; + JS_IteratorClose(ctx, sp[-1], TRUE); + } else { + /* Found catch handler */ + *sp++ = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + } + + /* No handler found - propagate exception */ + ret_val = JS_EXCEPTION; + +done: + /* Close any var refs created during this frame's execution */ + if (unlikely(!list_empty(&sf->var_ref_list))) { + close_var_refs(rt, sf); + } + + /* Update frame state for vm_pop_frame to do cleanup. + vm_pop_frame will free values in [0, sp_offset) range. */ + frame->sp_offset = sp - stack_buf; + frame->pc = pc; + + rt->current_stack_frame = sf->prev_frame; + + *return_value = ret_val; + if (JS_IsException(ret_val)) return VM_EXEC_EXCEPTION; return VM_EXEC_RETURN; + +#undef SWITCH +#undef CASE +#undef DEFAULT +#undef BREAK } /* Trampoline-based JS_CallInternal - eliminates C stack recursion */