| /* |
| * Ecmascript compiler. |
| * |
| * Parses an input string and generates a function template result. |
| * Compilation may happen in multiple contexts (global code, eval |
| * code, function code). |
| * |
| * The parser uses a traditional top-down recursive parsing for the |
| * statement level, and an operator precedence based top-down approach |
| * for the expression level. The attempt is to minimize the C stack |
| * depth. Bytecode is generated directly without an intermediate |
| * representation (tree), at the cost of needing two passes over each |
| * function. |
| * |
| * The top-down recursive parser functions are named "duk__parse_XXX". |
| * |
| * Recursion limits are in key functions to prevent arbitrary C recursion: |
| * function body parsing, statement parsing, and expression parsing. |
| * |
| * See doc/compiler.rst for discussion on the design. |
| * |
| * A few typing notes: |
| * |
| * - duk_regconst_t: unsigned, no marker value for "none" |
| * - duk_reg_t: signed, < 0 = none |
| * - PC values: duk_int_t, negative values used as markers |
| */ |
| |
| #include "duk_internal.h" |
| |
| /* if highest bit of a register number is set, it refers to a constant instead */ |
| #define DUK__CONST_MARKER DUK_JS_CONST_MARKER |
| |
| /* for array and object literals */ |
| #define DUK__MAX_ARRAY_INIT_VALUES 20 |
| #define DUK__MAX_OBJECT_INIT_PAIRS 10 |
| |
| /* XXX: hack, remove when const lookup is not O(n) */ |
| #define DUK__GETCONST_MAX_CONSTS_CHECK 256 |
| |
| /* These limits are based on bytecode limits. Max temps is limited |
| * by duk_hcompiledfunction nargs/nregs fields being 16 bits. |
| */ |
| #define DUK__MAX_CONSTS DUK_BC_BC_MAX |
| #define DUK__MAX_FUNCS DUK_BC_BC_MAX |
| #define DUK__MAX_TEMPS 0xffffL |
| |
| /* Initial bytecode size allocation. */ |
| #define DUK__BC_INITIAL_INSTS 256 |
| |
| #define DUK__RECURSION_INCREASE(comp_ctx,thr) do { \ |
| DUK_DDD(DUK_DDDPRINT("RECURSION INCREASE: %s:%ld", (const char *) DUK_FILE_MACRO, (long) DUK_LINE_MACRO)); \ |
| duk__recursion_increase((comp_ctx)); \ |
| } while (0) |
| |
| #define DUK__RECURSION_DECREASE(comp_ctx,thr) do { \ |
| DUK_DDD(DUK_DDDPRINT("RECURSION DECREASE: %s:%ld", (const char *) DUK_FILE_MACRO, (long) DUK_LINE_MACRO)); \ |
| duk__recursion_decrease((comp_ctx)); \ |
| } while (0) |
| |
| /* Value stack slot limits: these are quite approximate right now, and |
| * because they overlap in control flow, some could be eliminated. |
| */ |
| #define DUK__COMPILE_ENTRY_SLOTS 8 |
| #define DUK__FUNCTION_INIT_REQUIRE_SLOTS 16 |
| #define DUK__FUNCTION_BODY_REQUIRE_SLOTS 16 |
| #define DUK__PARSE_STATEMENTS_SLOTS 16 |
| #define DUK__PARSE_EXPR_SLOTS 16 |
| |
| /* Temporary structure used to pass a stack allocated region through |
| * duk_safe_call(). |
| */ |
| typedef struct { |
| duk_small_uint_t flags; |
| duk_compiler_ctx comp_ctx_alloc; |
| duk_lexer_point lex_pt_alloc; |
| } duk__compiler_stkstate; |
| |
| /* |
| * Prototypes |
| */ |
| |
| /* lexing */ |
| DUK_LOCAL_DECL void duk__advance_helper(duk_compiler_ctx *comp_ctx, duk_small_int_t expect); |
| DUK_LOCAL_DECL void duk__advance_expect(duk_compiler_ctx *comp_ctx, duk_small_int_t expect); |
| DUK_LOCAL_DECL void duk__advance(duk_compiler_ctx *ctx); |
| |
| /* function helpers */ |
| DUK_LOCAL_DECL void duk__init_func_valstack_slots(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL void duk__reset_func_for_pass2(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL void duk__init_varmap_and_prologue_for_pass2(duk_compiler_ctx *comp_ctx, duk_reg_t *out_stmt_value_reg); |
| DUK_LOCAL_DECL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_bool_t force_no_namebind); |
| DUK_LOCAL_DECL duk_int_t duk__cleanup_varmap(duk_compiler_ctx *comp_ctx); |
| |
| /* code emission */ |
| DUK_LOCAL_DECL duk_int_t duk__get_current_pc(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL duk_compiler_instr *duk__get_instr_ptr(duk_compiler_ctx *comp_ctx, duk_int_t pc); |
| DUK_LOCAL_DECL void duk__emit(duk_compiler_ctx *comp_ctx, duk_instr_t ins); |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL void duk__emit_op_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t op); |
| #endif |
| DUK_LOCAL_DECL void duk__emit_a_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b, duk_regconst_t c); |
| DUK_LOCAL_DECL void duk__emit_a_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b); |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL void duk__emit_a(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a); |
| #endif |
| DUK_LOCAL_DECL void duk__emit_a_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t bc); |
| DUK_LOCAL_DECL void duk__emit_abc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op, duk_regconst_t abc); |
| DUK_LOCAL_DECL void duk__emit_extraop_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b, duk_regconst_t c); |
| DUK_LOCAL_DECL void duk__emit_extraop_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b); |
| DUK_LOCAL_DECL void duk__emit_extraop_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop, duk_regconst_t bc); |
| DUK_LOCAL_DECL void duk__emit_extraop_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags); |
| DUK_LOCAL_DECL void duk__emit_load_int32(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val); |
| DUK_LOCAL_DECL void duk__emit_load_int32_noshuffle(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val); |
| DUK_LOCAL_DECL void duk__emit_jump(duk_compiler_ctx *comp_ctx, duk_int_t target_pc); |
| DUK_LOCAL_DECL duk_int_t duk__emit_jump_empty(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL void duk__insert_jump_entry(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc); |
| DUK_LOCAL_DECL void duk__patch_jump(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc, duk_int_t target_pc); |
| DUK_LOCAL_DECL void duk__patch_jump_here(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc); |
| DUK_LOCAL_DECL void duk__patch_trycatch(duk_compiler_ctx *comp_ctx, duk_int_t ldconst_pc, duk_int_t trycatch_pc, duk_regconst_t reg_catch, duk_regconst_t const_varname, duk_small_uint_t flags); |
| DUK_LOCAL_DECL void duk__emit_if_false_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst); |
| DUK_LOCAL_DECL void duk__emit_if_true_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst); |
| DUK_LOCAL_DECL void duk__emit_invalid(duk_compiler_ctx *comp_ctx); |
| |
| /* ivalue/ispec helpers */ |
| DUK_LOCAL_DECL void duk__copy_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *src, duk_ispec *dst); |
| DUK_LOCAL_DECL void duk__copy_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *src, duk_ivalue *dst); |
| DUK_LOCAL_DECL duk_bool_t duk__is_whole_get_int32(duk_double_t x, duk_int32_t *ival); |
| DUK_LOCAL_DECL duk_reg_t duk__alloctemps(duk_compiler_ctx *comp_ctx, duk_small_int_t num); |
| DUK_LOCAL_DECL duk_reg_t duk__alloctemp(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL void duk__settemp_checkmax(duk_compiler_ctx *comp_ctx, duk_reg_t temp_next); |
| DUK_LOCAL_DECL duk_regconst_t duk__getconst(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL |
| duk_regconst_t duk__ispec_toregconst_raw(duk_compiler_ctx *comp_ctx, |
| duk_ispec *x, |
| duk_reg_t forced_reg, |
| duk_small_uint_t flags); |
| DUK_LOCAL_DECL void duk__ispec_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ispec *x, duk_reg_t forced_reg); |
| DUK_LOCAL_DECL void duk__ivalue_toplain_raw(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_reg_t forced_reg); |
| DUK_LOCAL_DECL void duk__ivalue_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *x); |
| DUK_LOCAL_DECL void duk__ivalue_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *x); |
| DUK_LOCAL_DECL |
| duk_regconst_t duk__ivalue_toregconst_raw(duk_compiler_ctx *comp_ctx, |
| duk_ivalue *x, |
| duk_reg_t forced_reg, |
| duk_small_uint_t flags); |
| DUK_LOCAL_DECL duk_reg_t duk__ivalue_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x); |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL duk_reg_t duk__ivalue_totemp(duk_compiler_ctx *comp_ctx, duk_ivalue *x); |
| #endif |
| DUK_LOCAL_DECL void duk__ivalue_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_int_t forced_reg); |
| DUK_LOCAL_DECL duk_regconst_t duk__ivalue_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x); |
| DUK_LOCAL_DECL duk_regconst_t duk__ivalue_totempconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x); |
| |
| /* identifier handling */ |
| DUK_LOCAL_DECL duk_reg_t duk__lookup_active_register_binding(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL duk_bool_t duk__lookup_lhs(duk_compiler_ctx *ctx, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname); |
| |
| /* label handling */ |
| DUK_LOCAL_DECL void duk__add_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_int_t pc_label, duk_int_t label_id); |
| DUK_LOCAL_DECL void duk__update_label_flags(duk_compiler_ctx *comp_ctx, duk_int_t label_id, duk_small_uint_t flags); |
| DUK_LOCAL_DECL void duk__lookup_active_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_bool_t is_break, duk_int_t *out_label_id, duk_int_t *out_label_catch_depth, duk_int_t *out_label_pc, duk_bool_t *out_is_closest); |
| DUK_LOCAL_DECL void duk__reset_labels_to_length(duk_compiler_ctx *comp_ctx, duk_int_t len); |
| |
| /* top-down expression parser */ |
| DUK_LOCAL_DECL void duk__expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__expr_led(duk_compiler_ctx *comp_ctx, duk_ivalue *left, duk_ivalue *res); |
| DUK_LOCAL_DECL duk_small_uint_t duk__expr_lbp(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL duk_bool_t duk__expr_is_empty(duk_compiler_ctx *comp_ctx); |
| |
| /* exprtop is the top level variant which resets nud/led counts */ |
| DUK_LOCAL_DECL void duk__expr(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| DUK_LOCAL_DECL void duk__exprtop(duk_compiler_ctx *ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| |
| /* convenience helpers */ |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL duk_reg_t duk__expr_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #endif |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL duk_reg_t duk__expr_totemp(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #endif |
| DUK_LOCAL_DECL void duk__expr_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg); |
| DUK_LOCAL_DECL duk_regconst_t duk__expr_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL duk_regconst_t duk__expr_totempconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #endif |
| DUK_LOCAL_DECL void duk__expr_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| DUK_LOCAL_DECL void duk__expr_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| DUK_LOCAL_DECL duk_reg_t duk__exprtop_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL duk_reg_t duk__exprtop_totemp(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #endif |
| DUK_LOCAL_DECL void duk__exprtop_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg); |
| DUK_LOCAL_DECL duk_regconst_t duk__exprtop_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #if 0 /* unused */ |
| DUK_LOCAL_DECL void duk__exprtop_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags); |
| #endif |
| |
| /* expression parsing helpers */ |
| DUK_LOCAL_DECL duk_int_t duk__parse_arguments(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL duk_bool_t duk__nud_object_literal_key_check(duk_compiler_ctx *comp_ctx, duk_small_uint_t new_key_flags); |
| |
| /* statement parsing */ |
| DUK_LOCAL_DECL void duk__parse_var_decl(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t expr_flags, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname); |
| DUK_LOCAL_DECL void duk__parse_var_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t expr_flags); |
| DUK_LOCAL_DECL void duk__parse_for_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site); |
| DUK_LOCAL_DECL void duk__parse_switch_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site); |
| DUK_LOCAL_DECL void duk__parse_if_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__parse_do_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site); |
| DUK_LOCAL_DECL void duk__parse_while_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site); |
| DUK_LOCAL_DECL void duk__parse_break_or_continue_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__parse_return_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__parse_throw_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__parse_try_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__parse_with_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res); |
| DUK_LOCAL_DECL void duk__parse_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_bool_t allow_source_elem); |
| DUK_LOCAL_DECL duk_int_t duk__stmt_label_site(duk_compiler_ctx *comp_ctx, duk_int_t label_id); |
| DUK_LOCAL_DECL void duk__parse_stmts(duk_compiler_ctx *comp_ctx, duk_bool_t allow_source_elem, duk_bool_t expect_eof); |
| |
| DUK_LOCAL_DECL void duk__parse_func_body(duk_compiler_ctx *comp_ctx, duk_bool_t expect_eof, duk_bool_t implicit_return_value, duk_small_int_t expect_token); |
| DUK_LOCAL_DECL void duk__parse_func_formals(duk_compiler_ctx *comp_ctx); |
| DUK_LOCAL_DECL void duk__parse_func_like_raw(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget); |
| DUK_LOCAL_DECL duk_int_t duk__parse_func_like_fnum(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget); |
| |
| /* |
| * Parser control values for tokens. The token table is ordered by the |
| * DUK_TOK_XXX defines. |
| * |
| * The binding powers are for lbp() use (i.e. for use in led() context). |
| * Binding powers are positive for typing convenience, and bits at the |
| * top should be reserved for flags. Binding power step must be higher |
| * than 1 so that binding power "lbp - 1" can be used for right associative |
| * operators. Currently a step of 2 is used (which frees one more bit for |
| * flags). |
| */ |
| |
| /* XXX: actually single step levels would work just fine, clean up */ |
| |
| /* binding power "levels" (see doc/compiler.rst) */ |
| #define DUK__BP_INVALID 0 /* always terminates led() */ |
| #define DUK__BP_EOF 2 |
| #define DUK__BP_CLOSING 4 /* token closes expression, e.g. ')', ']' */ |
| #define DUK__BP_FOR_EXPR DUK__BP_CLOSING /* bp to use when parsing a top level Expression */ |
| #define DUK__BP_COMMA 6 |
| #define DUK__BP_ASSIGNMENT 8 |
| #define DUK__BP_CONDITIONAL 10 |
| #define DUK__BP_LOR 12 |
| #define DUK__BP_LAND 14 |
| #define DUK__BP_BOR 16 |
| #define DUK__BP_BXOR 18 |
| #define DUK__BP_BAND 20 |
| #define DUK__BP_EQUALITY 22 |
| #define DUK__BP_RELATIONAL 24 |
| #define DUK__BP_SHIFT 26 |
| #define DUK__BP_ADDITIVE 28 |
| #define DUK__BP_MULTIPLICATIVE 30 |
| #define DUK__BP_POSTFIX 32 |
| #define DUK__BP_CALL 34 |
| #define DUK__BP_MEMBER 36 |
| |
| #define DUK__TOKEN_LBP_BP_MASK 0x1f |
| #define DUK__TOKEN_LBP_FLAG_NO_REGEXP (1 << 5) /* regexp literal must not follow this token */ |
| #define DUK__TOKEN_LBP_FLAG_TERMINATES (1 << 6) /* terminates expression; e.g. post-increment/-decrement */ |
| #define DUK__TOKEN_LBP_FLAG_UNUSED (1 << 7) /* spare */ |
| |
| #define DUK__TOKEN_LBP_GET_BP(x) ((duk_small_uint_t) (((x) & DUK__TOKEN_LBP_BP_MASK) * 2)) |
| |
| #define DUK__MK_LBP(bp) ((bp) >> 1) /* bp is assumed to be even */ |
| #define DUK__MK_LBP_FLAGS(bp,flags) (((bp) >> 1) | (flags)) |
| |
| DUK_LOCAL const duk_uint8_t duk__token_lbp[] = { |
| DUK__MK_LBP(DUK__BP_EOF), /* DUK_TOK_EOF */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_IDENTIFIER */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_BREAK */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_CASE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_CATCH */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_CONTINUE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_DEBUGGER */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_DEFAULT */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_DELETE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_DO */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_ELSE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_FINALLY */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_FOR */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_FUNCTION */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_IF */ |
| DUK__MK_LBP(DUK__BP_RELATIONAL), /* DUK_TOK_IN */ |
| DUK__MK_LBP(DUK__BP_RELATIONAL), /* DUK_TOK_INSTANCEOF */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_NEW */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_RETURN */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_SWITCH */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_THIS */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_THROW */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_TRY */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_TYPEOF */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_VAR */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_CONST */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_VOID */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_WHILE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_WITH */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_CLASS */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_ENUM */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_EXPORT */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_EXTENDS */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_IMPORT */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_SUPER */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_NULL */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_TRUE */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_FALSE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_GET */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_SET */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_IMPLEMENTS */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_INTERFACE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_LET */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_PACKAGE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_PRIVATE */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_PROTECTED */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_PUBLIC */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_STATIC */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_YIELD */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_LCURLY */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_RCURLY */ |
| DUK__MK_LBP(DUK__BP_MEMBER), /* DUK_TOK_LBRACKET */ |
| DUK__MK_LBP_FLAGS(DUK__BP_CLOSING, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_RBRACKET */ |
| DUK__MK_LBP(DUK__BP_CALL), /* DUK_TOK_LPAREN */ |
| DUK__MK_LBP_FLAGS(DUK__BP_CLOSING, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_RPAREN */ |
| DUK__MK_LBP(DUK__BP_MEMBER), /* DUK_TOK_PERIOD */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_SEMICOLON */ |
| DUK__MK_LBP(DUK__BP_COMMA), /* DUK_TOK_COMMA */ |
| DUK__MK_LBP(DUK__BP_RELATIONAL), /* DUK_TOK_LT */ |
| DUK__MK_LBP(DUK__BP_RELATIONAL), /* DUK_TOK_GT */ |
| DUK__MK_LBP(DUK__BP_RELATIONAL), /* DUK_TOK_LE */ |
| DUK__MK_LBP(DUK__BP_RELATIONAL), /* DUK_TOK_GE */ |
| DUK__MK_LBP(DUK__BP_EQUALITY), /* DUK_TOK_EQ */ |
| DUK__MK_LBP(DUK__BP_EQUALITY), /* DUK_TOK_NEQ */ |
| DUK__MK_LBP(DUK__BP_EQUALITY), /* DUK_TOK_SEQ */ |
| DUK__MK_LBP(DUK__BP_EQUALITY), /* DUK_TOK_SNEQ */ |
| DUK__MK_LBP(DUK__BP_ADDITIVE), /* DUK_TOK_ADD */ |
| DUK__MK_LBP(DUK__BP_ADDITIVE), /* DUK_TOK_SUB */ |
| DUK__MK_LBP(DUK__BP_MULTIPLICATIVE), /* DUK_TOK_MUL */ |
| DUK__MK_LBP(DUK__BP_MULTIPLICATIVE), /* DUK_TOK_DIV */ |
| DUK__MK_LBP(DUK__BP_MULTIPLICATIVE), /* DUK_TOK_MOD */ |
| DUK__MK_LBP(DUK__BP_POSTFIX), /* DUK_TOK_INCREMENT */ |
| DUK__MK_LBP(DUK__BP_POSTFIX), /* DUK_TOK_DECREMENT */ |
| DUK__MK_LBP(DUK__BP_SHIFT), /* DUK_TOK_ALSHIFT */ |
| DUK__MK_LBP(DUK__BP_SHIFT), /* DUK_TOK_ARSHIFT */ |
| DUK__MK_LBP(DUK__BP_SHIFT), /* DUK_TOK_RSHIFT */ |
| DUK__MK_LBP(DUK__BP_BAND), /* DUK_TOK_BAND */ |
| DUK__MK_LBP(DUK__BP_BOR), /* DUK_TOK_BOR */ |
| DUK__MK_LBP(DUK__BP_BXOR), /* DUK_TOK_BXOR */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_LNOT */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_BNOT */ |
| DUK__MK_LBP(DUK__BP_LAND), /* DUK_TOK_LAND */ |
| DUK__MK_LBP(DUK__BP_LOR), /* DUK_TOK_LOR */ |
| DUK__MK_LBP(DUK__BP_CONDITIONAL), /* DUK_TOK_QUESTION */ |
| DUK__MK_LBP(DUK__BP_INVALID), /* DUK_TOK_COLON */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_EQUALSIGN */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_ADD_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_SUB_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_MUL_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_DIV_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_MOD_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_ALSHIFT_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_ARSHIFT_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_RSHIFT_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_BAND_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_BOR_EQ */ |
| DUK__MK_LBP(DUK__BP_ASSIGNMENT), /* DUK_TOK_BXOR_EQ */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_NUMBER */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_STRING */ |
| DUK__MK_LBP_FLAGS(DUK__BP_INVALID, DUK__TOKEN_LBP_FLAG_NO_REGEXP), /* DUK_TOK_REGEXP */ |
| }; |
| |
| /* |
| * Misc helpers |
| */ |
| |
| DUK_LOCAL void duk__recursion_increase(duk_compiler_ctx *comp_ctx) { |
| DUK_ASSERT(comp_ctx != NULL); |
| DUK_ASSERT(comp_ctx->recursion_depth >= 0); |
| if (comp_ctx->recursion_depth >= comp_ctx->recursion_limit) { |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_COMPILER_RECURSION_LIMIT); |
| } |
| comp_ctx->recursion_depth++; |
| } |
| |
| DUK_LOCAL void duk__recursion_decrease(duk_compiler_ctx *comp_ctx) { |
| DUK_ASSERT(comp_ctx != NULL); |
| DUK_ASSERT(comp_ctx->recursion_depth > 0); |
| comp_ctx->recursion_depth--; |
| } |
| |
| DUK_LOCAL duk_bool_t duk__hstring_is_eval_or_arguments(duk_compiler_ctx *comp_ctx, duk_hstring *h) { |
| DUK_UNREF(comp_ctx); |
| DUK_ASSERT(h != NULL); |
| return DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(h); |
| } |
| |
| DUK_LOCAL duk_bool_t duk__hstring_is_eval_or_arguments_in_strict_mode(duk_compiler_ctx *comp_ctx, duk_hstring *h) { |
| DUK_ASSERT(h != NULL); |
| return (comp_ctx->curr_func.is_strict && |
| DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(h)); |
| } |
| |
| /* |
| * Parser duk__advance() token eating functions |
| */ |
| |
| /* XXX: valstack handling is awkward. Add a valstack helper which |
| * avoids dup():ing; valstack_copy(src, dst)? |
| */ |
| |
| DUK_LOCAL void duk__advance_helper(duk_compiler_ctx *comp_ctx, duk_small_int_t expect) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_bool_t regexp; |
| |
| DUK_ASSERT(comp_ctx->curr_token.t >= 0 && comp_ctx->curr_token.t <= DUK_TOK_MAXVAL); /* MAXVAL is inclusive */ |
| |
| /* |
| * Use current token to decide whether a RegExp can follow. |
| * |
| * We can use either 't' or 't_nores'; the latter would not |
| * recognize keywords. Some keywords can be followed by a |
| * RegExp (e.g. "return"), so using 't' is better. This is |
| * not trivial, see doc/compiler.rst. |
| */ |
| |
| regexp = 1; |
| if (duk__token_lbp[comp_ctx->curr_token.t] & DUK__TOKEN_LBP_FLAG_NO_REGEXP) { |
| regexp = 0; |
| } |
| if (comp_ctx->curr_func.reject_regexp_in_adv) { |
| comp_ctx->curr_func.reject_regexp_in_adv = 0; |
| regexp = 0; |
| } |
| |
| if (expect >= 0 && comp_ctx->curr_token.t != expect) { |
| DUK_D(DUK_DPRINT("parse error: expect=%ld, got=%ld", |
| (long) expect, (long) comp_ctx->curr_token.t)); |
| DUK_ERROR_SYNTAX(thr, DUK_STR_PARSE_ERROR); |
| } |
| |
| /* make current token the previous; need to fiddle with valstack "backing store" */ |
| DUK_MEMCPY(&comp_ctx->prev_token, &comp_ctx->curr_token, sizeof(duk_token)); |
| duk_copy(ctx, comp_ctx->tok11_idx, comp_ctx->tok21_idx); |
| duk_copy(ctx, comp_ctx->tok12_idx, comp_ctx->tok22_idx); |
| |
| /* parse new token */ |
| duk_lexer_parse_js_input_element(&comp_ctx->lex, |
| &comp_ctx->curr_token, |
| comp_ctx->curr_func.is_strict, |
| regexp); |
| |
| DUK_DDD(DUK_DDDPRINT("advance: curr: tok=%ld/%ld,%ld,term=%ld,%!T,%!T " |
| "prev: tok=%ld/%ld,%ld,term=%ld,%!T,%!T", |
| (long) comp_ctx->curr_token.t, |
| (long) comp_ctx->curr_token.t_nores, |
| (long) comp_ctx->curr_token.start_line, |
| (long) comp_ctx->curr_token.lineterm, |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->tok11_idx), |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->tok12_idx), |
| (long) comp_ctx->prev_token.t, |
| (long) comp_ctx->prev_token.t_nores, |
| (long) comp_ctx->prev_token.start_line, |
| (long) comp_ctx->prev_token.lineterm, |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->tok21_idx), |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->tok22_idx))); |
| } |
| |
| /* advance, expecting current token to be a specific token; parse next token in regexp context */ |
| DUK_LOCAL void duk__advance_expect(duk_compiler_ctx *comp_ctx, duk_small_int_t expect) { |
| duk__advance_helper(comp_ctx, expect); |
| } |
| |
| /* advance, whatever the current token is; parse next token in regexp context */ |
| DUK_LOCAL void duk__advance(duk_compiler_ctx *comp_ctx) { |
| duk__advance_helper(comp_ctx, -1); |
| } |
| |
| /* |
| * Helpers for duk_compiler_func. |
| */ |
| |
| /* init function state: inits valstack allocations */ |
| DUK_LOCAL void duk__init_func_valstack_slots(duk_compiler_ctx *comp_ctx) { |
| duk_compiler_func *func = &comp_ctx->curr_func; |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_idx_t entry_top; |
| |
| entry_top = duk_get_top(ctx); |
| |
| DUK_MEMZERO(func, sizeof(*func)); /* intentional overlap with earlier memzero */ |
| #ifdef DUK_USE_EXPLICIT_NULL_INIT |
| func->h_name = NULL; |
| func->h_consts = NULL; |
| func->h_funcs = NULL; |
| func->h_decls = NULL; |
| func->h_labelnames = NULL; |
| func->h_labelinfos = NULL; |
| func->h_argnames = NULL; |
| func->h_varmap = NULL; |
| #endif |
| |
| duk_require_stack(ctx, DUK__FUNCTION_INIT_REQUIRE_SLOTS); |
| |
| DUK_BW_INIT_PUSHBUF(thr, &func->bw_code, DUK__BC_INITIAL_INSTS * sizeof(duk_compiler_instr)); |
| /* code_idx = entry_top + 0 */ |
| |
| duk_push_array(ctx); |
| func->consts_idx = entry_top + 1; |
| func->h_consts = DUK_GET_HOBJECT_POSIDX(ctx, entry_top + 1); |
| DUK_ASSERT(func->h_consts != NULL); |
| |
| duk_push_array(ctx); |
| func->funcs_idx = entry_top + 2; |
| func->h_funcs = DUK_GET_HOBJECT_POSIDX(ctx, entry_top + 2); |
| DUK_ASSERT(func->h_funcs != NULL); |
| DUK_ASSERT(func->fnum_next == 0); |
| |
| duk_push_array(ctx); |
| func->decls_idx = entry_top + 3; |
| func->h_decls = DUK_GET_HOBJECT_POSIDX(ctx, entry_top + 3); |
| DUK_ASSERT(func->h_decls != NULL); |
| |
| duk_push_array(ctx); |
| func->labelnames_idx = entry_top + 4; |
| func->h_labelnames = DUK_GET_HOBJECT_POSIDX(ctx, entry_top + 4); |
| DUK_ASSERT(func->h_labelnames != NULL); |
| |
| duk_push_dynamic_buffer(ctx, 0); |
| func->labelinfos_idx = entry_top + 5; |
| func->h_labelinfos = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 5); |
| DUK_ASSERT(func->h_labelinfos != NULL); |
| DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_labelinfos) && !DUK_HBUFFER_HAS_EXTERNAL(func->h_labelinfos)); |
| |
| duk_push_array(ctx); |
| func->argnames_idx = entry_top + 6; |
| func->h_argnames = DUK_GET_HOBJECT_POSIDX(ctx, entry_top + 6); |
| DUK_ASSERT(func->h_argnames != NULL); |
| |
| duk_push_object_internal(ctx); |
| func->varmap_idx = entry_top + 7; |
| func->h_varmap = DUK_GET_HOBJECT_POSIDX(ctx, entry_top + 7); |
| DUK_ASSERT(func->h_varmap != NULL); |
| } |
| |
| /* reset function state (prepare for pass 2) */ |
| DUK_LOCAL void duk__reset_func_for_pass2(duk_compiler_ctx *comp_ctx) { |
| duk_compiler_func *func = &comp_ctx->curr_func; |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| |
| /* reset bytecode buffer but keep current size; pass 2 will |
| * require same amount or more. |
| */ |
| DUK_BW_RESET_SIZE(thr, &func->bw_code); |
| |
| duk_hobject_set_length_zero(thr, func->h_consts); |
| /* keep func->h_funcs; inner functions are not reparsed to avoid O(depth^2) parsing */ |
| func->fnum_next = 0; |
| /* duk_hobject_set_length_zero(thr, func->h_funcs); */ |
| duk_hobject_set_length_zero(thr, func->h_labelnames); |
| duk_hbuffer_reset(thr, func->h_labelinfos); |
| /* keep func->h_argnames; it is fixed for all passes */ |
| |
| /* truncated in case pass 3 needed */ |
| duk_push_object_internal(ctx); |
| duk_replace(ctx, func->varmap_idx); |
| func->h_varmap = DUK_GET_HOBJECT_POSIDX(ctx, func->varmap_idx); |
| DUK_ASSERT(func->h_varmap != NULL); |
| } |
| |
| /* cleanup varmap from any null entries, compact it, etc; returns number |
| * of final entries after cleanup. |
| */ |
| DUK_LOCAL duk_int_t duk__cleanup_varmap(duk_compiler_ctx *comp_ctx) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_hobject *h_varmap; |
| duk_hstring *h_key; |
| duk_tval *tv; |
| duk_uint32_t i, e_next; |
| duk_int_t ret; |
| |
| /* [ ... varmap ] */ |
| |
| h_varmap = DUK_GET_HOBJECT_NEGIDX(ctx, -1); |
| DUK_ASSERT(h_varmap != NULL); |
| |
| ret = 0; |
| e_next = DUK_HOBJECT_GET_ENEXT(h_varmap); |
| for (i = 0; i < e_next; i++) { |
| h_key = DUK_HOBJECT_E_GET_KEY(thr->heap, h_varmap, i); |
| if (!h_key) { |
| continue; |
| } |
| |
| DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, h_varmap, i)); |
| |
| /* The entries can either be register numbers or 'null' values. |
| * Thus, no need to DECREF them and get side effects. DECREF'ing |
| * the keys (strings) can cause memory to be freed but no side |
| * effects as strings don't have finalizers. This is why we can |
| * rely on the object properties not changing from underneath us. |
| */ |
| |
| tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, h_varmap, i); |
| if (!DUK_TVAL_IS_NUMBER(tv)) { |
| DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv)); |
| DUK_HOBJECT_E_SET_KEY(thr->heap, h_varmap, i, NULL); |
| DUK_HSTRING_DECREF(thr, h_key); |
| /* when key is NULL, value is garbage so no need to set */ |
| } else { |
| ret++; |
| } |
| } |
| |
| duk_compact(ctx, -1); |
| |
| return ret; |
| } |
| |
| /* convert duk_compiler_func into a function template, leaving the result |
| * on top of stack. |
| */ |
| /* XXX: awkward and bloated asm -- use faster internal accesses */ |
| DUK_LOCAL void duk__convert_to_func_template(duk_compiler_ctx *comp_ctx, duk_bool_t force_no_namebind) { |
| duk_compiler_func *func = &comp_ctx->curr_func; |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_hcompiledfunction *h_res; |
| duk_hbuffer_fixed *h_data; |
| duk_size_t consts_count; |
| duk_size_t funcs_count; |
| duk_size_t code_count; |
| duk_size_t code_size; |
| duk_size_t data_size; |
| duk_size_t i; |
| duk_tval *p_const; |
| duk_hobject **p_func; |
| duk_instr_t *p_instr; |
| duk_compiler_instr *q_instr; |
| duk_tval *tv; |
| |
| DUK_DDD(DUK_DDDPRINT("converting duk_compiler_func to function/template")); |
| |
| /* |
| * Push result object and init its flags |
| */ |
| |
| /* Valstack should suffice here, required on function valstack init */ |
| |
| (void) duk_push_compiledfunction(ctx); |
| h_res = (duk_hcompiledfunction *) DUK_GET_HOBJECT_NEGIDX(ctx, -1); /* XXX: specific getter */ |
| DUK_ASSERT(h_res != NULL); |
| |
| if (func->is_function) { |
| DUK_DDD(DUK_DDDPRINT("function -> set NEWENV")); |
| DUK_HOBJECT_SET_NEWENV((duk_hobject *) h_res); |
| |
| if (!func->is_arguments_shadowed) { |
| /* arguments object would be accessible; note that shadowing |
| * bindings are arguments or function declarations, neither |
| * of which are deletable, so this is safe. |
| */ |
| |
| if (func->id_access_arguments || func->may_direct_eval) { |
| DUK_DDD(DUK_DDDPRINT("function may access 'arguments' object directly or " |
| "indirectly -> set CREATEARGS")); |
| DUK_HOBJECT_SET_CREATEARGS((duk_hobject *) h_res); |
| } |
| } |
| } else if (func->is_eval && func->is_strict) { |
| DUK_DDD(DUK_DDDPRINT("strict eval code -> set NEWENV")); |
| DUK_HOBJECT_SET_NEWENV((duk_hobject *) h_res); |
| } else { |
| /* non-strict eval: env is caller's env or global env (direct vs. indirect call) |
| * global code: env is is global env |
| */ |
| DUK_DDD(DUK_DDDPRINT("non-strict eval code or global code -> no NEWENV")); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV((duk_hobject *) h_res)); |
| } |
| |
| if (func->is_function && !func->is_decl && func->h_name != NULL && !force_no_namebind) { |
| /* Object literal set/get functions have a name (property |
| * name) but must not have a lexical name binding, see |
| * test-bug-getset-func-name.js. |
| */ |
| DUK_DDD(DUK_DDDPRINT("function expression with a name -> set NAMEBINDING")); |
| DUK_HOBJECT_SET_NAMEBINDING((duk_hobject *) h_res); |
| } |
| |
| if (func->is_strict) { |
| DUK_DDD(DUK_DDDPRINT("function is strict -> set STRICT")); |
| DUK_HOBJECT_SET_STRICT((duk_hobject *) h_res); |
| } |
| |
| if (func->is_notail) { |
| DUK_DDD(DUK_DDDPRINT("function is notail -> set NOTAIL")); |
| DUK_HOBJECT_SET_NOTAIL((duk_hobject *) h_res); |
| } |
| |
| /* |
| * Build function fixed size 'data' buffer, which contains bytecode, |
| * constants, and inner function references. |
| * |
| * During the building phase 'data' is reachable but incomplete. |
| * Only incref's occur during building (no refzero or GC happens), |
| * so the building process is atomic. |
| */ |
| |
| consts_count = duk_hobject_get_length(thr, func->h_consts); |
| funcs_count = duk_hobject_get_length(thr, func->h_funcs) / 3; |
| code_count = DUK_BW_GET_SIZE(thr, &func->bw_code) / sizeof(duk_compiler_instr); |
| code_size = code_count * sizeof(duk_instr_t); |
| |
| data_size = consts_count * sizeof(duk_tval) + |
| funcs_count * sizeof(duk_hobject *) + |
| code_size; |
| |
| DUK_DDD(DUK_DDDPRINT("consts_count=%ld, funcs_count=%ld, code_size=%ld -> " |
| "data_size=%ld*%ld + %ld*%ld + %ld = %ld", |
| (long) consts_count, (long) funcs_count, (long) code_size, |
| (long) consts_count, (long) sizeof(duk_tval), |
| (long) funcs_count, (long) sizeof(duk_hobject *), |
| (long) code_size, (long) data_size)); |
| |
| duk_push_fixed_buffer(ctx, data_size); |
| h_data = (duk_hbuffer_fixed *) duk_get_hbuffer(ctx, -1); |
| DUK_ASSERT(h_data != NULL); |
| |
| DUK_HCOMPILEDFUNCTION_SET_DATA(thr->heap, h_res, (duk_hbuffer *) h_data); |
| DUK_HEAPHDR_INCREF(thr, h_data); |
| |
| p_const = (duk_tval *) (void *) DUK_HBUFFER_FIXED_GET_DATA_PTR(thr->heap, h_data); |
| for (i = 0; i < consts_count; i++) { |
| DUK_ASSERT(i <= DUK_UARRIDX_MAX); /* const limits */ |
| tv = duk_hobject_find_existing_array_entry_tval_ptr(thr->heap, func->h_consts, (duk_uarridx_t) i); |
| DUK_ASSERT(tv != NULL); |
| DUK_TVAL_SET_TVAL(p_const, tv); |
| p_const++; |
| DUK_TVAL_INCREF(thr, tv); /* may be a string constant */ |
| |
| DUK_DDD(DUK_DDDPRINT("constant: %!T", (duk_tval *) tv)); |
| } |
| |
| p_func = (duk_hobject **) p_const; |
| DUK_HCOMPILEDFUNCTION_SET_FUNCS(thr->heap, h_res, p_func); |
| for (i = 0; i < funcs_count; i++) { |
| duk_hobject *h; |
| DUK_ASSERT(i * 3 <= DUK_UARRIDX_MAX); /* func limits */ |
| tv = duk_hobject_find_existing_array_entry_tval_ptr(thr->heap, func->h_funcs, (duk_uarridx_t) (i * 3)); |
| DUK_ASSERT(tv != NULL); |
| DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv)); |
| h = DUK_TVAL_GET_OBJECT(tv); |
| DUK_ASSERT(h != NULL); |
| DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(h)); |
| *p_func++ = h; |
| DUK_HOBJECT_INCREF(thr, h); |
| |
| DUK_DDD(DUK_DDDPRINT("inner function: %p -> %!iO", |
| (void *) h, (duk_heaphdr *) h)); |
| } |
| |
| p_instr = (duk_instr_t *) p_func; |
| DUK_HCOMPILEDFUNCTION_SET_BYTECODE(thr->heap, h_res, p_instr); |
| |
| /* copy bytecode instructions one at a time */ |
| q_instr = (duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(thr, &func->bw_code); |
| for (i = 0; i < code_count; i++) { |
| p_instr[i] = q_instr[i].ins; |
| } |
| /* Note: 'q_instr' is still used below */ |
| |
| DUK_ASSERT((duk_uint8_t *) (p_instr + code_count) == DUK_HBUFFER_FIXED_GET_DATA_PTR(thr->heap, h_data) + data_size); |
| |
| duk_pop(ctx); /* 'data' (and everything in it) is reachable through h_res now */ |
| |
| /* |
| * Init object properties |
| * |
| * Properties should be added in decreasing order of access frequency. |
| * (Not very critical for function templates.) |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("init function properties")); |
| |
| /* [ ... res ] */ |
| |
| /* _Varmap: omitted if function is guaranteed not to do slow path identifier |
| * accesses or if it would turn out to be empty of actual register mappings |
| * after a cleanup. When debugging is enabled, we always need the varmap to |
| * be able to lookup variables at any point. |
| */ |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| if (1) { |
| #else |
| if (func->id_access_slow || /* directly uses slow accesses */ |
| func->may_direct_eval || /* may indirectly slow access through a direct eval */ |
| funcs_count > 0) { /* has inner functions which may slow access (XXX: this can be optimized by looking at the inner functions) */ |
| #endif |
| duk_int_t num_used; |
| duk_dup(ctx, func->varmap_idx); |
| num_used = duk__cleanup_varmap(comp_ctx); |
| DUK_DDD(DUK_DDDPRINT("cleaned up varmap: %!T (num_used=%ld)", |
| (duk_tval *) duk_get_tval(ctx, -1), (long) num_used)); |
| |
| if (num_used > 0) { |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("varmap is empty after cleanup -> no need to add")); |
| duk_pop(ctx); |
| } |
| } |
| |
| /* _Formals: omitted if function is guaranteed not to need a (non-strict) arguments object */ |
| if (1) { |
| /* XXX: Add a proper condition. If formals list is omitted, recheck |
| * handling for 'length' in duk_js_push_closure(); it currently relies |
| * on _Formals being set. Removal may need to be conditional to debugging |
| * being enabled/disabled too. |
| */ |
| duk_dup(ctx, func->argnames_idx); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE); |
| } |
| |
| /* name */ |
| if (func->h_name) { |
| duk_push_hstring(ctx, func->h_name); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); |
| } |
| |
| /* _Source */ |
| #if defined(DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY) |
| if (0) { |
| /* XXX: Currently function source code is not stored, as it is not |
| * required by the standard. Source code should not be stored by |
| * default (user should enable it explicitly), and the source should |
| * probably be compressed with a trivial text compressor; average |
| * compression of 20-30% is quite easy to achieve even with a trivial |
| * compressor (RLE + backwards lookup). |
| * |
| * Debugging needs source code to be useful: sometimes input code is |
| * not found in files as it may be generated and then eval()'d, given |
| * by dynamic C code, etc. |
| * |
| * Other issues: |
| * |
| * - Need tokenizer indices for start and end to substring |
| * - Always normalize function declaration part? |
| * - If we keep _Formals, only need to store body |
| */ |
| |
| /* |
| * For global or eval code this is straightforward. For functions |
| * created with the Function constructor we only get the source for |
| * the body and must manufacture the "function ..." part. |
| * |
| * For instance, for constructed functions (v8): |
| * |
| * > a = new Function("foo", "bar", "print(foo)"); |
| * [Function] |
| * > a.toString() |
| * 'function anonymous(foo,bar) {\nprint(foo)\n}' |
| * |
| * Similarly for e.g. getters (v8): |
| * |
| * > x = { get a(foo,bar) { print(foo); } } |
| * { a: [Getter] } |
| * > Object.getOwnPropertyDescriptor(x, 'a').get.toString() |
| * 'function a(foo,bar) { print(foo); }' |
| */ |
| |
| #if 0 |
| duk_push_string(ctx, "XXX"); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_SOURCE, DUK_PROPDESC_FLAGS_NONE); |
| #endif |
| } |
| #endif /* DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY */ |
| |
| /* _Pc2line */ |
| #if defined(DUK_USE_PC2LINE) |
| if (1) { |
| /* |
| * Size-optimized pc->line mapping. |
| */ |
| |
| DUK_ASSERT(code_count <= DUK_COMPILER_MAX_BYTECODE_LENGTH); |
| duk_hobject_pc2line_pack(thr, q_instr, (duk_uint_fast32_t) code_count); /* -> pushes fixed buffer */ |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_NONE); |
| |
| /* XXX: if assertions enabled, walk through all valid PCs |
| * and check line mapping. |
| */ |
| } |
| #endif /* DUK_USE_PC2LINE */ |
| |
| /* fileName */ |
| if (comp_ctx->h_filename) { |
| /* |
| * Source filename (or equivalent), for identifying thrown errors. |
| */ |
| |
| duk_push_hstring(ctx, comp_ctx->h_filename); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_NONE); |
| } |
| |
| /* |
| * Init remaining result fields |
| * |
| * 'nregs' controls how large a register frame is allocated. |
| * |
| * 'nargs' controls how many formal arguments are written to registers: |
| * r0, ... r(nargs-1). The remaining registers are initialized to |
| * undefined. |
| */ |
| |
| DUK_ASSERT(func->temp_max >= 0); |
| h_res->nregs = (duk_uint16_t) func->temp_max; |
| h_res->nargs = (duk_uint16_t) duk_hobject_get_length(thr, func->h_argnames); |
| DUK_ASSERT(h_res->nregs >= h_res->nargs); /* pass2 allocation handles this */ |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| h_res->start_line = (duk_uint32_t) func->min_line; |
| h_res->end_line = (duk_uint32_t) func->max_line; |
| #endif |
| |
| DUK_DD(DUK_DDPRINT("converted function: %!ixT", |
| (duk_tval *) duk_get_tval(ctx, -1))); |
| |
| /* |
| * Compact the function template. |
| */ |
| |
| duk_compact(ctx, -1); |
| |
| /* |
| * Debug dumping |
| */ |
| |
| #ifdef DUK_USE_DDDPRINT |
| { |
| duk_hcompiledfunction *h; |
| duk_instr_t *p, *p_start, *p_end; |
| |
| h = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1); |
| p_start = (duk_instr_t *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(thr->heap, h); |
| p_end = (duk_instr_t *) DUK_HCOMPILEDFUNCTION_GET_CODE_END(thr->heap, h); |
| |
| p = p_start; |
| while (p < p_end) { |
| DUK_DDD(DUK_DDDPRINT("BC %04ld: %!I ; 0x%08lx op=%ld (%!C) a=%ld b=%ld c=%ld", |
| (long) (p - p_start), |
| (duk_instr_t) (*p), |
| (unsigned long) (*p), |
| (long) DUK_DEC_OP(*p), |
| (long) DUK_DEC_OP(*p), |
| (long) DUK_DEC_A(*p), |
| (long) DUK_DEC_B(*p), |
| (long) DUK_DEC_C(*p))); |
| p++; |
| } |
| } |
| #endif |
| } |
| |
| /* |
| * Code emission helpers |
| * |
| * Some emission helpers understand the range of target and source reg/const |
| * values and automatically emit shuffling code if necessary. This is the |
| * case when the slot in question (A, B, C) is used in the standard way and |
| * for opcodes the emission helpers explicitly understand (like DUK_OP_CALL). |
| * |
| * The standard way is that: |
| * - slot A is a target register |
| * - slot B is a source register/constant |
| * - slot C is a source register/constant |
| * |
| * If a slot is used in a non-standard way the caller must indicate this |
| * somehow. If a slot is used as a target instead of a source (or vice |
| * versa), this can be indicated with a flag to trigger proper shuffling |
| * (e.g. DUK__EMIT_FLAG_B_IS_TARGET). If the value in the slot is not |
| * register/const related at all, the caller must ensure that the raw value |
| * fits into the corresponding slot so as to not trigger shuffling. The |
| * caller must set a "no shuffle" flag to ensure compilation fails if |
| * shuffling were to be triggered because of an internal error. |
| * |
| * For slots B and C the raw slot size is 9 bits but one bit is reserved for |
| * the reg/const indicator. To use the full 9-bit range for a raw value, |
| * shuffling must be disabled with the DUK__EMIT_FLAG_NO_SHUFFLE_{B,C} flag. |
| * Shuffling is only done for A, B, and C slots, not the larger BC or ABC slots. |
| * |
| * There is call handling specific understanding in the A-B-C emitter to |
| * convert call setup and call instructions into indirect ones if necessary. |
| */ |
| |
| /* Code emission flags, passed in the 'opcode' field. Opcode + flags |
| * fit into 16 bits for now, so use duk_small_uint.t. |
| */ |
| #define DUK__EMIT_FLAG_NO_SHUFFLE_A (1 << 8) |
| #define DUK__EMIT_FLAG_NO_SHUFFLE_B (1 << 9) |
| #define DUK__EMIT_FLAG_NO_SHUFFLE_C (1 << 10) |
| #define DUK__EMIT_FLAG_A_IS_SOURCE (1 << 11) /* slot A is a source (default: target) */ |
| #define DUK__EMIT_FLAG_B_IS_TARGET (1 << 12) /* slot B is a target (default: source) */ |
| #define DUK__EMIT_FLAG_C_IS_TARGET (1 << 13) /* slot C is a target (default: source) */ |
| #define DUK__EMIT_FLAG_B_IS_TARGETSOURCE (1 << 14) /* slot B is both a target and a source (used by extraops like DUK_EXTRAOP_INSTOF */ |
| #define DUK__EMIT_FLAG_RESERVE_JUMPSLOT (1 << 15) /* reserve a jumpslot after instr before target spilling, used for NEXTENUM */ |
| |
| /* XXX: clarify on when and where DUK__CONST_MARKER is allowed */ |
| /* XXX: opcode specific assertions on when consts are allowed */ |
| |
| /* XXX: macro smaller than call? */ |
| DUK_LOCAL duk_int_t duk__get_current_pc(duk_compiler_ctx *comp_ctx) { |
| duk_compiler_func *func; |
| func = &comp_ctx->curr_func; |
| return (duk_int_t) (DUK_BW_GET_SIZE(comp_ctx->thr, &func->bw_code) / sizeof(duk_compiler_instr)); |
| } |
| |
| DUK_LOCAL duk_compiler_instr *duk__get_instr_ptr(duk_compiler_ctx *comp_ctx, duk_int_t pc) { |
| DUK_ASSERT(pc >= 0); |
| DUK_ASSERT((duk_size_t) pc < (duk_size_t) (DUK_BW_GET_SIZE(comp_ctx->thr, &comp_ctx->curr_func.bw_code) / sizeof(duk_compiler_instr))); |
| return ((duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code)) + pc; |
| } |
| |
| /* emit instruction; could return PC but that's not needed in the majority |
| * of cases. |
| */ |
| DUK_LOCAL void duk__emit(duk_compiler_ctx *comp_ctx, duk_instr_t ins) { |
| #if defined(DUK_USE_PC2LINE) |
| duk_int_t line; |
| #endif |
| duk_compiler_instr *instr; |
| |
| DUK_DDD(DUK_DDDPRINT("duk__emit: 0x%08lx curr_token.start_line=%ld prev_token.start_line=%ld pc=%ld --> %!I", |
| (unsigned long) ins, |
| (long) comp_ctx->curr_token.start_line, |
| (long) comp_ctx->prev_token.start_line, |
| (long) duk__get_current_pc(comp_ctx), |
| (duk_instr_t) ins)); |
| |
| instr = (duk_compiler_instr *) (void *) DUK_BW_ENSURE_GETPTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code, sizeof(duk_compiler_instr)); |
| DUK_BW_ADD_PTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code, sizeof(duk_compiler_instr)); |
| |
| #if defined(DUK_USE_PC2LINE) |
| /* The line number tracking is a bit inconsistent right now, which |
| * affects debugger accuracy. Mostly call sites emit opcodes when |
| * they have parsed a token (say a terminating semicolon) and called |
| * duk__advance(). In this case the line number of the previous |
| * token is the most accurate one (except in prologue where |
| * prev_token.start_line is 0). This is probably not 100% correct |
| * right now. |
| */ |
| /* approximation, close enough */ |
| line = comp_ctx->prev_token.start_line; |
| if (line == 0) { |
| line = comp_ctx->curr_token.start_line; |
| } |
| #endif |
| |
| instr->ins = ins; |
| #if defined(DUK_USE_PC2LINE) |
| instr->line = line; |
| #endif |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| if (line < comp_ctx->curr_func.min_line) { |
| comp_ctx->curr_func.min_line = line; |
| } |
| if (line > comp_ctx->curr_func.max_line) { |
| comp_ctx->curr_func.max_line = line; |
| } |
| #endif |
| |
| /* Limit checks for bytecode byte size and line number. */ |
| if (DUK_UNLIKELY(DUK_BW_GET_SIZE(comp_ctx->thr, &comp_ctx->curr_func.bw_code) > DUK_USE_ESBC_MAX_BYTES)) { |
| goto fail_bc_limit; |
| } |
| #if defined(DUK_USE_PC2LINE) && defined(DUK_USE_ESBC_LIMITS) |
| #if defined(DUK_USE_BUFLEN16) |
| /* Buffer length is bounded to 0xffff automatically, avoid compile warning. */ |
| if (DUK_UNLIKELY(line > DUK_USE_ESBC_MAX_LINENUMBER)) { |
| goto fail_bc_limit; |
| } |
| #else |
| if (DUK_UNLIKELY(line > DUK_USE_ESBC_MAX_LINENUMBER)) { |
| goto fail_bc_limit; |
| } |
| #endif |
| #endif |
| |
| return; |
| |
| fail_bc_limit: |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_BYTECODE_LIMIT); |
| } |
| |
| /* Update function min/max line from current token. Needed to improve |
| * function line range information for debugging, so that e.g. opening |
| * curly brace is covered by line range even when no opcodes are emitted |
| * for the line containing the brace. |
| */ |
| DUK_LOCAL void duk__update_lineinfo_currtoken(duk_compiler_ctx *comp_ctx) { |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| duk_int_t line; |
| |
| line = comp_ctx->curr_token.start_line; |
| if (line == 0) { |
| return; |
| } |
| if (line < comp_ctx->curr_func.min_line) { |
| comp_ctx->curr_func.min_line = line; |
| } |
| if (line > comp_ctx->curr_func.max_line) { |
| comp_ctx->curr_func.max_line = line; |
| } |
| #else |
| DUK_UNREF(comp_ctx); |
| #endif |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL void duk__emit_op_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t op) { |
| duk__emit(comp_ctx, DUK_ENC_OP_ABC(op, 0)); |
| } |
| #endif |
| |
| /* Important main primitive. */ |
| DUK_LOCAL void duk__emit_a_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b, duk_regconst_t c) { |
| duk_instr_t ins = 0; |
| duk_int_t a_out = -1; |
| duk_int_t b_out = -1; |
| duk_int_t c_out = -1; |
| duk_int_t tmp; |
| |
| DUK_DDD(DUK_DDDPRINT("emit: op_flags=%04lx, a=%ld, b=%ld, c=%ld", |
| (unsigned long) op_flags, (long) a, (long) b, (long) c)); |
| |
| /* We could rely on max temp/const checks: if they don't exceed BC |
| * limit, nothing here can either (just asserts would be enough). |
| * Currently we check for the limits, which provides additional |
| * protection against creating invalid bytecode due to compiler |
| * bugs. |
| */ |
| |
| DUK_ASSERT_DISABLE((op_flags & 0xff) >= DUK_BC_OP_MIN); /* unsigned */ |
| DUK_ASSERT((op_flags & 0xff) <= DUK_BC_OP_MAX); |
| |
| /* Input shuffling happens before the actual operation, while output |
| * shuffling happens afterwards. Output shuffling decisions are still |
| * made at the same time to reduce branch clutter; output shuffle decisions |
| * are recorded into X_out variables. |
| */ |
| |
| /* Slot A */ |
| |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| if (a <= DUK_BC_A_MAX && (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_A)) { |
| #else |
| if (a <= DUK_BC_A_MAX) { |
| #endif |
| ; |
| } else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_A) { |
| DUK_D(DUK_DPRINT("out of regs: 'a' (reg) needs shuffling but shuffle prohibited, a: %ld", (long) a)); |
| goto error_outofregs; |
| } else if (a <= DUK_BC_BC_MAX) { |
| comp_ctx->curr_func.needs_shuffle = 1; |
| tmp = comp_ctx->curr_func.shuffle1; |
| if (op_flags & DUK__EMIT_FLAG_A_IS_SOURCE) { |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, a)); |
| } else { |
| duk_small_int_t op = op_flags & 0xff; |
| if (op == DUK_OP_CSVAR || op == DUK_OP_CSREG || op == DUK_OP_CSPROP) { |
| /* Special handling for call setup instructions. The target |
| * is expressed indirectly, but there is no output shuffling. |
| */ |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_A_IS_SOURCE) == 0); |
| duk__emit_load_int32_noshuffle(comp_ctx, tmp, a); |
| DUK_ASSERT(DUK_OP_CSVARI == DUK_OP_CSVAR + 1); |
| DUK_ASSERT(DUK_OP_CSREGI == DUK_OP_CSREG + 1); |
| DUK_ASSERT(DUK_OP_CSPROPI == DUK_OP_CSPROP + 1); |
| op_flags++; /* indirect opcode follows direct */ |
| } else { |
| /* Output shuffle needed after main operation */ |
| a_out = a; |
| } |
| } |
| a = tmp; |
| } else { |
| DUK_D(DUK_DPRINT("out of regs: 'a' (reg) needs shuffling but does not fit into BC, a: %ld", (long) a)); |
| goto error_outofregs; |
| } |
| |
| /* Slot B */ |
| |
| if (b & DUK__CONST_MARKER) { |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_B) == 0); |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_B_IS_TARGET) == 0); |
| DUK_ASSERT((op_flags & 0xff) != DUK_OP_CALL); |
| DUK_ASSERT((op_flags & 0xff) != DUK_OP_NEW); |
| b = b & ~DUK__CONST_MARKER; |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| if (0) { |
| #else |
| if (b <= 0xff) { |
| #endif |
| ins |= DUK_ENC_OP_A_B_C(0, 0, 0x100, 0); /* const flag for B */ |
| } else if (b <= DUK_BC_BC_MAX) { |
| comp_ctx->curr_func.needs_shuffle = 1; |
| tmp = comp_ctx->curr_func.shuffle2; |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDCONST, tmp, b)); |
| b = tmp; |
| } else { |
| DUK_D(DUK_DPRINT("out of regs: 'b' (const) needs shuffling but does not fit into BC, b: %ld", (long) b)); |
| goto error_outofregs; |
| } |
| } else { |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| if (b <= 0xff && (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_B)) { |
| #else |
| if (b <= 0xff) { |
| #endif |
| ; |
| } else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_B) { |
| if (b > DUK_BC_B_MAX) { |
| /* Note: 0xff != DUK_BC_B_MAX */ |
| DUK_D(DUK_DPRINT("out of regs: 'b' (reg) needs shuffling but shuffle prohibited, b: %ld", (long) b)); |
| goto error_outofregs; |
| } |
| } else if (b <= DUK_BC_BC_MAX) { |
| comp_ctx->curr_func.needs_shuffle = 1; |
| tmp = comp_ctx->curr_func.shuffle2; |
| if (op_flags & DUK__EMIT_FLAG_B_IS_TARGET) { |
| /* Output shuffle needed after main operation */ |
| b_out = b; |
| } |
| if (!(op_flags & DUK__EMIT_FLAG_B_IS_TARGET) || (op_flags & DUK__EMIT_FLAG_B_IS_TARGETSOURCE)) { |
| duk_small_int_t op = op_flags & 0xff; |
| if (op == DUK_OP_CALL || op == DUK_OP_NEW || |
| op == DUK_OP_MPUTOBJ || op == DUK_OP_MPUTARR) { |
| /* Special handling for CALL/NEW/MPUTOBJ/MPUTARR shuffling. |
| * For each, slot B identifies the first register of a range |
| * of registers, so normal shuffling won't work. Instead, |
| * an indirect version of the opcode is used. |
| */ |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_B_IS_TARGET) == 0); |
| duk__emit_load_int32_noshuffle(comp_ctx, tmp, b); |
| DUK_ASSERT(DUK_OP_CALLI == DUK_OP_CALL + 1); |
| DUK_ASSERT(DUK_OP_NEWI == DUK_OP_NEW + 1); |
| DUK_ASSERT(DUK_OP_MPUTOBJI == DUK_OP_MPUTOBJ + 1); |
| DUK_ASSERT(DUK_OP_MPUTARRI == DUK_OP_MPUTARR + 1); |
| op_flags++; /* indirect opcode follows direct */ |
| } else { |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, b)); |
| } |
| } |
| b = tmp; |
| } else { |
| DUK_D(DUK_DPRINT("out of regs: 'b' (reg) needs shuffling but does not fit into BC, b: %ld", (long) b)); |
| goto error_outofregs; |
| } |
| } |
| |
| /* Slot C */ |
| |
| if (c & DUK__CONST_MARKER) { |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_C) == 0); |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_C_IS_TARGET) == 0); |
| c = c & ~DUK__CONST_MARKER; |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| if (0) { |
| #else |
| if (c <= 0xff) { |
| #endif |
| ins |= DUK_ENC_OP_A_B_C(0, 0, 0, 0x100); /* const flag for C */ |
| } else if (c <= DUK_BC_BC_MAX) { |
| comp_ctx->curr_func.needs_shuffle = 1; |
| tmp = comp_ctx->curr_func.shuffle3; |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDCONST, tmp, c)); |
| c = tmp; |
| } else { |
| DUK_D(DUK_DPRINT("out of regs: 'c' (const) needs shuffling but does not fit into BC, c: %ld", (long) c)); |
| goto error_outofregs; |
| } |
| } else { |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| if (c <= 0xff && (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_C)) { |
| #else |
| if (c <= 0xff) { |
| #endif |
| ; |
| } else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_C) { |
| if (c > DUK_BC_C_MAX) { |
| /* Note: 0xff != DUK_BC_C_MAX */ |
| DUK_D(DUK_DPRINT("out of regs: 'c' (reg) needs shuffling but shuffle prohibited, c: %ld", (long) c)); |
| goto error_outofregs; |
| } |
| } else if (c <= DUK_BC_BC_MAX) { |
| comp_ctx->curr_func.needs_shuffle = 1; |
| tmp = comp_ctx->curr_func.shuffle3; |
| if (op_flags & DUK__EMIT_FLAG_C_IS_TARGET) { |
| /* Output shuffle needed after main operation */ |
| c_out = c; |
| } else { |
| duk_small_int_t op = op_flags & 0xff; |
| if (op == DUK_OP_EXTRA && |
| (a == DUK_EXTRAOP_INITGET || a == DUK_EXTRAOP_INITSET)) { |
| /* Special shuffling for INITGET/INITSET, where slot C |
| * identifies a register pair and cannot be shuffled |
| * normally. Use an indirect variant instead. |
| */ |
| DUK_ASSERT((op_flags & DUK__EMIT_FLAG_C_IS_TARGET) == 0); |
| duk__emit_load_int32_noshuffle(comp_ctx, tmp, c); |
| DUK_ASSERT(DUK_EXTRAOP_INITGETI == DUK_EXTRAOP_INITGET + 1); |
| DUK_ASSERT(DUK_EXTRAOP_INITSETI == DUK_EXTRAOP_INITSET + 1); |
| a++; /* indirect opcode follows direct */ |
| } else { |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, c)); |
| } |
| } |
| c = tmp; |
| } else { |
| DUK_D(DUK_DPRINT("out of regs: 'c' (reg) needs shuffling but does not fit into BC, c: %ld", (long) c)); |
| goto error_outofregs; |
| } |
| } |
| |
| /* Main operation */ |
| |
| DUK_ASSERT_DISABLE(a >= DUK_BC_A_MIN); /* unsigned */ |
| DUK_ASSERT(a <= DUK_BC_A_MAX); |
| DUK_ASSERT_DISABLE(b >= DUK_BC_B_MIN); /* unsigned */ |
| DUK_ASSERT(b <= DUK_BC_B_MAX); |
| DUK_ASSERT_DISABLE(c >= DUK_BC_C_MIN); /* unsigned */ |
| DUK_ASSERT(c <= DUK_BC_C_MAX); |
| |
| ins |= DUK_ENC_OP_A_B_C(op_flags & 0xff, a, b, c); |
| duk__emit(comp_ctx, ins); |
| |
| /* NEXTENUM needs a jump slot right after the main instruction. |
| * When the JUMP is taken, output spilling is not needed so this |
| * workaround is possible. The jump slot PC is exceptionally |
| * plumbed through comp_ctx to minimize call sites. |
| */ |
| if (op_flags & DUK__EMIT_FLAG_RESERVE_JUMPSLOT) { |
| comp_ctx->emit_jumpslot_pc = duk__get_current_pc(comp_ctx); |
| duk__emit_abc(comp_ctx, DUK_OP_JUMP, 0); |
| } |
| |
| /* Output shuffling: only one output register is realistically possible. |
| * |
| * (Zero would normally be an OK marker value: if the target register |
| * was zero, it would never be shuffled. But with DUK_USE_SHUFFLE_TORTURE |
| * this is no longer true, so use -1 as a marker instead.) |
| */ |
| |
| if (a_out >= 0) { |
| DUK_ASSERT(b_out < 0); |
| DUK_ASSERT(c_out < 0); |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, a, a_out)); |
| } else if (b_out >= 0) { |
| DUK_ASSERT(a_out < 0); |
| DUK_ASSERT(c_out < 0); |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, b, b_out)); |
| } else if (c_out >= 0) { |
| DUK_ASSERT(b_out < 0); |
| DUK_ASSERT(c_out < 0); |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, c, c_out)); |
| } |
| |
| return; |
| |
| error_outofregs: |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_REG_LIMIT); |
| } |
| |
| DUK_LOCAL void duk__emit_a_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t b) { |
| duk__emit_a_b_c(comp_ctx, op_flags | DUK__EMIT_FLAG_NO_SHUFFLE_C, a, b, 0); |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL void duk__emit_a(duk_compiler_ctx *comp_ctx, int op_flags, int a) { |
| duk__emit_a_b_c(comp_ctx, op_flags | DUK__EMIT_FLAG_NO_SHUFFLE_B | DUK__EMIT_FLAG_NO_SHUFFLE_C, a, 0, 0); |
| } |
| #endif |
| |
| DUK_LOCAL void duk__emit_a_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op_flags, duk_regconst_t a, duk_regconst_t bc) { |
| duk_instr_t ins; |
| duk_int_t tmp; |
| |
| /* allow caller to give a const number with the DUK__CONST_MARKER */ |
| bc = bc & (~DUK__CONST_MARKER); |
| |
| DUK_ASSERT_DISABLE((op_flags & 0xff) >= DUK_BC_OP_MIN); /* unsigned */ |
| DUK_ASSERT((op_flags & 0xff) <= DUK_BC_OP_MAX); |
| DUK_ASSERT_DISABLE(bc >= DUK_BC_BC_MIN); /* unsigned */ |
| DUK_ASSERT(bc <= DUK_BC_BC_MAX); |
| DUK_ASSERT((bc & DUK__CONST_MARKER) == 0); |
| |
| if (bc <= DUK_BC_BC_MAX) { |
| ; |
| } else { |
| /* No BC shuffling now. */ |
| goto error_outofregs; |
| } |
| |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| if (a <= DUK_BC_A_MAX && (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_A)) { |
| #else |
| if (a <= DUK_BC_A_MAX) { |
| #endif |
| ins = DUK_ENC_OP_A_BC(op_flags & 0xff, a, bc); |
| duk__emit(comp_ctx, ins); |
| } else if (op_flags & DUK__EMIT_FLAG_NO_SHUFFLE_A) { |
| goto error_outofregs; |
| } else if (a <= DUK_BC_BC_MAX) { |
| comp_ctx->curr_func.needs_shuffle = 1; |
| tmp = comp_ctx->curr_func.shuffle1; |
| ins = DUK_ENC_OP_A_BC(op_flags & 0xff, tmp, bc); |
| if (op_flags & DUK__EMIT_FLAG_A_IS_SOURCE) { |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_LDREG, tmp, a)); |
| duk__emit(comp_ctx, ins); |
| } else { |
| duk__emit(comp_ctx, ins); |
| duk__emit(comp_ctx, DUK_ENC_OP_A_BC(DUK_OP_STREG, tmp, a)); |
| } |
| } else { |
| goto error_outofregs; |
| } |
| return; |
| |
| error_outofregs: |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_REG_LIMIT); |
| } |
| |
| DUK_LOCAL void duk__emit_abc(duk_compiler_ctx *comp_ctx, duk_small_uint_t op, duk_regconst_t abc) { |
| duk_instr_t ins; |
| |
| DUK_ASSERT_DISABLE(op >= DUK_BC_OP_MIN); /* unsigned */ |
| DUK_ASSERT(op <= DUK_BC_OP_MAX); |
| DUK_ASSERT_DISABLE(abc >= DUK_BC_ABC_MIN); /* unsigned */ |
| DUK_ASSERT(abc <= DUK_BC_ABC_MAX); |
| DUK_ASSERT((abc & DUK__CONST_MARKER) == 0); |
| |
| if (abc <= DUK_BC_ABC_MAX) { |
| ; |
| } else { |
| goto error_outofregs; |
| } |
| ins = DUK_ENC_OP_ABC(op, abc); |
| DUK_DDD(DUK_DDDPRINT("duk__emit_abc: 0x%08lx line=%ld pc=%ld op=%ld (%!C) abc=%ld (%!I)", |
| (unsigned long) ins, (long) comp_ctx->curr_token.start_line, |
| (long) duk__get_current_pc(comp_ctx), (long) op, (long) op, |
| (long) abc, (duk_instr_t) ins)); |
| duk__emit(comp_ctx, ins); |
| return; |
| |
| error_outofregs: |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_REG_LIMIT); |
| } |
| |
| DUK_LOCAL void duk__emit_extraop_b_c(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b, duk_regconst_t c) { |
| DUK_ASSERT_DISABLE((extraop_flags & 0xff) >= DUK_BC_EXTRAOP_MIN); /* unsigned */ |
| DUK_ASSERT((extraop_flags & 0xff) <= DUK_BC_EXTRAOP_MAX); |
| /* Setting "no shuffle A" is covered by the assert, but it's needed |
| * with DUK_USE_SHUFFLE_TORTURE. |
| */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_EXTRA | DUK__EMIT_FLAG_NO_SHUFFLE_A | (extraop_flags & ~0xff), /* transfer flags */ |
| extraop_flags & 0xff, |
| b, |
| c); |
| } |
| |
| DUK_LOCAL void duk__emit_extraop_b(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags, duk_regconst_t b) { |
| DUK_ASSERT_DISABLE((extraop_flags & 0xff) >= DUK_BC_EXTRAOP_MIN); /* unsigned */ |
| DUK_ASSERT((extraop_flags & 0xff) <= DUK_BC_EXTRAOP_MAX); |
| /* Setting "no shuffle A" is covered by the assert, but it's needed |
| * with DUK_USE_SHUFFLE_TORTURE. |
| */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_EXTRA | DUK__EMIT_FLAG_NO_SHUFFLE_A | (extraop_flags & ~0xff), /* transfer flags */ |
| extraop_flags & 0xff, |
| b, |
| 0); |
| } |
| |
| DUK_LOCAL void duk__emit_extraop_bc(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop, duk_regconst_t bc) { |
| DUK_ASSERT_DISABLE(extraop >= DUK_BC_EXTRAOP_MIN); /* unsigned */ |
| DUK_ASSERT(extraop <= DUK_BC_EXTRAOP_MAX); |
| /* Setting "no shuffle A" is covered by the assert, but it's needed |
| * with DUK_USE_SHUFFLE_TORTURE. |
| */ |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_EXTRA | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| extraop, |
| bc); |
| } |
| |
| DUK_LOCAL void duk__emit_extraop_only(duk_compiler_ctx *comp_ctx, duk_small_uint_t extraop_flags) { |
| DUK_ASSERT_DISABLE((extraop_flags & 0xff) >= DUK_BC_EXTRAOP_MIN); /* unsigned */ |
| DUK_ASSERT((extraop_flags & 0xff) <= DUK_BC_EXTRAOP_MAX); |
| /* Setting "no shuffle A" is covered by the assert, but it's needed |
| * with DUK_USE_SHUFFLE_TORTURE. |
| */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_EXTRA | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_B | |
| DUK__EMIT_FLAG_NO_SHUFFLE_C | (extraop_flags & ~0xff), /* transfer flags */ |
| extraop_flags & 0xff, |
| 0, |
| 0); |
| } |
| |
| DUK_LOCAL void duk__emit_load_int32_raw(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val, duk_small_uint_t op_flags) { |
| /* XXX: Shuffling support could be implemented here so that LDINT+LDINTX |
| * would only shuffle once (instead of twice). The current code works |
| * though, and has a smaller compiler footprint. |
| */ |
| |
| if ((val >= (duk_int32_t) DUK_BC_BC_MIN - (duk_int32_t) DUK_BC_LDINT_BIAS) && |
| (val <= (duk_int32_t) DUK_BC_BC_MAX - (duk_int32_t) DUK_BC_LDINT_BIAS)) { |
| DUK_DDD(DUK_DDDPRINT("emit LDINT to reg %ld for %ld", (long) reg, (long) val)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDINT | op_flags, reg, (duk_regconst_t) (val + (duk_int32_t) DUK_BC_LDINT_BIAS)); |
| } else { |
| duk_int32_t hi = val >> DUK_BC_LDINTX_SHIFT; |
| duk_int32_t lo = val & ((((duk_int32_t) 1) << DUK_BC_LDINTX_SHIFT) - 1); |
| DUK_ASSERT(lo >= 0); |
| DUK_DDD(DUK_DDDPRINT("emit LDINT+LDINTX to reg %ld for %ld -> hi %ld, lo %ld", |
| (long) reg, (long) val, (long) hi, (long) lo)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDINT | op_flags, reg, (duk_regconst_t) (hi + (duk_int32_t) DUK_BC_LDINT_BIAS)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDINTX | op_flags, reg, (duk_regconst_t) lo); |
| } |
| } |
| |
| DUK_LOCAL void duk__emit_load_int32(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val) { |
| duk__emit_load_int32_raw(comp_ctx, reg, val, 0 /*op_flags*/); |
| } |
| |
| #if defined(DUK_USE_SHUFFLE_TORTURE) |
| /* Used by duk__emit*() calls so that we don't shuffle the loadints that |
| * are needed to handle indirect opcodes. |
| */ |
| DUK_LOCAL void duk__emit_load_int32_noshuffle(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val) { |
| duk__emit_load_int32_raw(comp_ctx, reg, val, DUK__EMIT_FLAG_NO_SHUFFLE_A /*op_flags*/); |
| } |
| #else |
| DUK_LOCAL void duk__emit_load_int32_noshuffle(duk_compiler_ctx *comp_ctx, duk_reg_t reg, duk_int32_t val) { |
| /* When torture not enabled, can just use the same helper because |
| * 'reg' won't get spilled. |
| */ |
| DUK_ASSERT(reg <= DUK_BC_A_MAX); |
| duk__emit_load_int32(comp_ctx, reg, val); |
| } |
| #endif |
| |
| DUK_LOCAL void duk__emit_jump(duk_compiler_ctx *comp_ctx, duk_int_t target_pc) { |
| duk_int_t curr_pc; |
| duk_int_t offset; |
| |
| curr_pc = (duk_int_t) (DUK_BW_GET_SIZE(comp_ctx->thr, &comp_ctx->curr_func.bw_code) / sizeof(duk_compiler_instr)); |
| offset = (duk_int_t) target_pc - (duk_int_t) curr_pc - 1; |
| DUK_ASSERT(offset + DUK_BC_JUMP_BIAS >= DUK_BC_ABC_MIN); |
| DUK_ASSERT(offset + DUK_BC_JUMP_BIAS <= DUK_BC_ABC_MAX); |
| duk__emit_abc(comp_ctx, DUK_OP_JUMP, (duk_regconst_t) (offset + DUK_BC_JUMP_BIAS)); |
| } |
| |
| DUK_LOCAL duk_int_t duk__emit_jump_empty(duk_compiler_ctx *comp_ctx) { |
| duk_int_t ret; |
| |
| ret = duk__get_current_pc(comp_ctx); /* useful for patching jumps later */ |
| duk__emit_abc(comp_ctx, DUK_OP_JUMP, 0); |
| return ret; |
| } |
| |
| /* Insert an empty jump in the middle of code emitted earlier. This is |
| * currently needed for compiling for-in. |
| */ |
| DUK_LOCAL void duk__insert_jump_entry(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc) { |
| #if defined(DUK_USE_PC2LINE) |
| duk_int_t line; |
| #endif |
| duk_compiler_instr *instr; |
| duk_size_t offset; |
| |
| offset = jump_pc * sizeof(duk_compiler_instr), |
| instr = (duk_compiler_instr *) (void *) |
| DUK_BW_INSERT_ENSURE_AREA(comp_ctx->thr, |
| &comp_ctx->curr_func.bw_code, |
| offset, |
| sizeof(duk_compiler_instr)); |
| |
| #if defined(DUK_USE_PC2LINE) |
| line = comp_ctx->curr_token.start_line; /* approximation, close enough */ |
| #endif |
| instr->ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, 0); |
| #if defined(DUK_USE_PC2LINE) |
| instr->line = line; |
| #endif |
| |
| DUK_BW_ADD_PTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code, sizeof(duk_compiler_instr)); |
| if (DUK_UNLIKELY(DUK_BW_GET_SIZE(comp_ctx->thr, &comp_ctx->curr_func.bw_code) > DUK_USE_ESBC_MAX_BYTES)) { |
| goto fail_bc_limit; |
| } |
| return; |
| |
| fail_bc_limit: |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_BYTECODE_LIMIT); |
| } |
| |
| /* Does not assume that jump_pc contains a DUK_OP_JUMP previously; this is intentional |
| * to allow e.g. an INVALID opcode be overwritten with a JUMP (label management uses this). |
| */ |
| DUK_LOCAL void duk__patch_jump(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc, duk_int_t target_pc) { |
| duk_compiler_instr *instr; |
| duk_int_t offset; |
| |
| /* allow negative PCs, behave as a no-op */ |
| if (jump_pc < 0) { |
| DUK_DDD(DUK_DDDPRINT("duk__patch_jump(): nop call, jump_pc=%ld (<0), target_pc=%ld", |
| (long) jump_pc, (long) target_pc)); |
| return; |
| } |
| DUK_ASSERT(jump_pc >= 0); |
| |
| /* XXX: range assert */ |
| instr = duk__get_instr_ptr(comp_ctx, jump_pc); |
| DUK_ASSERT(instr != NULL); |
| |
| /* XXX: range assert */ |
| offset = target_pc - jump_pc - 1; |
| |
| instr->ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, offset + DUK_BC_JUMP_BIAS); |
| DUK_DDD(DUK_DDDPRINT("duk__patch_jump(): jump_pc=%ld, target_pc=%ld, offset=%ld", |
| (long) jump_pc, (long) target_pc, (long) offset)); |
| } |
| |
| DUK_LOCAL void duk__patch_jump_here(duk_compiler_ctx *comp_ctx, duk_int_t jump_pc) { |
| duk__patch_jump(comp_ctx, jump_pc, duk__get_current_pc(comp_ctx)); |
| } |
| |
| DUK_LOCAL void duk__patch_trycatch(duk_compiler_ctx *comp_ctx, duk_int_t ldconst_pc, duk_int_t trycatch_pc, duk_regconst_t reg_catch, duk_regconst_t const_varname, duk_small_uint_t flags) { |
| duk_compiler_instr *instr; |
| |
| DUK_ASSERT((reg_catch & DUK__CONST_MARKER) == 0); |
| |
| instr = duk__get_instr_ptr(comp_ctx, ldconst_pc); |
| DUK_ASSERT(DUK_DEC_OP(instr->ins) == DUK_OP_LDCONST); |
| DUK_ASSERT(instr != NULL); |
| if (const_varname & DUK__CONST_MARKER) { |
| /* Have a catch variable. */ |
| const_varname = const_varname & (~DUK__CONST_MARKER); |
| if (reg_catch > DUK_BC_BC_MAX || const_varname > DUK_BC_BC_MAX) { |
| /* Catch attempts to use out-of-range reg/const. Without this |
| * check Duktape 0.12.0 could generate invalid code which caused |
| * an assert failure on execution. This error is triggered e.g. |
| * for functions with a lot of constants and a try-catch statement. |
| * Shuffling or opcode semantics change is needed to fix the issue. |
| * See: test-bug-trycatch-many-constants.js. |
| */ |
| DUK_D(DUK_DPRINT("failed to patch trycatch: flags=%ld, reg_catch=%ld, const_varname=%ld (0x%08lx)", |
| (long) flags, (long) reg_catch, (long) const_varname, (long) const_varname)); |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_REG_LIMIT); |
| } |
| instr->ins |= DUK_ENC_OP_A_BC(0, 0, const_varname); |
| } else { |
| /* No catch variable, e.g. a try-finally; replace LDCONST with |
| * NOP to avoid a bogus LDCONST. |
| */ |
| instr->ins = DUK_ENC_OP_A(DUK_OP_EXTRA, DUK_EXTRAOP_NOP); |
| } |
| |
| instr = duk__get_instr_ptr(comp_ctx, trycatch_pc); |
| DUK_ASSERT(instr != NULL); |
| DUK_ASSERT_DISABLE(flags >= DUK_BC_A_MIN); |
| DUK_ASSERT(flags <= DUK_BC_A_MAX); |
| instr->ins = DUK_ENC_OP_A_BC(DUK_OP_TRYCATCH, flags, reg_catch); |
| } |
| |
| DUK_LOCAL void duk__emit_if_false_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst) { |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_IF | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_C, |
| 0 /*false*/, |
| regconst, |
| 0 /*unused*/); |
| } |
| |
| DUK_LOCAL void duk__emit_if_true_skip(duk_compiler_ctx *comp_ctx, duk_regconst_t regconst) { |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_IF | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_C, |
| 1 /*true*/, |
| regconst, |
| 0 /*unused*/); |
| } |
| |
| DUK_LOCAL void duk__emit_invalid(duk_compiler_ctx *comp_ctx) { |
| duk__emit_extraop_bc(comp_ctx, DUK_EXTRAOP_INVALID, 0); |
| } |
| |
| /* |
| * Peephole optimizer for finished bytecode. |
| * |
| * Does not remove opcodes; currently only straightens out unconditional |
| * jump chains which are generated by several control structures. |
| */ |
| |
| DUK_LOCAL void duk__peephole_optimize_bytecode(duk_compiler_ctx *comp_ctx) { |
| duk_compiler_instr *bc; |
| duk_small_uint_t iter; |
| duk_int_t i, n; |
| duk_int_t count_opt; |
| |
| bc = (duk_compiler_instr *) (void *) DUK_BW_GET_BASEPTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code); |
| #if defined(DUK_USE_BUFLEN16) |
| /* No need to assert, buffer size maximum is 0xffff. */ |
| #else |
| DUK_ASSERT((duk_size_t) DUK_BW_GET_SIZE(comp_ctx->thr, &comp_ctx->curr_func.bw_code) / sizeof(duk_compiler_instr) <= (duk_size_t) DUK_INT_MAX); /* bytecode limits */ |
| #endif |
| n = (duk_int_t) (DUK_BW_GET_SIZE(comp_ctx->thr, &comp_ctx->curr_func.bw_code) / sizeof(duk_compiler_instr)); |
| |
| for (iter = 0; iter < DUK_COMPILER_PEEPHOLE_MAXITER; iter++) { |
| count_opt = 0; |
| |
| for (i = 0; i < n; i++) { |
| duk_instr_t ins; |
| duk_int_t target_pc1; |
| duk_int_t target_pc2; |
| |
| ins = bc[i].ins; |
| if (DUK_DEC_OP(ins) != DUK_OP_JUMP) { |
| continue; |
| } |
| |
| target_pc1 = i + 1 + DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS; |
| DUK_DDD(DUK_DDDPRINT("consider jump at pc %ld; target_pc=%ld", (long) i, (long) target_pc1)); |
| DUK_ASSERT(target_pc1 >= 0); |
| DUK_ASSERT(target_pc1 < n); |
| |
| /* Note: if target_pc1 == i, we'll optimize a jump to itself. |
| * This does not need to be checked for explicitly; the case |
| * is rare and max iter breaks us out. |
| */ |
| |
| ins = bc[target_pc1].ins; |
| if (DUK_DEC_OP(ins) != DUK_OP_JUMP) { |
| continue; |
| } |
| |
| target_pc2 = target_pc1 + 1 + DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS; |
| |
| DUK_DDD(DUK_DDDPRINT("optimizing jump at pc %ld; old target is %ld -> new target is %ld", |
| (long) i, (long) target_pc1, (long) target_pc2)); |
| |
| bc[i].ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, target_pc2 - (i + 1) + DUK_BC_JUMP_BIAS); |
| |
| count_opt++; |
| } |
| |
| DUK_DD(DUK_DDPRINT("optimized %ld jumps on peephole round %ld", (long) count_opt, (long) (iter + 1))); |
| |
| if (count_opt == 0) { |
| break; |
| } |
| } |
| } |
| |
| /* |
| * Intermediate value helpers |
| */ |
| |
| #define DUK__ISREG(comp_ctx,x) (((x) & DUK__CONST_MARKER) == 0) |
| #define DUK__ISCONST(comp_ctx,x) (((x) & DUK__CONST_MARKER) != 0) |
| #define DUK__ISTEMP(comp_ctx,x) (DUK__ISREG((comp_ctx), (x)) && (duk_regconst_t) (x) >= (duk_regconst_t) ((comp_ctx)->curr_func.temp_first)) |
| #define DUK__GETTEMP(comp_ctx) ((comp_ctx)->curr_func.temp_next) |
| #define DUK__SETTEMP(comp_ctx,x) ((comp_ctx)->curr_func.temp_next = (x)) /* dangerous: must only lower (temp_max not updated) */ |
| #define DUK__SETTEMP_CHECKMAX(comp_ctx,x) duk__settemp_checkmax((comp_ctx),(x)) |
| #define DUK__ALLOCTEMP(comp_ctx) duk__alloctemp((comp_ctx)) |
| #define DUK__ALLOCTEMPS(comp_ctx,count) duk__alloctemps((comp_ctx),(count)) |
| |
| /* Flags for intermediate value coercions. A flag for using a forced reg |
| * is not needed, the forced_reg argument suffices and generates better |
| * code (it is checked as it is used). |
| */ |
| #define DUK__IVAL_FLAG_ALLOW_CONST (1 << 0) /* allow a constant to be returned */ |
| #define DUK__IVAL_FLAG_REQUIRE_TEMP (1 << 1) /* require a (mutable) temporary as a result (or a const if allowed) */ |
| #define DUK__IVAL_FLAG_REQUIRE_SHORT (1 << 2) /* require a short (8-bit) reg/const which fits into bytecode B/C slot */ |
| |
| /* XXX: some code might benefit from DUK__SETTEMP_IFTEMP(ctx,x) */ |
| |
| #if 0 /* enable manually for dumping */ |
| #define DUK__DUMP_ISPEC(compctx,ispec) do { duk__dump_ispec((compctx), (ispec)); } while (0) |
| #define DUK__DUMP_IVALUE(compctx,ivalue) do { duk__dump_ivalue((compctx), (ivalue)); } while (0) |
| |
| DUK_LOCAL void duk__dump_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *x) { |
| DUK_D(DUK_DPRINT("ispec dump: t=%ld regconst=0x%08lx, valstack_idx=%ld, value=%!T", |
| (long) x->t, (unsigned long) x->regconst, (long) x->valstack_idx, |
| duk_get_tval((duk_context *) comp_ctx->thr, x->valstack_idx))); |
| } |
| DUK_LOCAL void duk__dump_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| DUK_D(DUK_DPRINT("ivalue dump: t=%ld op=%ld " |
| "x1={t=%ld regconst=0x%08lx valstack_idx=%ld value=%!T} " |
| "x2={t=%ld regconst=0x%08lx valstack_idx=%ld value=%!T}", |
| (long) x->t, (long) x->op, |
| (long) x->x1.t, (unsigned long) x->x1.regconst, (long) x->x1.valstack_idx, |
| duk_get_tval((duk_context *) comp_ctx->thr, x->x1.valstack_idx), |
| (long) x->x2.t, (unsigned long) x->x2.regconst, (long) x->x2.valstack_idx, |
| duk_get_tval((duk_context *) comp_ctx->thr, x->x2.valstack_idx))); |
| } |
| #else |
| #define DUK__DUMP_ISPEC(comp_ctx,x) do {} while (0) |
| #define DUK__DUMP_IVALUE(comp_ctx,x) do {} while (0) |
| #endif |
| |
| DUK_LOCAL void duk__copy_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *src, duk_ispec *dst) { |
| duk_context *ctx = (duk_context *) comp_ctx->thr; |
| |
| dst->t = src->t; |
| dst->regconst = src->regconst; |
| duk_copy(ctx, src->valstack_idx, dst->valstack_idx); |
| } |
| |
| DUK_LOCAL void duk__copy_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *src, duk_ivalue *dst) { |
| duk_context *ctx = (duk_context *) comp_ctx->thr; |
| |
| dst->t = src->t; |
| dst->op = src->op; |
| dst->x1.t = src->x1.t; |
| dst->x1.regconst = src->x1.regconst; |
| dst->x2.t = src->x2.t; |
| dst->x2.regconst = src->x2.regconst; |
| duk_copy(ctx, src->x1.valstack_idx, dst->x1.valstack_idx); |
| duk_copy(ctx, src->x2.valstack_idx, dst->x2.valstack_idx); |
| } |
| |
| /* XXX: to util */ |
| DUK_LOCAL duk_bool_t duk__is_whole_get_int32(duk_double_t x, duk_int32_t *ival) { |
| duk_small_int_t c; |
| duk_int32_t t; |
| |
| c = DUK_FPCLASSIFY(x); |
| if (c == DUK_FP_NORMAL || (c == DUK_FP_ZERO && !DUK_SIGNBIT(x))) { |
| /* Don't allow negative zero as it will cause trouble with |
| * LDINT+LDINTX. But positive zero is OK. |
| */ |
| t = (duk_int32_t) x; |
| if ((duk_double_t) t == x) { |
| *ival = t; |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| DUK_LOCAL duk_reg_t duk__alloctemps(duk_compiler_ctx *comp_ctx, duk_small_int_t num) { |
| duk_reg_t res; |
| |
| res = comp_ctx->curr_func.temp_next; |
| comp_ctx->curr_func.temp_next += num; |
| |
| if (comp_ctx->curr_func.temp_next > DUK__MAX_TEMPS) { /* == DUK__MAX_TEMPS is OK */ |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_TEMP_LIMIT); |
| } |
| |
| /* maintain highest 'used' temporary, needed to figure out nregs of function */ |
| if (comp_ctx->curr_func.temp_next > comp_ctx->curr_func.temp_max) { |
| comp_ctx->curr_func.temp_max = comp_ctx->curr_func.temp_next; |
| } |
| |
| return res; |
| } |
| |
| DUK_LOCAL duk_reg_t duk__alloctemp(duk_compiler_ctx *comp_ctx) { |
| return duk__alloctemps(comp_ctx, 1); |
| } |
| |
| DUK_LOCAL void duk__settemp_checkmax(duk_compiler_ctx *comp_ctx, duk_reg_t temp_next) { |
| comp_ctx->curr_func.temp_next = temp_next; |
| if (temp_next > comp_ctx->curr_func.temp_max) { |
| comp_ctx->curr_func.temp_max = temp_next; |
| } |
| } |
| |
| /* get const for value at valstack top */ |
| DUK_LOCAL duk_regconst_t duk__getconst(duk_compiler_ctx *comp_ctx) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_compiler_func *f = &comp_ctx->curr_func; |
| duk_tval *tv1; |
| duk_int_t i, n, n_check; |
| |
| n = (duk_int_t) duk_get_length(ctx, f->consts_idx); |
| |
| tv1 = DUK_GET_TVAL_NEGIDX(ctx, -1); |
| DUK_ASSERT(tv1 != NULL); |
| |
| #if defined(DUK_USE_FASTINT) |
| /* Explicit check for fastint downgrade. */ |
| DUK_TVAL_CHKFAST_INPLACE(tv1); |
| #endif |
| |
| /* Sanity workaround for handling functions with a large number of |
| * constants at least somewhat reasonably. Otherwise checking whether |
| * we already have the constant would grow very slow (as it is O(N^2)). |
| */ |
| n_check = (n > DUK__GETCONST_MAX_CONSTS_CHECK ? DUK__GETCONST_MAX_CONSTS_CHECK : n); |
| for (i = 0; i < n_check; i++) { |
| duk_tval *tv2 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, f->h_consts, i); |
| |
| /* Strict equality is NOT enough, because we cannot use the same |
| * constant for e.g. +0 and -0. |
| */ |
| if (duk_js_samevalue(tv1, tv2)) { |
| DUK_DDD(DUK_DDDPRINT("reused existing constant for %!T -> const index %ld", |
| (duk_tval *) tv1, (long) i)); |
| duk_pop(ctx); |
| return (duk_regconst_t) (i | DUK__CONST_MARKER); |
| } |
| } |
| |
| if (n > DUK__MAX_CONSTS) { |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_CONST_LIMIT); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("allocating new constant for %!T -> const index %ld", |
| (duk_tval *) tv1, (long) n)); |
| (void) duk_put_prop_index(ctx, f->consts_idx, n); /* invalidates tv1, tv2 */ |
| return (duk_regconst_t) (n | DUK__CONST_MARKER); |
| } |
| |
| /* Get the value represented by an duk_ispec to a register or constant. |
| * The caller can control the result by indicating whether or not: |
| * |
| * (1) a constant is allowed (sometimes the caller needs the result to |
| * be in a register) |
| * |
| * (2) a temporary register is required (usually when caller requires |
| * the register to be safely mutable; normally either a bound |
| * register or a temporary register are both OK) |
| * |
| * (3) a forced register target needs to be used |
| * |
| * Bytecode may be emitted to generate the necessary value. The return |
| * value is either a register or a constant. |
| */ |
| |
| DUK_LOCAL |
| duk_regconst_t duk__ispec_toregconst_raw(duk_compiler_ctx *comp_ctx, |
| duk_ispec *x, |
| duk_reg_t forced_reg, |
| duk_small_uint_t flags) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| |
| DUK_DDD(DUK_DDDPRINT("duk__ispec_toregconst_raw(): x={%ld:%ld:%!T}, " |
| "forced_reg=%ld, flags 0x%08lx: allow_const=%ld require_temp=%ld require_short=%ld", |
| (long) x->t, |
| (long) x->regconst, |
| (duk_tval *) duk_get_tval(ctx, x->valstack_idx), |
| (long) forced_reg, |
| (unsigned long) flags, |
| (long) ((flags & DUK__IVAL_FLAG_ALLOW_CONST) ? 1 : 0), |
| (long) ((flags & DUK__IVAL_FLAG_REQUIRE_TEMP) ? 1 : 0), |
| (long) ((flags & DUK__IVAL_FLAG_REQUIRE_SHORT) ? 1 : 0))); |
| |
| switch (x->t) { |
| case DUK_ISPEC_VALUE: { |
| duk_tval *tv; |
| |
| tv = DUK_GET_TVAL_POSIDX(ctx, x->valstack_idx); |
| DUK_ASSERT(tv != NULL); |
| |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: { |
| /* Note: although there is no 'undefined' literal, undefined |
| * values can occur during compilation as a result of e.g. |
| * the 'void' operator. |
| */ |
| duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_extraop_bc(comp_ctx, DUK_EXTRAOP_LDUNDEF, (duk_regconst_t) dest); |
| return (duk_regconst_t) dest; |
| } |
| case DUK_TAG_NULL: { |
| duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_extraop_bc(comp_ctx, DUK_EXTRAOP_LDNULL, (duk_regconst_t) dest); |
| return (duk_regconst_t) dest; |
| } |
| case DUK_TAG_BOOLEAN: { |
| duk_reg_t dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_extraop_bc(comp_ctx, |
| (DUK_TVAL_GET_BOOLEAN(tv) ? DUK_EXTRAOP_LDTRUE : DUK_EXTRAOP_LDFALSE), |
| (duk_regconst_t) dest); |
| return (duk_regconst_t) dest; |
| } |
| case DUK_TAG_POINTER: { |
| DUK_UNREACHABLE(); |
| break; |
| } |
| case DUK_TAG_STRING: { |
| duk_hstring *h; |
| duk_reg_t dest; |
| duk_regconst_t constidx; |
| |
| h = DUK_TVAL_GET_STRING(tv); |
| DUK_UNREF(h); |
| DUK_ASSERT(h != NULL); |
| |
| #if 0 /* XXX: to be implemented? */ |
| /* Use special opcodes to load short strings */ |
| if (DUK_HSTRING_GET_BYTELEN(h) <= 2) { |
| /* Encode into a single opcode (18 bits can encode 1-2 bytes + length indicator) */ |
| } else if (DUK_HSTRING_GET_BYTELEN(h) <= 6) { |
| /* Encode into a double constant (53 bits can encode 6*8 = 48 bits + 3-bit length */ |
| } |
| #endif |
| duk_dup(ctx, x->valstack_idx); |
| constidx = duk__getconst(comp_ctx); |
| |
| if (flags & DUK__IVAL_FLAG_ALLOW_CONST) { |
| return constidx; |
| } |
| |
| dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, (duk_regconst_t) dest, constidx); |
| return (duk_regconst_t) dest; |
| } |
| case DUK_TAG_OBJECT: { |
| DUK_UNREACHABLE(); |
| break; |
| } |
| case DUK_TAG_BUFFER: { |
| DUK_UNREACHABLE(); |
| break; |
| } |
| case DUK_TAG_LIGHTFUNC: { |
| DUK_UNREACHABLE(); |
| break; |
| } |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: { |
| /* number */ |
| duk_reg_t dest; |
| duk_regconst_t constidx; |
| duk_double_t dval; |
| duk_int32_t ival; |
| |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| dval = DUK_TVAL_GET_NUMBER(tv); |
| |
| if (!(flags & DUK__IVAL_FLAG_ALLOW_CONST)) { |
| /* A number can be loaded either through a constant, using |
| * LDINT, or using LDINT+LDINTX. LDINT is always a size win, |
| * LDINT+LDINTX is not if the constant is used multiple times. |
| * Currently always prefer LDINT+LDINTX over a double constant. |
| */ |
| |
| if (duk__is_whole_get_int32(dval, &ival)) { |
| dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_load_int32(comp_ctx, dest, ival); |
| return (duk_regconst_t) dest; |
| } |
| } |
| |
| duk_dup(ctx, x->valstack_idx); |
| constidx = duk__getconst(comp_ctx); |
| |
| if (flags & DUK__IVAL_FLAG_ALLOW_CONST) { |
| return constidx; |
| } else { |
| dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, (duk_regconst_t) dest, constidx); |
| return (duk_regconst_t) dest; |
| } |
| } |
| } /* end switch */ |
| } |
| case DUK_ISPEC_REGCONST: { |
| if (forced_reg >= 0) { |
| if (x->regconst & DUK__CONST_MARKER) { |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, forced_reg, x->regconst); |
| } else if (x->regconst != (duk_regconst_t) forced_reg) { |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDREG, forced_reg, x->regconst); |
| } else { |
| ; /* already in correct reg */ |
| } |
| return (duk_regconst_t) forced_reg; |
| } |
| |
| DUK_ASSERT(forced_reg < 0); |
| if (x->regconst & DUK__CONST_MARKER) { |
| if (!(flags & DUK__IVAL_FLAG_ALLOW_CONST)) { |
| duk_reg_t dest = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, (duk_regconst_t) dest, x->regconst); |
| return (duk_regconst_t) dest; |
| } |
| return x->regconst; |
| } |
| |
| DUK_ASSERT(forced_reg < 0 && !(x->regconst & DUK__CONST_MARKER)); |
| if ((flags & DUK__IVAL_FLAG_REQUIRE_TEMP) && !DUK__ISTEMP(comp_ctx, x->regconst)) { |
| duk_reg_t dest = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDREG, (duk_regconst_t) dest, x->regconst); |
| return (duk_regconst_t) dest; |
| } |
| return x->regconst; |
| } |
| default: { |
| break; |
| } |
| } |
| |
| DUK_ERROR_INTERNAL_DEFMSG(thr); |
| return 0; |
| } |
| |
| DUK_LOCAL void duk__ispec_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ispec *x, duk_reg_t forced_reg) { |
| DUK_ASSERT(forced_reg >= 0); |
| (void) duk__ispec_toregconst_raw(comp_ctx, x, forced_reg, 0 /*flags*/); |
| } |
| |
| /* Coerce an duk_ivalue to a 'plain' value by generating the necessary |
| * arithmetic operations, property access, or variable access bytecode. |
| * The duk_ivalue argument ('x') is converted into a plain value as a |
| * side effect. |
| */ |
| DUK_LOCAL void duk__ivalue_toplain_raw(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_reg_t forced_reg) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| |
| DUK_DDD(DUK_DDDPRINT("duk__ivalue_toplain_raw(): x={t=%ld,op=%ld,x1={%ld:%ld:%!T},x2={%ld:%ld:%!T}}, " |
| "forced_reg=%ld", |
| (long) x->t, (long) x->op, |
| (long) x->x1.t, (long) x->x1.regconst, |
| (duk_tval *) duk_get_tval(ctx, x->x1.valstack_idx), |
| (long) x->x2.t, (long) x->x2.regconst, |
| (duk_tval *) duk_get_tval(ctx, x->x2.valstack_idx), |
| (long) forced_reg)); |
| |
| switch (x->t) { |
| case DUK_IVAL_PLAIN: { |
| return; |
| } |
| /* XXX: support unary arithmetic ivalues (useful?) */ |
| case DUK_IVAL_ARITH: |
| case DUK_IVAL_ARITH_EXTRAOP: { |
| duk_regconst_t arg1; |
| duk_regconst_t arg2; |
| duk_reg_t dest; |
| duk_tval *tv1; |
| duk_tval *tv2; |
| |
| DUK_DDD(DUK_DDDPRINT("arith to plain conversion")); |
| |
| /* inline arithmetic check for constant values */ |
| /* XXX: use the exactly same arithmetic function here as in executor */ |
| if (x->x1.t == DUK_ISPEC_VALUE && x->x2.t == DUK_ISPEC_VALUE && x->t == DUK_IVAL_ARITH) { |
| tv1 = DUK_GET_TVAL_POSIDX(ctx, x->x1.valstack_idx); |
| tv2 = DUK_GET_TVAL_POSIDX(ctx, x->x2.valstack_idx); |
| DUK_ASSERT(tv1 != NULL); |
| DUK_ASSERT(tv2 != NULL); |
| |
| DUK_DDD(DUK_DDDPRINT("arith: tv1=%!T, tv2=%!T", |
| (duk_tval *) tv1, |
| (duk_tval *) tv2)); |
| |
| if (DUK_TVAL_IS_NUMBER(tv1) && DUK_TVAL_IS_NUMBER(tv2)) { |
| duk_double_t d1 = DUK_TVAL_GET_NUMBER(tv1); |
| duk_double_t d2 = DUK_TVAL_GET_NUMBER(tv2); |
| duk_double_t d3; |
| duk_bool_t accept = 1; |
| |
| DUK_DDD(DUK_DDDPRINT("arith inline check: d1=%lf, d2=%lf, op=%ld", |
| (double) d1, (double) d2, (long) x->op)); |
| switch (x->op) { |
| case DUK_OP_ADD: d3 = d1 + d2; break; |
| case DUK_OP_SUB: d3 = d1 - d2; break; |
| case DUK_OP_MUL: d3 = d1 * d2; break; |
| case DUK_OP_DIV: d3 = d1 / d2; break; |
| default: accept = 0; break; |
| } |
| |
| if (accept) { |
| duk_double_union du; |
| du.d = d3; |
| DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du); |
| d3 = du.d; |
| |
| x->t = DUK_IVAL_PLAIN; |
| DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE); |
| DUK_TVAL_SET_NUMBER(tv1, d3); /* old value is number: no refcount */ |
| return; |
| } |
| } else if (x->op == DUK_OP_ADD && DUK_TVAL_IS_STRING(tv1) && DUK_TVAL_IS_STRING(tv2)) { |
| /* inline string concatenation */ |
| duk_dup(ctx, x->x1.valstack_idx); |
| duk_dup(ctx, x->x2.valstack_idx); |
| duk_concat(ctx, 2); |
| duk_replace(ctx, x->x1.valstack_idx); |
| x->t = DUK_IVAL_PLAIN; |
| DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE); |
| return; |
| } |
| } |
| |
| arg1 = duk__ispec_toregconst_raw(comp_ctx, &x->x1, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/); |
| arg2 = duk__ispec_toregconst_raw(comp_ctx, &x->x2, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/); |
| |
| /* If forced reg, use it as destination. Otherwise try to |
| * use either coerced ispec if it is a temporary. |
| * |
| * When using extraops, avoid reusing arg2 as dest because that |
| * would lead to an LDREG shuffle below. We still can't guarantee |
| * dest != arg2 because we may have a forced_reg. |
| */ |
| if (forced_reg >= 0) { |
| dest = forced_reg; |
| } else if (DUK__ISTEMP(comp_ctx, arg1)) { |
| dest = (duk_reg_t) arg1; |
| } else if (DUK__ISTEMP(comp_ctx, arg2) && x->t != DUK_IVAL_ARITH_EXTRAOP) { |
| dest = (duk_reg_t) arg2; |
| } else { |
| dest = DUK__ALLOCTEMP(comp_ctx); |
| } |
| |
| /* Extraop arithmetic opcodes must have destination same as |
| * first source. If second source matches destination we need |
| * a temporary register to avoid clobbering the second source. |
| * |
| * XXX: change calling code to avoid this situation in most cases. |
| */ |
| |
| if (x->t == DUK_IVAL_ARITH_EXTRAOP) { |
| if (!(DUK__ISREG(comp_ctx, arg1) && (duk_reg_t) arg1 == dest)) { |
| if (DUK__ISREG(comp_ctx, arg2) && (duk_reg_t) arg2 == dest) { |
| /* arg2 would be clobbered so reassign it to a temp. */ |
| duk_reg_t tempreg; |
| tempreg = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDREG, tempreg, arg2); |
| arg2 = tempreg; |
| } |
| |
| if (DUK__ISREG(comp_ctx, arg1)) { |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDREG, dest, arg1); |
| } else { |
| DUK_ASSERT(DUK__ISCONST(comp_ctx, arg1)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, dest, arg1); |
| } |
| } |
| |
| /* Note: special DUK__EMIT_FLAG_B_IS_TARGETSOURCE |
| * used to indicate that B is both a source and a |
| * target register. When shuffled, it needs to be |
| * both input and output shuffled. |
| */ |
| DUK_ASSERT(DUK__ISREG(comp_ctx, dest)); |
| duk__emit_extraop_b_c(comp_ctx, |
| x->op | DUK__EMIT_FLAG_B_IS_TARGET | |
| DUK__EMIT_FLAG_B_IS_TARGETSOURCE, |
| (duk_regconst_t) dest, |
| (duk_regconst_t) arg2); |
| |
| } else { |
| DUK_ASSERT(DUK__ISREG(comp_ctx, dest)); |
| duk__emit_a_b_c(comp_ctx, x->op, (duk_regconst_t) dest, arg1, arg2); |
| } |
| |
| x->t = DUK_IVAL_PLAIN; |
| x->x1.t = DUK_ISPEC_REGCONST; |
| x->x1.regconst = (duk_regconst_t) dest; |
| return; |
| } |
| case DUK_IVAL_PROP: { |
| /* XXX: very similar to DUK_IVAL_ARITH - merge? */ |
| duk_regconst_t arg1; |
| duk_regconst_t arg2; |
| duk_reg_t dest; |
| |
| /* Need a short reg/const, does not have to be a mutable temp. */ |
| arg1 = duk__ispec_toregconst_raw(comp_ctx, &x->x1, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/); |
| arg2 = duk__ispec_toregconst_raw(comp_ctx, &x->x2, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_SHORT /*flags*/); |
| |
| /* Pick a destination register. If either base value or key |
| * happens to be a temp value, reuse it as the destination. |
| * |
| * XXX: The temp must be a "mutable" one, i.e. such that no |
| * other expression is using it anymore. Here this should be |
| * the case because the value of a property access expression |
| * is neither the base nor the key, but the lookup result. |
| */ |
| |
| if (forced_reg >= 0) { |
| dest = forced_reg; |
| } else if (DUK__ISTEMP(comp_ctx, arg1)) { |
| dest = (duk_reg_t) arg1; |
| } else if (DUK__ISTEMP(comp_ctx, arg2)) { |
| dest = (duk_reg_t) arg2; |
| } else { |
| dest = DUK__ALLOCTEMP(comp_ctx); |
| } |
| |
| duk__emit_a_b_c(comp_ctx, DUK_OP_GETPROP, (duk_regconst_t) dest, arg1, arg2); |
| |
| x->t = DUK_IVAL_PLAIN; |
| x->x1.t = DUK_ISPEC_REGCONST; |
| x->x1.regconst = (duk_regconst_t) dest; |
| return; |
| } |
| case DUK_IVAL_VAR: { |
| /* x1 must be a string */ |
| duk_reg_t dest; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE); |
| |
| duk_dup(ctx, x->x1.valstack_idx); |
| if (duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| x->t = DUK_IVAL_PLAIN; |
| x->x1.t = DUK_ISPEC_REGCONST; |
| x->x1.regconst = (duk_regconst_t) reg_varbind; |
| } else { |
| dest = (forced_reg >= 0 ? forced_reg : DUK__ALLOCTEMP(comp_ctx)); |
| duk__emit_a_bc(comp_ctx, DUK_OP_GETVAR, (duk_regconst_t) dest, rc_varname); |
| x->t = DUK_IVAL_PLAIN; |
| x->x1.t = DUK_ISPEC_REGCONST; |
| x->x1.regconst = (duk_regconst_t) dest; |
| } |
| return; |
| } |
| case DUK_IVAL_NONE: |
| default: { |
| DUK_D(DUK_DPRINT("invalid ivalue type: %ld", (long) x->t)); |
| break; |
| } |
| } |
| |
| DUK_ERROR_INTERNAL_DEFMSG(thr); |
| return; |
| } |
| |
| /* evaluate to plain value, no forced register (temp/bound reg both ok) */ |
| DUK_LOCAL void duk__ivalue_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| duk__ivalue_toplain_raw(comp_ctx, x, -1 /*forced_reg*/); |
| } |
| |
| /* evaluate to final form (e.g. coerce GETPROP to code), throw away temp */ |
| DUK_LOCAL void duk__ivalue_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| duk_reg_t temp; |
| |
| /* If duk__ivalue_toplain_raw() allocates a temp, forget it and |
| * restore next temp state. |
| */ |
| temp = DUK__GETTEMP(comp_ctx); |
| duk__ivalue_toplain_raw(comp_ctx, x, -1 /*forced_reg*/); |
| DUK__SETTEMP(comp_ctx, temp); |
| } |
| |
| /* Coerce an duk_ivalue to a register or constant; result register may |
| * be a temp or a bound register. |
| * |
| * The duk_ivalue argument ('x') is converted into a regconst as a |
| * side effect. |
| */ |
| DUK_LOCAL |
| duk_regconst_t duk__ivalue_toregconst_raw(duk_compiler_ctx *comp_ctx, |
| duk_ivalue *x, |
| duk_reg_t forced_reg, |
| duk_small_uint_t flags) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_regconst_t reg; |
| DUK_UNREF(thr); |
| DUK_UNREF(ctx); |
| |
| DUK_DDD(DUK_DDDPRINT("duk__ivalue_toregconst_raw(): x={t=%ld,op=%ld,x1={%ld:%ld:%!T},x2={%ld:%ld:%!T}}, " |
| "forced_reg=%ld, flags 0x%08lx: allow_const=%ld require_temp=%ld require_short=%ld", |
| (long) x->t, (long) x->op, |
| (long) x->x1.t, (long) x->x1.regconst, |
| (duk_tval *) duk_get_tval(ctx, x->x1.valstack_idx), |
| (long) x->x2.t, (long) x->x2.regconst, |
| (duk_tval *) duk_get_tval(ctx, x->x2.valstack_idx), |
| (long) forced_reg, |
| (unsigned long) flags, |
| (long) ((flags & DUK__IVAL_FLAG_ALLOW_CONST) ? 1 : 0), |
| (long) ((flags & DUK__IVAL_FLAG_REQUIRE_TEMP) ? 1 : 0), |
| (long) ((flags & DUK__IVAL_FLAG_REQUIRE_SHORT) ? 1 : 0))); |
| |
| /* first coerce to a plain value */ |
| duk__ivalue_toplain_raw(comp_ctx, x, forced_reg); |
| DUK_ASSERT(x->t == DUK_IVAL_PLAIN); |
| |
| /* then to a register */ |
| reg = duk__ispec_toregconst_raw(comp_ctx, &x->x1, forced_reg, flags); |
| x->x1.t = DUK_ISPEC_REGCONST; |
| x->x1.regconst = reg; |
| |
| return reg; |
| } |
| |
| DUK_LOCAL duk_reg_t duk__ivalue_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| return duk__ivalue_toregconst_raw(comp_ctx, x, -1, 0 /*flags*/); |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL duk_reg_t duk__ivalue_totemp(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| return duk__ivalue_toregconst_raw(comp_ctx, x, -1, DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/); |
| } |
| #endif |
| |
| DUK_LOCAL void duk__ivalue_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x, duk_int_t forced_reg) { |
| DUK_ASSERT(forced_reg >= 0); |
| (void) duk__ivalue_toregconst_raw(comp_ctx, x, forced_reg, 0 /*flags*/); |
| } |
| |
| DUK_LOCAL duk_regconst_t duk__ivalue_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| return duk__ivalue_toregconst_raw(comp_ctx, x, -1, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/); |
| } |
| |
| DUK_LOCAL duk_regconst_t duk__ivalue_totempconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x) { |
| return duk__ivalue_toregconst_raw(comp_ctx, x, -1, DUK__IVAL_FLAG_ALLOW_CONST | DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/); |
| } |
| |
| /* The issues below can be solved with better flags */ |
| |
| /* XXX: many operations actually want toforcedtemp() -- brand new temp? */ |
| /* XXX: need a toplain_ignore() which will only coerce a value to a temp |
| * register if it might have a side effect. Side-effect free values do not |
| * need to be coerced. |
| */ |
| |
| /* |
| * Identifier handling |
| */ |
| |
| DUK_LOCAL duk_reg_t duk__lookup_active_register_binding(duk_compiler_ctx *comp_ctx) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_hstring *h_varname; |
| duk_reg_t ret; |
| |
| DUK_DDD(DUK_DDDPRINT("resolving identifier reference to '%!T'", |
| (duk_tval *) duk_get_tval(ctx, -1))); |
| |
| /* |
| * Special name handling |
| */ |
| |
| h_varname = duk_get_hstring(ctx, -1); |
| DUK_ASSERT(h_varname != NULL); |
| |
| if (h_varname == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)) { |
| DUK_DDD(DUK_DDDPRINT("flagging function as accessing 'arguments'")); |
| comp_ctx->curr_func.id_access_arguments = 1; |
| } |
| |
| /* |
| * Inside one or more 'with' statements fall back to slow path always. |
| * (See e.g. test-stmt-with.js.) |
| */ |
| |
| if (comp_ctx->curr_func.with_depth > 0) { |
| DUK_DDD(DUK_DDDPRINT("identifier lookup inside a 'with' -> fall back to slow path")); |
| goto slow_path; |
| } |
| |
| /* |
| * Any catch bindings ("catch (e)") also affect identifier binding. |
| * |
| * Currently, the varmap is modified for the duration of the catch |
| * clause to ensure any identifier accesses with the catch variable |
| * name will use slow path. |
| */ |
| |
| duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| if (duk_is_number(ctx, -1)) { |
| ret = duk_to_int(ctx, -1); |
| duk_pop(ctx); |
| } else { |
| duk_pop(ctx); |
| goto slow_path; |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("identifier lookup -> reg %ld", (long) ret)); |
| return ret; |
| |
| slow_path: |
| DUK_DDD(DUK_DDDPRINT("identifier lookup -> slow path")); |
| |
| comp_ctx->curr_func.id_access_slow = 1; |
| return (duk_reg_t) -1; |
| } |
| |
| /* Lookup an identifier name in the current varmap, indicating whether the |
| * identifier is register-bound and if not, allocating a constant for the |
| * identifier name. Returns 1 if register-bound, 0 otherwise. Caller can |
| * also check (out_reg_varbind >= 0) to check whether or not identifier is |
| * register bound. The caller must NOT use out_rc_varname at all unless |
| * return code is 0 or out_reg_varbind is < 0; this is becuase out_rc_varname |
| * is unsigned and doesn't have a "unused" / none value. |
| */ |
| DUK_LOCAL duk_bool_t duk__lookup_lhs(duk_compiler_ctx *comp_ctx, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| /* [ ... varname ] */ |
| |
| duk_dup_top(ctx); |
| reg_varbind = duk__lookup_active_register_binding(comp_ctx); |
| |
| if (reg_varbind >= 0) { |
| *out_reg_varbind = reg_varbind; |
| *out_rc_varname = 0; /* duk_regconst_t is unsigned, so use 0 as dummy value (ignored by caller) */ |
| duk_pop(ctx); |
| return 1; |
| } else { |
| rc_varname = duk__getconst(comp_ctx); |
| *out_reg_varbind = -1; |
| *out_rc_varname = rc_varname; |
| return 0; |
| } |
| } |
| |
| /* |
| * Label handling |
| * |
| * Labels are initially added with flags prohibiting both break and continue. |
| * When the statement type is finally uncovered (after potentially multiple |
| * labels), all the labels are updated to allow/prohibit break and continue. |
| */ |
| |
| DUK_LOCAL void duk__add_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_int_t pc_label, duk_int_t label_id) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_size_t n; |
| duk_size_t new_size; |
| duk_uint8_t *p; |
| duk_labelinfo *li_start, *li; |
| |
| /* Duplicate (shadowing) labels are not allowed, except for the empty |
| * labels (which are used as default labels for switch and iteration |
| * statements). |
| * |
| * We could also allow shadowing of non-empty pending labels without any |
| * other issues than breaking the required label shadowing requirements |
| * of the E5 specification, see Section 12.12. |
| */ |
| |
| p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, comp_ctx->curr_func.h_labelinfos); |
| li_start = (duk_labelinfo *) (void *) p; |
| li = (duk_labelinfo *) (void *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos)); |
| n = (duk_size_t) (li - li_start); |
| |
| while (li > li_start) { |
| li--; |
| |
| if (li->h_label == h_label && h_label != DUK_HTHREAD_STRING_EMPTY_STRING(thr)) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_DUPLICATE_LABEL); |
| } |
| } |
| |
| duk_push_hstring(ctx, h_label); |
| DUK_ASSERT(n <= DUK_UARRIDX_MAX); /* label limits */ |
| (void) duk_put_prop_index(ctx, comp_ctx->curr_func.labelnames_idx, (duk_uarridx_t) n); |
| |
| new_size = (n + 1) * sizeof(duk_labelinfo); |
| duk_hbuffer_resize(thr, comp_ctx->curr_func.h_labelinfos, new_size); |
| /* XXX: spare handling, slow now */ |
| |
| /* relookup after possible realloc */ |
| p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, comp_ctx->curr_func.h_labelinfos); |
| li_start = (duk_labelinfo *) (void *) p; |
| DUK_UNREF(li_start); /* silence scan-build warning */ |
| li = (duk_labelinfo *) (void *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos)); |
| li--; |
| |
| /* Labels can be used for iteration statements but also for other statements, |
| * in particular a label can be used for a block statement. All cases of a |
| * named label accept a 'break' so that flag is set here. Iteration statements |
| * also allow 'continue', so that flag is updated when we figure out the |
| * statement type. |
| */ |
| |
| li->flags = DUK_LABEL_FLAG_ALLOW_BREAK; |
| li->label_id = label_id; |
| li->h_label = h_label; |
| li->catch_depth = comp_ctx->curr_func.catch_depth; /* catch depth from current func */ |
| li->pc_label = pc_label; |
| |
| DUK_DDD(DUK_DDDPRINT("registered label: flags=0x%08lx, id=%ld, name=%!O, catch_depth=%ld, pc_label=%ld", |
| (unsigned long) li->flags, (long) li->label_id, (duk_heaphdr *) li->h_label, |
| (long) li->catch_depth, (long) li->pc_label)); |
| } |
| |
| /* Update all labels with matching label_id. */ |
| DUK_LOCAL void duk__update_label_flags(duk_compiler_ctx *comp_ctx, duk_int_t label_id, duk_small_uint_t flags) { |
| duk_uint8_t *p; |
| duk_labelinfo *li_start, *li; |
| |
| p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(comp_ctx->thr->heap, comp_ctx->curr_func.h_labelinfos); |
| li_start = (duk_labelinfo *) (void *) p; |
| li = (duk_labelinfo *) (void *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos)); |
| |
| /* Match labels starting from latest; once label_id no longer matches, we can |
| * safely exit without checking the rest of the labels (only the topmost labels |
| * are ever updated). |
| */ |
| while (li > li_start) { |
| li--; |
| |
| if (li->label_id != label_id) { |
| break; |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("updating (overwriting) label flags for li=%p, label_id=%ld, flags=%ld", |
| (void *) li, (long) label_id, (long) flags)); |
| |
| li->flags = flags; |
| } |
| } |
| |
| /* Lookup active label information. Break/continue distinction is necessary to handle switch |
| * statement related labels correctly: a switch will only catch a 'break', not a 'continue'. |
| * |
| * An explicit label cannot appear multiple times in the active set, but empty labels (unlabelled |
| * iteration and switch statements) can. A break will match the closest unlabelled or labelled |
| * statement. A continue will match the closest unlabelled or labelled iteration statement. It is |
| * a syntax error if a continue matches a labelled switch statement; because an explicit label cannot |
| * be duplicated, the continue cannot match any valid label outside the switch. |
| * |
| * A side effect of these rules is that a LABEL statement related to a switch should never actually |
| * catch a continue abrupt completion at run-time. Hence an INVALID opcode can be placed in the |
| * continue slot of the switch's LABEL statement. |
| */ |
| |
| /* XXX: awkward, especially the bunch of separate output values -> output struct? */ |
| DUK_LOCAL void duk__lookup_active_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, duk_bool_t is_break, duk_int_t *out_label_id, duk_int_t *out_label_catch_depth, duk_int_t *out_label_pc, duk_bool_t *out_is_closest) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_uint8_t *p; |
| duk_labelinfo *li_start, *li_end, *li; |
| duk_bool_t match = 0; |
| |
| DUK_DDD(DUK_DDDPRINT("looking up active label: label='%!O', is_break=%ld", |
| (duk_heaphdr *) h_label, (long) is_break)); |
| |
| DUK_UNREF(ctx); |
| |
| p = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, comp_ctx->curr_func.h_labelinfos); |
| li_start = (duk_labelinfo *) (void *) p; |
| li_end = (duk_labelinfo *) (void *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos)); |
| li = li_end; |
| |
| /* Match labels starting from latest label because there can be duplicate empty |
| * labels in the label set. |
| */ |
| while (li > li_start) { |
| li--; |
| |
| if (li->h_label != h_label) { |
| DUK_DDD(DUK_DDDPRINT("labelinfo[%ld] ->'%!O' != %!O", |
| (long) (li - li_start), |
| (duk_heaphdr *) li->h_label, |
| (duk_heaphdr *) h_label)); |
| continue; |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("labelinfo[%ld] -> '%!O' label name matches (still need to check type)", |
| (long) (li - li_start), (duk_heaphdr *) h_label)); |
| |
| /* currently all labels accept a break, so no explicit check for it now */ |
| DUK_ASSERT(li->flags & DUK_LABEL_FLAG_ALLOW_BREAK); |
| |
| if (is_break) { |
| /* break matches always */ |
| match = 1; |
| break; |
| } else if (li->flags & DUK_LABEL_FLAG_ALLOW_CONTINUE) { |
| /* iteration statements allow continue */ |
| match = 1; |
| break; |
| } else { |
| /* continue matched this label -- we can only continue if this is the empty |
| * label, for which duplication is allowed, and thus there is hope of |
| * finding a match deeper in the label stack. |
| */ |
| if (h_label != DUK_HTHREAD_STRING_EMPTY_STRING(thr)) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_LABEL); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("continue matched an empty label which does not " |
| "allow a continue -> continue lookup deeper in label stack")); |
| } |
| } |
| } |
| /* XXX: match flag is awkward, rework */ |
| if (!match) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_LABEL); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("label match: %!O -> label_id %ld, catch_depth=%ld, pc_label=%ld", |
| (duk_heaphdr *) h_label, (long) li->label_id, |
| (long) li->catch_depth, (long) li->pc_label)); |
| |
| *out_label_id = li->label_id; |
| *out_label_catch_depth = li->catch_depth; |
| *out_label_pc = li->pc_label; |
| *out_is_closest = (li == li_end - 1); |
| } |
| |
| DUK_LOCAL void duk__reset_labels_to_length(duk_compiler_ctx *comp_ctx, duk_int_t len) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_size_t new_size; |
| |
| /* XXX: duk_set_length */ |
| new_size = sizeof(duk_labelinfo) * (duk_size_t) len; |
| duk_push_int(ctx, len); |
| duk_put_prop_stridx(ctx, comp_ctx->curr_func.labelnames_idx, DUK_STRIDX_LENGTH); |
| duk_hbuffer_resize(thr, comp_ctx->curr_func.h_labelinfos, new_size); |
| } |
| |
| /* |
| * Expression parsing: duk__expr_nud(), duk__expr_led(), duk__expr_lbp(), and helpers. |
| * |
| * - duk__expr_nud(): ("null denotation"): process prev_token as a "start" of an expression (e.g. literal) |
| * - duk__expr_led(): ("left denotation"): process prev_token in the "middle" of an expression (e.g. operator) |
| * - duk__expr_lbp(): ("left-binding power"): return left-binding power of curr_token |
| */ |
| |
| /* object literal key tracking flags */ |
| #define DUK__OBJ_LIT_KEY_PLAIN (1 << 0) /* key encountered as a plain property */ |
| #define DUK__OBJ_LIT_KEY_GET (1 << 1) /* key encountered as a getter */ |
| #define DUK__OBJ_LIT_KEY_SET (1 << 2) /* key encountered as a setter */ |
| |
| DUK_LOCAL void duk__nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_reg_t reg_obj; /* result reg */ |
| duk_reg_t reg_temp; /* temp reg */ |
| duk_reg_t temp_start; /* temp reg value for start of loop */ |
| duk_small_uint_t max_init_values; /* max # of values initialized in one MPUTARR set */ |
| duk_small_uint_t num_values; /* number of values in current MPUTARR set */ |
| duk_uarridx_t curr_idx; /* current (next) array index */ |
| duk_uarridx_t start_idx; /* start array index of current MPUTARR set */ |
| duk_uarridx_t init_idx; /* last array index explicitly initialized, +1 */ |
| duk_bool_t require_comma; /* next loop requires a comma */ |
| |
| /* DUK_TOK_LBRACKET already eaten, current token is right after that */ |
| DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LBRACKET); |
| |
| max_init_values = DUK__MAX_ARRAY_INIT_VALUES; /* XXX: depend on available temps? */ |
| |
| reg_obj = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_extraop_b_c(comp_ctx, |
| DUK_EXTRAOP_NEWARR | DUK__EMIT_FLAG_B_IS_TARGET, |
| reg_obj, |
| 0); /* XXX: patch initial size afterwards? */ |
| temp_start = DUK__GETTEMP(comp_ctx); |
| |
| /* |
| * Emit initializers in sets of maximum max_init_values. |
| * Corner cases such as single value initializers do not have |
| * special handling now. |
| * |
| * Elided elements must not be emitted as 'undefined' values, |
| * because such values would be enumerable (which is incorrect). |
| * Also note that trailing elisions must be reflected in the |
| * length of the final array but cause no elements to be actually |
| * inserted. |
| */ |
| |
| curr_idx = 0; |
| init_idx = 0; /* tracks maximum initialized index + 1 */ |
| start_idx = 0; |
| require_comma = 0; |
| |
| for (;;) { |
| num_values = 0; |
| DUK__SETTEMP(comp_ctx, temp_start); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_RBRACKET) { |
| break; |
| } |
| |
| for (;;) { |
| if (comp_ctx->curr_token.t == DUK_TOK_RBRACKET) { |
| /* the outer loop will recheck and exit */ |
| break; |
| } |
| |
| /* comma check */ |
| if (require_comma) { |
| if (comp_ctx->curr_token.t == DUK_TOK_COMMA) { |
| /* comma after a value, expected */ |
| duk__advance(comp_ctx); |
| require_comma = 0; |
| continue; |
| } else { |
| goto syntax_error; |
| } |
| } else { |
| if (comp_ctx->curr_token.t == DUK_TOK_COMMA) { |
| /* elision - flush */ |
| curr_idx++; |
| duk__advance(comp_ctx); |
| /* if num_values > 0, MPUTARR emitted by outer loop after break */ |
| break; |
| } |
| } |
| /* else an array initializer element */ |
| |
| /* initial index */ |
| if (num_values == 0) { |
| start_idx = curr_idx; |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_load_int32(comp_ctx, reg_temp, (duk_int32_t) start_idx); |
| } |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); /* alloc temp just in case, to update max temp */ |
| DUK__SETTEMP(comp_ctx, reg_temp); |
| duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/); |
| DUK__SETTEMP(comp_ctx, reg_temp + 1); |
| |
| num_values++; |
| curr_idx++; |
| require_comma = 1; |
| |
| if (num_values >= max_init_values) { |
| /* MPUTARR emitted by outer loop */ |
| break; |
| } |
| } |
| |
| if (num_values > 0) { |
| /* - A is a source register (it's not a write target, but used |
| * to identify the target object) but can be shuffled. |
| * - B cannot be shuffled normally because it identifies a range |
| * of registers, the emitter has special handling for this |
| * (the "no shuffle" flag must not be set). |
| * - C is a non-register number and cannot be shuffled, but |
| * never needs to be. |
| */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_MPUTARR | |
| DUK__EMIT_FLAG_NO_SHUFFLE_C | |
| DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) reg_obj, |
| (duk_regconst_t) temp_start, |
| (duk_regconst_t) num_values); |
| init_idx = start_idx + num_values; |
| |
| /* num_values and temp_start reset at top of outer loop */ |
| } |
| } |
| |
| DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RBRACKET); |
| duk__advance(comp_ctx); |
| |
| DUK_DDD(DUK_DDDPRINT("array literal done, curridx=%ld, initidx=%ld", |
| (long) curr_idx, (long) init_idx)); |
| |
| /* trailing elisions? */ |
| if (curr_idx > init_idx) { |
| /* yes, must set array length explicitly */ |
| DUK_DDD(DUK_DDDPRINT("array literal has trailing elisions which affect its length")); |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_load_int32(comp_ctx, reg_temp, (duk_int_t) curr_idx); |
| duk__emit_extraop_b_c(comp_ctx, |
| DUK_EXTRAOP_SETALEN, |
| (duk_regconst_t) reg_obj, |
| (duk_regconst_t) reg_temp); |
| } |
| |
| DUK__SETTEMP(comp_ctx, temp_start); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_obj; |
| return; |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_ARRAY_LITERAL); |
| } |
| |
| /* duplicate/invalid key checks; returns 1 if syntax error */ |
| DUK_LOCAL duk_bool_t duk__nud_object_literal_key_check(duk_compiler_ctx *comp_ctx, duk_small_uint_t new_key_flags) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_small_uint_t key_flags; |
| |
| /* [ ... key_obj key ] */ |
| |
| DUK_ASSERT(duk_is_string(ctx, -1)); |
| |
| /* |
| * 'key_obj' tracks keys encountered so far by associating an |
| * integer with flags with already encountered keys. The checks |
| * below implement E5 Section 11.1.5, step 4 for production: |
| * |
| * PropertyNameAndValueList: PropertyNameAndValueList , PropertyAssignment |
| */ |
| |
| duk_dup(ctx, -1); /* [ ... key_obj key key ] */ |
| duk_get_prop(ctx, -3); /* [ ... key_obj key val ] */ |
| key_flags = duk_to_int(ctx, -1); |
| duk_pop(ctx); /* [ ... key_obj key ] */ |
| |
| if (new_key_flags & DUK__OBJ_LIT_KEY_PLAIN) { |
| if ((key_flags & DUK__OBJ_LIT_KEY_PLAIN) && comp_ctx->curr_func.is_strict) { |
| /* step 4.a */ |
| DUK_DDD(DUK_DDDPRINT("duplicate key: plain key appears twice in strict mode")); |
| return 1; |
| } |
| if (key_flags & (DUK__OBJ_LIT_KEY_GET | DUK__OBJ_LIT_KEY_SET)) { |
| /* step 4.c */ |
| DUK_DDD(DUK_DDDPRINT("duplicate key: plain key encountered after setter/getter")); |
| return 1; |
| } |
| } else { |
| if (key_flags & DUK__OBJ_LIT_KEY_PLAIN) { |
| /* step 4.b */ |
| DUK_DDD(DUK_DDDPRINT("duplicate key: getter/setter encountered after plain key")); |
| return 1; |
| } |
| if (key_flags & new_key_flags) { |
| /* step 4.d */ |
| DUK_DDD(DUK_DDDPRINT("duplicate key: getter/setter encountered twice")); |
| return 1; |
| } |
| } |
| |
| new_key_flags |= key_flags; |
| DUK_DDD(DUK_DDDPRINT("setting/updating key %!T flags: 0x%08lx -> 0x%08lx", |
| (duk_tval *) duk_get_tval(ctx, -1), |
| (unsigned long) key_flags, |
| (unsigned long) new_key_flags)); |
| duk_dup(ctx, -1); |
| duk_push_int(ctx, new_key_flags); /* [ ... key_obj key key flags ] */ |
| duk_put_prop(ctx, -4); /* [ ... key_obj key ] */ |
| |
| return 0; |
| } |
| |
| DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_reg_t reg_obj; /* result reg */ |
| duk_reg_t reg_key; /* temp reg for key literal */ |
| duk_reg_t reg_temp; /* temp reg */ |
| duk_reg_t temp_start; /* temp reg value for start of loop */ |
| duk_small_uint_t max_init_pairs; /* max # of key-value pairs initialized in one MPUTOBJ set */ |
| duk_small_uint_t num_pairs; /* number of pairs in current MPUTOBJ set */ |
| duk_bool_t first; /* first value: comma must not precede the value */ |
| duk_bool_t is_set, is_get; /* temps */ |
| |
| DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LCURLY); |
| |
| max_init_pairs = DUK__MAX_OBJECT_INIT_PAIRS; /* XXX: depend on available temps? */ |
| |
| reg_obj = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_extraop_b_c(comp_ctx, |
| DUK_EXTRAOP_NEWOBJ | DUK__EMIT_FLAG_B_IS_TARGET, |
| reg_obj, |
| 0); /* XXX: patch initial size afterwards? */ |
| temp_start = DUK__GETTEMP(comp_ctx); |
| |
| /* temp object for tracking / detecting duplicate keys */ |
| duk_push_object(ctx); |
| |
| /* |
| * Emit initializers in sets of maximum max_init_pairs keys. |
| * Setter/getter is handled separately and terminates the |
| * current set of initializer values. Corner cases such as |
| * single value initializers do not have special handling now. |
| */ |
| |
| first = 1; |
| for (;;) { |
| num_pairs = 0; |
| DUK__SETTEMP(comp_ctx, temp_start); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) { |
| break; |
| } |
| |
| for (;;) { |
| /* |
| * Three possible element formats: |
| * 1) PropertyName : AssignmentExpression |
| * 2) get PropertyName () { FunctionBody } |
| * 3) set PropertyName ( PropertySetParameterList ) { FunctionBody } |
| * |
| * PropertyName can be IdentifierName (includes reserved words), a string |
| * literal, or a number literal. Note that IdentifierName allows 'get' and |
| * 'set' too, so we need to look ahead to the next token to distinguish: |
| * |
| * { get : 1 } |
| * |
| * and |
| * |
| * { get foo() { return 1 } } |
| * { get get() { return 1 } } // 'get' as getter propertyname |
| * |
| * Finally, a trailing comma is allowed. |
| * |
| * Key name is coerced to string at compile time (and ends up as a |
| * a string constant) even for numeric keys (e.g. "{1:'foo'}"). |
| * These could be emitted using e.g. LDINT, but that seems hardly |
| * worth the effort and would increase code size. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("object literal inner loop, curr_token->t = %ld", |
| (long) comp_ctx->curr_token.t)); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) { |
| /* the outer loop will recheck and exit */ |
| break; |
| } |
| if (num_pairs >= max_init_pairs) { |
| /* MPUTOBJ emitted by outer loop */ |
| break; |
| } |
| |
| if (first) { |
| first = 0; |
| } else { |
| if (comp_ctx->curr_token.t != DUK_TOK_COMMA) { |
| goto syntax_error; |
| } |
| duk__advance(comp_ctx); |
| if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) { |
| /* trailing comma followed by rcurly */ |
| break; |
| } |
| } |
| |
| /* advance to get one step of lookup */ |
| duk__advance(comp_ctx); |
| |
| /* NOTE: "get" and "set" are not officially ReservedWords and the lexer |
| * currently treats them always like ordinary identifiers (DUK_TOK_GET |
| * and DUK_TOK_SET are unused). They need to be detected based on the |
| * identifier string content. |
| */ |
| |
| is_get = (comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER && |
| comp_ctx->prev_token.str1 == DUK_HTHREAD_STRING_GET(thr)); |
| is_set = (comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER && |
| comp_ctx->prev_token.str1 == DUK_HTHREAD_STRING_SET(thr)); |
| if ((is_get || is_set) && comp_ctx->curr_token.t != DUK_TOK_COLON) { |
| /* getter/setter */ |
| duk_int_t fnum; |
| |
| if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER || |
| comp_ctx->curr_token.t_nores == DUK_TOK_STRING) { |
| /* same handling for identifiers and strings */ |
| DUK_ASSERT(comp_ctx->curr_token.str1 != NULL); |
| duk_push_hstring(ctx, comp_ctx->curr_token.str1); |
| } else if (comp_ctx->curr_token.t == DUK_TOK_NUMBER) { |
| duk_push_number(ctx, comp_ctx->curr_token.num); |
| duk_to_string(ctx, -1); |
| } else { |
| goto syntax_error; |
| } |
| |
| DUK_ASSERT(duk_is_string(ctx, -1)); |
| if (duk__nud_object_literal_key_check(comp_ctx, |
| (is_get ? DUK__OBJ_LIT_KEY_GET : DUK__OBJ_LIT_KEY_SET))) { |
| goto syntax_error; |
| } |
| reg_key = duk__getconst(comp_ctx); |
| |
| if (num_pairs > 0) { |
| /* - A is a source register (it's not a write target, but used |
| * to identify the target object) but can be shuffled. |
| * - B cannot be shuffled normally because it identifies a range |
| * of registers, the emitter has special handling for this |
| * (the "no shuffle" flag must not be set). |
| * - C is a non-register number and cannot be shuffled, but |
| * never needs to be. |
| */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_MPUTOBJ | |
| DUK__EMIT_FLAG_NO_SHUFFLE_C | |
| DUK__EMIT_FLAG_A_IS_SOURCE, |
| reg_obj, |
| temp_start, |
| num_pairs); |
| num_pairs = 0; |
| DUK__SETTEMP(comp_ctx, temp_start); |
| } |
| |
| /* curr_token = get/set name */ |
| fnum = duk__parse_func_like_fnum(comp_ctx, 0 /*is_decl*/, 1 /*is_setget*/); |
| |
| DUK_ASSERT(DUK__GETTEMP(comp_ctx) == temp_start); |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_LDCONST, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) reg_key); |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_CLOSURE, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) fnum); |
| |
| /* Slot C is used in a non-standard fashion (range of regs), |
| * emitter code has special handling for it (must not set the |
| * "no shuffle" flag). |
| */ |
| duk__emit_extraop_b_c(comp_ctx, |
| (is_get ? DUK_EXTRAOP_INITGET : DUK_EXTRAOP_INITSET), |
| reg_obj, |
| temp_start); /* temp_start+0 = key, temp_start+1 = closure */ |
| |
| DUK__SETTEMP(comp_ctx, temp_start); |
| } else { |
| /* normal key/value */ |
| if (comp_ctx->prev_token.t_nores == DUK_TOK_IDENTIFIER || |
| comp_ctx->prev_token.t_nores == DUK_TOK_STRING) { |
| /* same handling for identifiers and strings */ |
| DUK_ASSERT(comp_ctx->prev_token.str1 != NULL); |
| duk_push_hstring(ctx, comp_ctx->prev_token.str1); |
| } else if (comp_ctx->prev_token.t == DUK_TOK_NUMBER) { |
| duk_push_number(ctx, comp_ctx->prev_token.num); |
| duk_to_string(ctx, -1); |
| } else { |
| goto syntax_error; |
| } |
| |
| DUK_ASSERT(duk_is_string(ctx, -1)); |
| if (duk__nud_object_literal_key_check(comp_ctx, DUK__OBJ_LIT_KEY_PLAIN)) { |
| goto syntax_error; |
| } |
| reg_key = duk__getconst(comp_ctx); |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_LDCONST, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) reg_key); |
| duk__advance_expect(comp_ctx, DUK_TOK_COLON); |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); /* alloc temp just in case, to update max temp */ |
| DUK__SETTEMP(comp_ctx, reg_temp); |
| duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/); |
| DUK__SETTEMP(comp_ctx, reg_temp + 1); |
| |
| num_pairs++; |
| } |
| } |
| |
| if (num_pairs > 0) { |
| /* See MPUTOBJ comments above. */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_MPUTOBJ | |
| DUK__EMIT_FLAG_NO_SHUFFLE_C | |
| DUK__EMIT_FLAG_A_IS_SOURCE, |
| reg_obj, |
| temp_start, |
| num_pairs); |
| |
| /* num_pairs and temp_start reset at top of outer loop */ |
| } |
| } |
| |
| DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY); |
| duk__advance(comp_ctx); |
| |
| DUK__SETTEMP(comp_ctx, temp_start); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_obj; |
| |
| DUK_DDD(DUK_DDDPRINT("final tracking object: %!T", |
| (duk_tval *) duk_get_tval(ctx, -1))); |
| duk_pop(ctx); |
| return; |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_OBJECT_LITERAL); |
| } |
| |
| /* Parse argument list. Arguments are written to temps starting from |
| * "next temp". Returns number of arguments parsed. Expects left paren |
| * to be already eaten, and eats the right paren before returning. |
| */ |
| DUK_LOCAL duk_int_t duk__parse_arguments(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_int_t nargs = 0; |
| duk_reg_t reg_temp; |
| |
| /* Note: expect that caller has already eaten the left paren */ |
| |
| DUK_DDD(DUK_DDDPRINT("start parsing arguments, prev_token.t=%ld, curr_token.t=%ld", |
| (long) comp_ctx->prev_token.t, (long) comp_ctx->curr_token.t)); |
| |
| for (;;) { |
| if (comp_ctx->curr_token.t == DUK_TOK_RPAREN) { |
| break; |
| } |
| if (nargs > 0) { |
| duk__advance_expect(comp_ctx, DUK_TOK_COMMA); |
| } |
| |
| /* We want the argument expression value to go to "next temp" |
| * without additional moves. That should almost always be the |
| * case, but we double check after expression parsing. |
| * |
| * This is not the cleanest possible approach. |
| */ |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); /* bump up "allocated" reg count, just in case */ |
| DUK__SETTEMP(comp_ctx, reg_temp); |
| |
| /* binding power must be high enough to NOT allow comma expressions directly */ |
| duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp); /* always allow 'in', coerce to 'tr' just in case */ |
| |
| DUK__SETTEMP(comp_ctx, reg_temp + 1); |
| nargs++; |
| |
| DUK_DDD(DUK_DDDPRINT("argument #%ld written into reg %ld", (long) nargs, (long) reg_temp)); |
| } |
| |
| /* eat the right paren */ |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| DUK_DDD(DUK_DDDPRINT("end parsing arguments")); |
| |
| return nargs; |
| } |
| |
| DUK_LOCAL duk_bool_t duk__expr_is_empty(duk_compiler_ctx *comp_ctx) { |
| /* empty expressions can be detected conveniently with nud/led counts */ |
| return (comp_ctx->curr_func.nud_count == 0) && |
| (comp_ctx->curr_func.led_count == 0); |
| } |
| |
| DUK_LOCAL void duk__expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_token *tk; |
| duk_reg_t temp_at_entry; |
| duk_small_int_t tok; |
| duk_uint32_t args; /* temp variable to pass constants and flags to shared code */ |
| |
| /* |
| * ctx->prev_token token to process with duk__expr_nud() |
| * ctx->curr_token updated by caller |
| * |
| * Note: the token in the switch below has already been eaten. |
| */ |
| |
| temp_at_entry = DUK__GETTEMP(comp_ctx); |
| |
| comp_ctx->curr_func.nud_count++; |
| |
| tk = &comp_ctx->prev_token; |
| tok = tk->t; |
| res->t = DUK_IVAL_NONE; |
| |
| DUK_DDD(DUK_DDDPRINT("duk__expr_nud(), prev_token.t=%ld, allow_in=%ld, paren_level=%ld", |
| (long) tk->t, (long) comp_ctx->curr_func.allow_in, (long) comp_ctx->curr_func.paren_level)); |
| |
| switch (tok) { |
| |
| /* PRIMARY EXPRESSIONS */ |
| |
| case DUK_TOK_THIS: { |
| duk_reg_t reg_temp; |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_LDTHIS, |
| (duk_regconst_t) reg_temp); |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| } |
| case DUK_TOK_IDENTIFIER: { |
| res->t = DUK_IVAL_VAR; |
| res->x1.t = DUK_ISPEC_VALUE; |
| duk_push_hstring(ctx, tk->str1); |
| duk_replace(ctx, res->x1.valstack_idx); |
| return; |
| } |
| case DUK_TOK_NULL: { |
| duk_push_null(ctx); |
| goto plain_value; |
| } |
| case DUK_TOK_TRUE: { |
| duk_push_true(ctx); |
| goto plain_value; |
| } |
| case DUK_TOK_FALSE: { |
| duk_push_false(ctx); |
| goto plain_value; |
| } |
| case DUK_TOK_NUMBER: { |
| duk_push_number(ctx, tk->num); |
| goto plain_value; |
| } |
| case DUK_TOK_STRING: { |
| DUK_ASSERT(tk->str1 != NULL); |
| duk_push_hstring(ctx, tk->str1); |
| goto plain_value; |
| } |
| case DUK_TOK_REGEXP: { |
| #ifdef DUK_USE_REGEXP_SUPPORT |
| duk_reg_t reg_temp; |
| duk_regconst_t rc_re_bytecode; /* const */ |
| duk_regconst_t rc_re_source; /* const */ |
| |
| DUK_ASSERT(tk->str1 != NULL); |
| DUK_ASSERT(tk->str2 != NULL); |
| |
| DUK_DDD(DUK_DDDPRINT("emitting regexp op, str1=%!O, str2=%!O", |
| (duk_heaphdr *) tk->str1, |
| (duk_heaphdr *) tk->str2)); |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk_push_hstring(ctx, tk->str1); |
| duk_push_hstring(ctx, tk->str2); |
| |
| /* [ ... pattern flags ] */ |
| |
| duk_regexp_compile(thr); |
| |
| /* [ ... escaped_source bytecode ] */ |
| |
| rc_re_bytecode = duk__getconst(comp_ctx); |
| rc_re_source = duk__getconst(comp_ctx); |
| |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_REGEXP, |
| (duk_regconst_t) reg_temp /*a*/, |
| rc_re_bytecode /*b*/, |
| rc_re_source /*c*/); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| #else /* DUK_USE_REGEXP_SUPPORT */ |
| goto syntax_error; |
| #endif /* DUK_USE_REGEXP_SUPPORT */ |
| } |
| case DUK_TOK_LBRACKET: { |
| DUK_DDD(DUK_DDDPRINT("parsing array literal")); |
| duk__nud_array_literal(comp_ctx, res); |
| return; |
| } |
| case DUK_TOK_LCURLY: { |
| DUK_DDD(DUK_DDDPRINT("parsing object literal")); |
| duk__nud_object_literal(comp_ctx, res); |
| return; |
| } |
| case DUK_TOK_LPAREN: { |
| duk_bool_t prev_allow_in; |
| |
| comp_ctx->curr_func.paren_level++; |
| prev_allow_in = comp_ctx->curr_func.allow_in; |
| comp_ctx->curr_func.allow_in = 1; /* reset 'allow_in' for parenthesized expression */ |
| |
| duk__expr(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); /* Expression, terminates at a ')' */ |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| comp_ctx->curr_func.allow_in = prev_allow_in; |
| comp_ctx->curr_func.paren_level--; |
| return; |
| } |
| |
| /* MEMBER/NEW/CALL EXPRESSIONS */ |
| |
| case DUK_TOK_NEW: { |
| /* |
| * Parsing an expression starting with 'new' is tricky because |
| * there are multiple possible productions deriving from |
| * LeftHandSideExpression which begin with 'new'. |
| * |
| * We currently resort to one-token lookahead to distinguish the |
| * cases. Hopefully this is correct. The binding power must be |
| * such that parsing ends at an LPAREN (CallExpression) but not at |
| * a PERIOD or LBRACKET (MemberExpression). |
| * |
| * See doc/compiler.rst for discussion on the parsing approach, |
| * and testcases/test-dev-new.js for a bunch of documented tests. |
| */ |
| |
| duk_reg_t reg_target; |
| duk_int_t nargs; |
| |
| DUK_DDD(DUK_DDDPRINT("begin parsing new expression")); |
| |
| reg_target = DUK__ALLOCTEMP(comp_ctx); |
| duk__expr_toforcedreg(comp_ctx, res, DUK__BP_CALL /*rbp_flags*/, reg_target /*forced_reg*/); |
| DUK__SETTEMP(comp_ctx, reg_target + 1); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_LPAREN) { |
| /* 'new' MemberExpression Arguments */ |
| DUK_DDD(DUK_DDDPRINT("new expression has argument list")); |
| duk__advance(comp_ctx); |
| nargs = duk__parse_arguments(comp_ctx, res); /* parse args starting from "next temp", reg_target + 1 */ |
| /* right paren eaten */ |
| } else { |
| /* 'new' MemberExpression */ |
| DUK_DDD(DUK_DDDPRINT("new expression has no argument list")); |
| nargs = 0; |
| } |
| |
| /* Opcode slot C is used in a non-standard way, so shuffling |
| * is not allowed. |
| */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_NEW | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_C, |
| 0 /*unused*/, |
| reg_target /*target*/, |
| nargs /*num_args*/); |
| |
| DUK_DDD(DUK_DDDPRINT("end parsing new expression")); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_target; |
| return; |
| } |
| |
| /* FUNCTION EXPRESSIONS */ |
| |
| case DUK_TOK_FUNCTION: { |
| /* Function expression. Note that any statement beginning with 'function' |
| * is handled by the statement parser as a function declaration, or a |
| * non-standard function expression/statement (or a SyntaxError). We only |
| * handle actual function expressions (occurring inside an expression) here. |
| * |
| * O(depth^2) parse count for inner functions is handled by recording a |
| * lexer offset on the first compilation pass, so that the function can |
| * be efficiently skipped on the second pass. This is encapsulated into |
| * duk__parse_func_like_fnum(). |
| */ |
| |
| duk_reg_t reg_temp; |
| duk_int_t fnum; |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| |
| /* curr_token follows 'function' */ |
| fnum = duk__parse_func_like_fnum(comp_ctx, 0 /*is_decl*/, 0 /*is_setget*/); |
| DUK_DDD(DUK_DDDPRINT("parsed inner function -> fnum %ld", (long) fnum)); |
| |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_CLOSURE, |
| (duk_regconst_t) reg_temp /*a*/, |
| (duk_regconst_t) fnum /*bc*/); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| } |
| |
| /* UNARY EXPRESSIONS */ |
| |
| case DUK_TOK_DELETE: { |
| /* Delete semantics are a bit tricky. The description in E5 specification |
| * is kind of confusing, because it distinguishes between resolvability of |
| * a reference (which is only known at runtime) seemingly at compile time |
| * (= SyntaxError throwing). |
| */ |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| if (res->t == DUK_IVAL_VAR) { |
| /* not allowed in strict mode, regardless of whether resolves; |
| * in non-strict mode DELVAR handles both non-resolving and |
| * resolving cases (the specification description is a bit confusing). |
| */ |
| |
| duk_reg_t reg_temp; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| if (comp_ctx->curr_func.is_strict) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_CANNOT_DELETE_IDENTIFIER); |
| } |
| |
| DUK__SETTEMP(comp_ctx, temp_at_entry); |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| |
| duk_dup(ctx, res->x1.valstack_idx); |
| if (duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| /* register bound variables are non-configurable -> always false */ |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_LDFALSE, |
| (duk_regconst_t) reg_temp); |
| } else { |
| duk_dup(ctx, res->x1.valstack_idx); |
| rc_varname = duk__getconst(comp_ctx); |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_DELVAR, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) rc_varname); |
| } |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| } else if (res->t == DUK_IVAL_PROP) { |
| duk_reg_t reg_temp; |
| duk_reg_t reg_obj; |
| duk_regconst_t rc_key; |
| |
| DUK__SETTEMP(comp_ctx, temp_at_entry); |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| reg_obj = duk__ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*flags*/); /* don't allow const */ |
| rc_key = duk__ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/); |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_DELPROP, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) reg_obj, |
| rc_key); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| } else { |
| /* non-Reference deletion is always 'true', even in strict mode */ |
| duk_push_true(ctx); |
| goto plain_value; |
| } |
| return; |
| } |
| case DUK_TOK_VOID: { |
| duk__expr_toplain_ignore(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| duk_push_undefined(ctx); |
| goto plain_value; |
| } |
| case DUK_TOK_TYPEOF: { |
| /* 'typeof' must handle unresolvable references without throwing |
| * a ReferenceError (E5 Section 11.4.3). Register mapped values |
| * will never be unresolvable so special handling is only required |
| * when an identifier is a "slow path" one. |
| */ |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| |
| if (res->t == DUK_IVAL_VAR) { |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| duk_reg_t reg_temp; |
| |
| duk_dup(ctx, res->x1.valstack_idx); |
| if (!duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| DUK_DDD(DUK_DDDPRINT("typeof for an identifier name which could not be resolved " |
| "at compile time, need to use special run-time handling")); |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_extraop_b_c(comp_ctx, |
| DUK_EXTRAOP_TYPEOFID | DUK__EMIT_FLAG_B_IS_TARGET, |
| reg_temp, |
| rc_varname); |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| } |
| } |
| |
| args = (DUK_EXTRAOP_TYPEOF << 8) + 0; |
| goto unary_extraop; |
| } |
| case DUK_TOK_INCREMENT: { |
| args = (DUK_OP_PREINCR << 8) + 0; |
| goto preincdec; |
| } |
| case DUK_TOK_DECREMENT: { |
| args = (DUK_OP_PREDECR << 8) + 0; |
| goto preincdec; |
| } |
| case DUK_TOK_ADD: { |
| /* unary plus */ |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE && |
| duk_is_number(ctx, res->x1.valstack_idx)) { |
| /* unary plus of a number is identity */ |
| ; |
| return; |
| } |
| args = (DUK_EXTRAOP_UNP << 8) + 0; |
| goto unary_extraop; |
| } |
| case DUK_TOK_SUB: { |
| /* unary minus */ |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE && |
| duk_is_number(ctx, res->x1.valstack_idx)) { |
| /* this optimization is important to handle negative literals |
| * (which are not directly provided by the lexical grammar) |
| */ |
| duk_tval *tv_num; |
| duk_double_union du; |
| |
| tv_num = DUK_GET_TVAL_POSIDX(ctx, res->x1.valstack_idx); |
| DUK_ASSERT(tv_num != NULL); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_num)); |
| du.d = DUK_TVAL_GET_NUMBER(tv_num); |
| du.d = -du.d; |
| DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du); |
| DUK_TVAL_SET_NUMBER(tv_num, du.d); |
| return; |
| } |
| args = (DUK_EXTRAOP_UNM << 8) + 0; |
| goto unary_extraop; |
| } |
| case DUK_TOK_BNOT: { |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| args = (DUK_EXTRAOP_BNOT << 8) + 0; |
| goto unary_extraop; |
| } |
| case DUK_TOK_LNOT: { |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE) { |
| /* Very minimal inlining to handle common idioms '!0' and '!1', |
| * and also boolean arguments like '!false' and '!true'. |
| */ |
| duk_tval *tv_val; |
| |
| tv_val = DUK_GET_TVAL_POSIDX(ctx, res->x1.valstack_idx); |
| DUK_ASSERT(tv_val != NULL); |
| if (DUK_TVAL_IS_NUMBER(tv_val)) { |
| duk_double_t d; |
| d = DUK_TVAL_GET_NUMBER(tv_val); |
| if (d == 0.0) { |
| /* Matches both +0 and -0 on purpose. */ |
| DUK_DDD(DUK_DDDPRINT("inlined lnot: !0 -> true")); |
| DUK_TVAL_SET_BOOLEAN_TRUE(tv_val); |
| return; |
| } else if (d == 1.0) { |
| DUK_DDD(DUK_DDDPRINT("inlined lnot: !1 -> false")); |
| DUK_TVAL_SET_BOOLEAN_FALSE(tv_val); |
| return; |
| } |
| } else if (DUK_TVAL_IS_BOOLEAN(tv_val)) { |
| duk_small_int_t v; |
| v = DUK_TVAL_GET_BOOLEAN(tv_val); |
| DUK_DDD(DUK_DDDPRINT("inlined lnot boolean: %ld", (long) v)); |
| DUK_ASSERT(v == 0 || v == 1); |
| DUK_TVAL_SET_BOOLEAN(tv_val, v ^ 0x01); |
| return; |
| } |
| } |
| args = (DUK_EXTRAOP_LNOT << 8) + 0; |
| goto unary_extraop; |
| } |
| |
| } /* end switch */ |
| |
| DUK_ERROR_SYNTAX(thr, DUK_STR_PARSE_ERROR); |
| return; |
| |
| unary_extraop: |
| { |
| /* Note: must coerce to a (writable) temp register, so that e.g. "!x" where x |
| * is a reg-mapped variable works correctly (does not mutate the variable register). |
| */ |
| |
| duk_reg_t reg_temp; |
| reg_temp = duk__ivalue_toregconst_raw(comp_ctx, res, -1 /*forced_reg*/, DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/); |
| duk__emit_extraop_bc(comp_ctx, |
| (args >> 8), |
| (duk_regconst_t) reg_temp); |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| } |
| |
| preincdec: |
| { |
| /* preincrement and predecrement */ |
| duk_reg_t reg_res; |
| duk_small_uint_t args_op = args >> 8; |
| |
| /* Specific assumptions for opcode numbering. */ |
| DUK_ASSERT(DUK_OP_PREINCR + 4 == DUK_OP_PREINCV); |
| DUK_ASSERT(DUK_OP_PREDECR + 4 == DUK_OP_PREDECV); |
| DUK_ASSERT(DUK_OP_PREINCR + 8 == DUK_OP_PREINCP); |
| DUK_ASSERT(DUK_OP_PREDECR + 8 == DUK_OP_PREDECP); |
| |
| reg_res = DUK__ALLOCTEMP(comp_ctx); |
| |
| duk__expr(comp_ctx, res, DUK__BP_MULTIPLICATIVE /*rbp_flags*/); /* UnaryExpression */ |
| if (res->t == DUK_IVAL_VAR) { |
| duk_hstring *h_varname; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| h_varname = duk_get_hstring(ctx, res->x1.valstack_idx); |
| DUK_ASSERT(h_varname != NULL); |
| |
| if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) { |
| goto syntax_error; |
| } |
| |
| duk_dup(ctx, res->x1.valstack_idx); |
| if (duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| duk__emit_a_bc(comp_ctx, |
| args_op, /* e.g. DUK_OP_PREINCR */ |
| (duk_regconst_t) reg_res, |
| (duk_regconst_t) reg_varbind); |
| } else { |
| duk__emit_a_bc(comp_ctx, |
| args_op + 4, /* e.g. DUK_OP_PREINCV */ |
| (duk_regconst_t) reg_res, |
| rc_varname); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("preincdec to '%!O' -> reg_varbind=%ld, rc_varname=%ld", |
| (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname)); |
| } else if (res->t == DUK_IVAL_PROP) { |
| duk_reg_t reg_obj; /* allocate to reg only (not const) */ |
| duk_regconst_t rc_key; |
| reg_obj = duk__ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*flags*/); /* don't allow const */ |
| rc_key = duk__ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/); |
| duk__emit_a_b_c(comp_ctx, |
| args_op + 8, /* e.g. DUK_OP_PREINCP */ |
| (duk_regconst_t) reg_res, |
| (duk_regconst_t) reg_obj, |
| rc_key); |
| } else { |
| /* Technically return value is not needed because INVLHS will |
| * unconditially throw a ReferenceError. Coercion is necessary |
| * for proper semantics (consider ToNumber() called for an object). |
| * Use DUK_EXTRAOP_UNP with a dummy register to get ToNumber(). |
| */ |
| |
| duk__ivalue_toforcedreg(comp_ctx, res, reg_res); |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_UNP, |
| reg_res); /* for side effects, result ignored */ |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_INVLHS); |
| } |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_res; |
| DUK__SETTEMP(comp_ctx, reg_res + 1); |
| return; |
| } |
| |
| plain_value: |
| { |
| /* Stack top contains plain value */ |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_VALUE; |
| duk_replace(ctx, res->x1.valstack_idx); |
| return; |
| } |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_EXPRESSION); |
| } |
| |
| /* XXX: add flag to indicate whether caller cares about return value; this |
| * affects e.g. handling of assignment expressions. This change needs API |
| * changes elsewhere too. |
| */ |
| DUK_LOCAL void duk__expr_led(duk_compiler_ctx *comp_ctx, duk_ivalue *left, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_token *tk; |
| duk_small_int_t tok; |
| duk_uint32_t args; /* temp variable to pass constants and flags to shared code */ |
| |
| /* |
| * ctx->prev_token token to process with duk__expr_led() |
| * ctx->curr_token updated by caller |
| */ |
| |
| comp_ctx->curr_func.led_count++; |
| |
| /* The token in the switch has already been eaten here */ |
| tk = &comp_ctx->prev_token; |
| tok = tk->t; |
| |
| DUK_DDD(DUK_DDDPRINT("duk__expr_led(), prev_token.t=%ld, allow_in=%ld, paren_level=%ld", |
| (long) tk->t, (long) comp_ctx->curr_func.allow_in, (long) comp_ctx->curr_func.paren_level)); |
| |
| /* XXX: default priority for infix operators is duk__expr_lbp(tok) -> get it here? */ |
| |
| switch (tok) { |
| |
| /* PRIMARY EXPRESSIONS */ |
| |
| case DUK_TOK_PERIOD: { |
| /* Property access expressions are critical for correct LHS ordering, |
| * see comments in duk__expr()! |
| * |
| * A conservative approach would be to use duk__ivalue_totempconst() |
| * for 'left'. However, allowing a reg-bound variable seems safe here |
| * and is nice because "foo.bar" is a common expression. If the ivalue |
| * is used in an expression a GETPROP will occur before any changes to |
| * the base value can occur. If the ivalue is used as an assignment |
| * LHS, the assignment code will ensure the base value is safe from |
| * RHS mutation. |
| */ |
| |
| /* XXX: This now coerces an identifier into a GETVAR to a temp, which |
| * causes an extra LDREG in call setup. It's sufficient to coerce to a |
| * unary ivalue? |
| */ |
| duk__ivalue_toplain(comp_ctx, left); |
| |
| /* NB: must accept reserved words as property name */ |
| if (comp_ctx->curr_token.t_nores != DUK_TOK_IDENTIFIER) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_EXPECTED_IDENTIFIER); |
| } |
| |
| res->t = DUK_IVAL_PROP; |
| duk__copy_ispec(comp_ctx, &left->x1, &res->x1); /* left.x1 -> res.x1 */ |
| DUK_ASSERT(comp_ctx->curr_token.str1 != NULL); |
| duk_push_hstring(ctx, comp_ctx->curr_token.str1); |
| duk_replace(ctx, res->x2.valstack_idx); |
| res->x2.t = DUK_ISPEC_VALUE; |
| |
| /* special RegExp literal handling after IdentifierName */ |
| comp_ctx->curr_func.reject_regexp_in_adv = 1; |
| |
| duk__advance(comp_ctx); |
| return; |
| } |
| case DUK_TOK_LBRACKET: { |
| /* Property access expressions are critical for correct LHS ordering, |
| * see comments in duk__expr()! |
| */ |
| |
| /* XXX: optimize temp reg use */ |
| /* XXX: similar coercion issue as in DUK_TOK_PERIOD */ |
| /* XXX: coerce to regs? it might be better for enumeration use, where the |
| * same PROP ivalue is used multiple times. Or perhaps coerce PROP further |
| * there? |
| */ |
| /* XXX: for simple cases like x['y'] an unnecessary LDREG is |
| * emitted for the base value; could avoid it if we knew that |
| * the key expression is safe (e.g. just a single literal). |
| */ |
| |
| /* The 'left' value must not be a register bound variable |
| * because it may be mutated during the rest of the expression |
| * and E5.1 Section 11.2.1 specifies the order of evaluation |
| * so that the base value is evaluated first. |
| * See: test-bug-nested-prop-mutate.js. |
| */ |
| duk__ivalue_totempconst(comp_ctx, left); |
| duk__expr_toplain(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); /* Expression, ']' terminates */ |
| duk__advance_expect(comp_ctx, DUK_TOK_RBRACKET); |
| |
| res->t = DUK_IVAL_PROP; |
| duk__copy_ispec(comp_ctx, &res->x1, &res->x2); /* res.x1 -> res.x2 */ |
| duk__copy_ispec(comp_ctx, &left->x1, &res->x1); /* left.x1 -> res.x1 */ |
| return; |
| } |
| case DUK_TOK_LPAREN: { |
| /* function call */ |
| duk_reg_t reg_cs = DUK__ALLOCTEMPS(comp_ctx, 2); |
| duk_int_t nargs; |
| duk_small_uint_t call_flags = 0; |
| |
| /* |
| * XXX: attempt to get the call result to "next temp" whenever |
| * possible to avoid unnecessary register shuffles. |
| * |
| * XXX: CSPROP (and CSREG) can overwrite the call target register, and save one temp, |
| * if the call target is a temporary register and at the top of the temp reg "stack". |
| */ |
| |
| /* |
| * Setup call: target and 'this' binding. Three cases: |
| * |
| * 1. Identifier base (e.g. "foo()") |
| * 2. Property base (e.g. "foo.bar()") |
| * 3. Register base (e.g. "foo()()"; i.e. when a return value is a function) |
| */ |
| |
| if (left->t == DUK_IVAL_VAR) { |
| duk_hstring *h_varname; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| DUK_DDD(DUK_DDDPRINT("function call with identifier base")); |
| |
| h_varname = duk_get_hstring(ctx, left->x1.valstack_idx); |
| DUK_ASSERT(h_varname != NULL); |
| if (h_varname == DUK_HTHREAD_STRING_EVAL(thr)) { |
| /* Potential direct eval call detected, flag the CALL |
| * so that a run-time "direct eval" check is made and |
| * special behavior may be triggered. Note that this |
| * does not prevent 'eval' from being register bound. |
| */ |
| DUK_DDD(DUK_DDDPRINT("function call with identifier 'eval' " |
| "-> enabling EVALCALL flag, marking function " |
| "as may_direct_eval")); |
| call_flags |= DUK_BC_CALL_FLAG_EVALCALL; |
| |
| comp_ctx->curr_func.may_direct_eval = 1; |
| } |
| |
| duk_dup(ctx, left->x1.valstack_idx); |
| if (duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_CSREG, |
| (duk_regconst_t) (reg_cs + 0), |
| (duk_regconst_t) reg_varbind); |
| } else { |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_CSVAR, |
| (duk_regconst_t) (reg_cs + 0), |
| rc_varname); |
| } |
| } else if (left->t == DUK_IVAL_PROP) { |
| DUK_DDD(DUK_DDDPRINT("function call with property base")); |
| |
| duk__ispec_toforcedreg(comp_ctx, &left->x1, reg_cs + 0); /* base */ |
| duk__ispec_toforcedreg(comp_ctx, &left->x2, reg_cs + 1); /* key */ |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_CSPROP, |
| (duk_regconst_t) (reg_cs + 0), |
| (duk_regconst_t) (reg_cs + 0), |
| (duk_regconst_t) (reg_cs + 1)); /* in-place setup */ |
| } else { |
| DUK_DDD(DUK_DDDPRINT("function call with register base")); |
| |
| duk__ivalue_toforcedreg(comp_ctx, left, reg_cs + 0); |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_CSREG, |
| (duk_regconst_t) (reg_cs + 0), |
| (duk_regconst_t) (reg_cs + 0)); /* in-place setup */ |
| } |
| |
| DUK__SETTEMP(comp_ctx, reg_cs + 2); |
| nargs = duk__parse_arguments(comp_ctx, res); /* parse args starting from "next temp" */ |
| |
| /* Tailcalls are handled by back-patching the TAILCALL flag to the |
| * already emitted instruction later (in return statement parser). |
| * Since A and C have a special meaning here, they cannot be "shuffled". |
| */ |
| |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_CALL | DUK__EMIT_FLAG_NO_SHUFFLE_A | DUK__EMIT_FLAG_NO_SHUFFLE_C, |
| (duk_regconst_t) call_flags /*flags*/, |
| (duk_regconst_t) reg_cs /*basereg*/, |
| (duk_regconst_t) nargs /*numargs*/); |
| DUK__SETTEMP(comp_ctx, reg_cs + 1); /* result in csreg */ |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_cs; |
| return; |
| } |
| |
| /* POSTFIX EXPRESSION */ |
| |
| case DUK_TOK_INCREMENT: { |
| args = (DUK_OP_POSTINCR << 8) + 0; |
| goto postincdec; |
| } |
| case DUK_TOK_DECREMENT: { |
| args = (DUK_OP_POSTDECR << 8) + 0; |
| goto postincdec; |
| } |
| |
| /* MULTIPLICATIVE EXPRESSION */ |
| |
| case DUK_TOK_MUL: { |
| args = (DUK_OP_MUL << 8) + DUK__BP_MULTIPLICATIVE; /* UnaryExpression */ |
| goto binary; |
| } |
| case DUK_TOK_DIV: { |
| args = (DUK_OP_DIV << 8) + DUK__BP_MULTIPLICATIVE; /* UnaryExpression */ |
| goto binary; |
| } |
| case DUK_TOK_MOD: { |
| args = (DUK_OP_MOD << 8) + DUK__BP_MULTIPLICATIVE; /* UnaryExpression */ |
| goto binary; |
| } |
| |
| /* ADDITIVE EXPRESSION */ |
| |
| case DUK_TOK_ADD: { |
| args = (DUK_OP_ADD << 8) + DUK__BP_ADDITIVE; /* MultiplicativeExpression */ |
| goto binary; |
| } |
| case DUK_TOK_SUB: { |
| args = (DUK_OP_SUB << 8) + DUK__BP_ADDITIVE; /* MultiplicativeExpression */ |
| goto binary; |
| } |
| |
| /* SHIFT EXPRESSION */ |
| |
| case DUK_TOK_ALSHIFT: { |
| /* << */ |
| args = (DUK_OP_BASL << 8) + DUK__BP_SHIFT; |
| goto binary; |
| } |
| case DUK_TOK_ARSHIFT: { |
| /* >> */ |
| args = (DUK_OP_BASR << 8) + DUK__BP_SHIFT; |
| goto binary; |
| } |
| case DUK_TOK_RSHIFT: { |
| /* >>> */ |
| args = (DUK_OP_BLSR << 8) + DUK__BP_SHIFT; |
| goto binary; |
| } |
| |
| /* RELATIONAL EXPRESSION */ |
| |
| case DUK_TOK_LT: { |
| /* < */ |
| args = (DUK_OP_LT << 8) + DUK__BP_RELATIONAL; |
| goto binary; |
| } |
| case DUK_TOK_GT: { |
| args = (DUK_OP_GT << 8) + DUK__BP_RELATIONAL; |
| goto binary; |
| } |
| case DUK_TOK_LE: { |
| args = (DUK_OP_LE << 8) + DUK__BP_RELATIONAL; |
| goto binary; |
| } |
| case DUK_TOK_GE: { |
| args = (DUK_OP_GE << 8) + DUK__BP_RELATIONAL; |
| goto binary; |
| } |
| case DUK_TOK_INSTANCEOF: { |
| args = (1 << 16 /*is_extra*/) + (DUK_EXTRAOP_INSTOF << 8) + DUK__BP_RELATIONAL; |
| goto binary; |
| } |
| case DUK_TOK_IN: { |
| args = (1 << 16 /*is_extra*/) + (DUK_EXTRAOP_IN << 8) + DUK__BP_RELATIONAL; |
| goto binary; |
| } |
| |
| /* EQUALITY EXPRESSION */ |
| |
| case DUK_TOK_EQ: { |
| args = (DUK_OP_EQ << 8) + DUK__BP_EQUALITY; |
| goto binary; |
| } |
| case DUK_TOK_NEQ: { |
| args = (DUK_OP_NEQ << 8) + DUK__BP_EQUALITY; |
| goto binary; |
| } |
| case DUK_TOK_SEQ: { |
| args = (DUK_OP_SEQ << 8) + DUK__BP_EQUALITY; |
| goto binary; |
| } |
| case DUK_TOK_SNEQ: { |
| args = (DUK_OP_SNEQ << 8) + DUK__BP_EQUALITY; |
| goto binary; |
| } |
| |
| /* BITWISE EXPRESSIONS */ |
| |
| case DUK_TOK_BAND: { |
| args = (DUK_OP_BAND << 8) + DUK__BP_BAND; |
| goto binary; |
| } |
| case DUK_TOK_BXOR: { |
| args = (DUK_OP_BXOR << 8) + DUK__BP_BXOR; |
| goto binary; |
| } |
| case DUK_TOK_BOR: { |
| args = (DUK_OP_BOR << 8) + DUK__BP_BOR; |
| goto binary; |
| } |
| |
| /* LOGICAL EXPRESSIONS */ |
| |
| case DUK_TOK_LAND: { |
| /* syntactically left-associative but parsed as right-associative */ |
| args = (1 << 8) + DUK__BP_LAND - 1; |
| goto binary_logical; |
| } |
| case DUK_TOK_LOR: { |
| /* syntactically left-associative but parsed as right-associative */ |
| args = (0 << 8) + DUK__BP_LOR - 1; |
| goto binary_logical; |
| } |
| |
| /* CONDITIONAL EXPRESSION */ |
| |
| case DUK_TOK_QUESTION: { |
| /* XXX: common reg allocation need is to reuse a sub-expression's temp reg, |
| * but only if it really is a temp. Nothing fancy here now. |
| */ |
| duk_reg_t reg_temp; |
| duk_int_t pc_jump1; |
| duk_int_t pc_jump2; |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__ivalue_toforcedreg(comp_ctx, left, reg_temp); |
| duk__emit_if_true_skip(comp_ctx, reg_temp); |
| pc_jump1 = duk__emit_jump_empty(comp_ctx); /* jump to false */ |
| duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/); /* AssignmentExpression */ |
| duk__advance_expect(comp_ctx, DUK_TOK_COLON); |
| pc_jump2 = duk__emit_jump_empty(comp_ctx); /* jump to end */ |
| duk__patch_jump_here(comp_ctx, pc_jump1); |
| duk__expr_toforcedreg(comp_ctx, res, DUK__BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/); /* AssignmentExpression */ |
| duk__patch_jump_here(comp_ctx, pc_jump2); |
| |
| DUK__SETTEMP(comp_ctx, reg_temp + 1); |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| } |
| |
| /* ASSIGNMENT EXPRESSION */ |
| |
| case DUK_TOK_EQUALSIGN: { |
| /* |
| * Assignments are right associative, allows e.g. |
| * a = 5; |
| * a += b = 9; // same as a += (b = 9) |
| * -> expression value 14, a = 14, b = 9 |
| * |
| * Right associativiness is reflected in the BP for recursion, |
| * "-1" ensures assignment operations are allowed. |
| * |
| * XXX: just use DUK__BP_COMMA (i.e. no need for 2-step bp levels)? |
| */ |
| args = (DUK_OP_NONE << 8) + DUK__BP_ASSIGNMENT - 1; /* DUK_OP_NONE marks a 'plain' assignment */ |
| goto assign; |
| } |
| case DUK_TOK_ADD_EQ: { |
| /* right associative */ |
| args = (DUK_OP_ADD << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_SUB_EQ: { |
| /* right associative */ |
| args = (DUK_OP_SUB << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_MUL_EQ: { |
| /* right associative */ |
| args = (DUK_OP_MUL << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_DIV_EQ: { |
| /* right associative */ |
| args = (DUK_OP_DIV << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_MOD_EQ: { |
| /* right associative */ |
| args = (DUK_OP_MOD << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_ALSHIFT_EQ: { |
| /* right associative */ |
| args = (DUK_OP_BASL << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_ARSHIFT_EQ: { |
| /* right associative */ |
| args = (DUK_OP_BASR << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_RSHIFT_EQ: { |
| /* right associative */ |
| args = (DUK_OP_BLSR << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_BAND_EQ: { |
| /* right associative */ |
| args = (DUK_OP_BAND << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_BOR_EQ: { |
| /* right associative */ |
| args = (DUK_OP_BOR << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| case DUK_TOK_BXOR_EQ: { |
| /* right associative */ |
| args = (DUK_OP_BXOR << 8) + DUK__BP_ASSIGNMENT - 1; |
| goto assign; |
| } |
| |
| /* COMMA */ |
| |
| case DUK_TOK_COMMA: { |
| /* right associative */ |
| |
| duk__ivalue_toplain_ignore(comp_ctx, left); /* need side effects, not value */ |
| duk__expr_toplain(comp_ctx, res, DUK__BP_COMMA - 1 /*rbp_flags*/); |
| |
| /* return 'res' (of right part) as our result */ |
| return; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| |
| DUK_D(DUK_DPRINT("parse error: unexpected token: %ld", (long) tok)); |
| DUK_ERROR_SYNTAX(thr, DUK_STR_PARSE_ERROR); |
| return; |
| |
| #if 0 |
| /* XXX: shared handling for 'duk__expr_lhs'? */ |
| if (comp_ctx->curr_func.paren_level == 0 && XXX) { |
| comp_ctx->curr_func.duk__expr_lhs = 0; |
| } |
| #endif |
| |
| binary: |
| /* |
| * Shared handling of binary operations |
| * |
| * args = (is_extraop << 16) + (opcode << 8) + rbp |
| */ |
| { |
| duk__ivalue_toplain(comp_ctx, left); |
| duk__expr_toplain(comp_ctx, res, args & 0xff /*rbp_flags*/); |
| |
| /* combine left->x1 and res->x1 (right->x1, really) -> (left->x1 OP res->x1) */ |
| DUK_ASSERT(left->t == DUK_IVAL_PLAIN); |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN); |
| |
| res->t = (args >> 16) ? DUK_IVAL_ARITH_EXTRAOP : DUK_IVAL_ARITH; |
| res->op = (args >> 8) & 0xff; |
| |
| res->x2.t = res->x1.t; |
| res->x2.regconst = res->x1.regconst; |
| duk_copy(ctx, res->x1.valstack_idx, res->x2.valstack_idx); |
| |
| res->x1.t = left->x1.t; |
| res->x1.regconst = left->x1.regconst; |
| duk_copy(ctx, left->x1.valstack_idx, res->x1.valstack_idx); |
| |
| DUK_DDD(DUK_DDDPRINT("binary op, res: t=%ld, x1.t=%ld, x1.regconst=0x%08lx, x2.t=%ld, x2.regconst=0x%08lx", |
| (long) res->t, (long) res->x1.t, (unsigned long) res->x1.regconst, (long) res->x2.t, (unsigned long) res->x2.regconst)); |
| return; |
| } |
| |
| binary_logical: |
| /* |
| * Shared handling for logical AND and logical OR. |
| * |
| * args = (truthval << 8) + rbp |
| * |
| * Truthval determines when to skip right-hand-side. |
| * For logical AND truthval=1, for logical OR truthval=0. |
| * |
| * See doc/compiler.rst for discussion on compiling logical |
| * AND and OR expressions. The approach here is very simplistic, |
| * generating extra jumps and multiple evaluations of truth values, |
| * but generates code on-the-fly with only local back-patching. |
| * |
| * Both logical AND and OR are syntactically left-associated. |
| * However, logical ANDs are compiled as right associative |
| * expressions, i.e. "A && B && C" as "A && (B && C)", to allow |
| * skip jumps to skip over the entire tail. Similarly for logical OR. |
| */ |
| |
| { |
| duk_reg_t reg_temp; |
| duk_int_t pc_jump; |
| duk_small_uint_t args_truthval = args >> 8; |
| duk_small_uint_t args_rbp = args & 0xff; |
| |
| /* XXX: unoptimal use of temps, resetting */ |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| |
| duk__ivalue_toforcedreg(comp_ctx, left, reg_temp); |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_IF | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) args_truthval, |
| (duk_regconst_t) reg_temp); /* skip jump conditionally */ |
| pc_jump = duk__emit_jump_empty(comp_ctx); |
| duk__expr_toforcedreg(comp_ctx, res, args_rbp /*rbp_flags*/, reg_temp /*forced_reg*/); |
| duk__patch_jump_here(comp_ctx, pc_jump); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| return; |
| } |
| |
| assign: |
| /* |
| * Shared assignment expression handling |
| * |
| * args = (opcode << 8) + rbp |
| * |
| * If 'opcode' is DUK_OP_NONE, plain assignment without arithmetic. |
| * Syntactically valid left-hand-side forms which are not accepted as |
| * left-hand-side values (e.g. as in "f() = 1") must NOT cause a |
| * SyntaxError, but rather a run-time ReferenceError. |
| * |
| * When evaluating X <op>= Y, the LHS (X) is conceptually evaluated |
| * to a temporary first. The RHS is then evaluated. Finally, the |
| * <op> is applied to the initial value of RHS (not the value after |
| * RHS evaluation), and written to X. Doing so concretely generates |
| * inefficient code so we'd like to avoid the temporary when possible. |
| * See: https://github.com/svaarala/duktape/pull/992. |
| * |
| * The expression value (final LHS value, written to RHS) is |
| * conceptually copied into a fresh temporary so that it won't |
| * change even if the LHS/RHS values change in outer expressions. |
| * For example, it'd be generally incorrect for the expression value |
| * to be the RHS register binding, unless there's a guarantee that it |
| * won't change during further expression evaluation. Using the |
| * temporary concretely produces inefficient bytecode, so we try to |
| * avoid the extra temporary for some known-to-be-safe cases. |
| * Currently the only safe case we detect is a "top level assignment", |
| * for example "x = y + z;", where the assignment expression value is |
| * ignored. |
| * See: test-dev-assign-expr.js and test-bug-assign-mutate-gh381.js. |
| */ |
| |
| { |
| duk_small_uint_t args_op = args >> 8; |
| duk_small_uint_t args_rbp = args & 0xff; |
| duk_bool_t toplevel_assign; |
| |
| /* XXX: here we need to know if 'left' is left-hand-side compatible. |
| * That information is no longer available from current expr parsing |
| * state; it would need to be carried into the 'left' ivalue or by |
| * some other means. |
| */ |
| |
| /* A top-level assignment is e.g. "x = y;". For these it's safe |
| * to use the RHS as-is as the expression value, even if the RHS |
| * is a reg-bound identifier. The RHS ('res') is right associative |
| * so it has consumed all other assignment level operations; the |
| * only relevant lower binding power construct is comma operator |
| * which will ignore the expression value provided here. Usually |
| * the top level assignment expression value is ignored, but it |
| * is relevant for e.g. eval code. |
| */ |
| toplevel_assign = (comp_ctx->curr_func.nud_count == 1 && /* one token before */ |
| comp_ctx->curr_func.led_count == 1); /* one operator (= assign) */ |
| DUK_DDD(DUK_DDDPRINT("assignment: nud_count=%ld, led_count=%ld, toplevel_assign=%ld", |
| (long) comp_ctx->curr_func.nud_count, |
| (long) comp_ctx->curr_func.led_count, |
| (long) toplevel_assign)); |
| |
| if (left->t == DUK_IVAL_VAR) { |
| duk_hstring *h_varname; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| DUK_ASSERT(left->x1.t == DUK_ISPEC_VALUE); /* LHS is already side effect free */ |
| |
| h_varname = duk_get_hstring(ctx, left->x1.valstack_idx); |
| DUK_ASSERT(h_varname != NULL); |
| if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) { |
| /* E5 Section 11.13.1 (and others for other assignments), step 4. */ |
| goto syntax_error_lvalue; |
| } |
| duk_dup(ctx, left->x1.valstack_idx); |
| (void) duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname); |
| |
| if (args_op == DUK_OP_NONE) { |
| duk__expr(comp_ctx, res, args_rbp /*rbp_flags*/); |
| if (toplevel_assign) { |
| /* Any 'res' will do. */ |
| DUK_DDD(DUK_DDDPRINT("plain assignment, toplevel assign, use as is")); |
| } else { |
| /* 'res' must be a plain ivalue, and not register-bound variable. */ |
| DUK_DDD(DUK_DDDPRINT("plain assignment, not toplevel assign, ensure not a reg-bound identifier")); |
| if (res->t != DUK_IVAL_PLAIN || (res->x1.t == DUK_ISPEC_REGCONST && |
| (res->x1.regconst & DUK__CONST_MARKER) == 0 && |
| !DUK__ISTEMP(comp_ctx, res->x1.regconst))) { |
| duk__ivalue_totempconst(comp_ctx, res); |
| } |
| } |
| } else { |
| /* For X <op>= Y we need to evaluate the pre-op |
| * value of X before evaluating the RHS: the RHS |
| * can change X, but when we do <op> we must use |
| * the pre-op value. |
| */ |
| duk_reg_t reg_temp; |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| |
| if (reg_varbind >= 0) { |
| duk_reg_t reg_res; |
| duk_reg_t reg_src; |
| duk_int_t pc_temp_load; |
| duk_int_t pc_before_rhs; |
| duk_int_t pc_after_rhs; |
| |
| if (toplevel_assign) { |
| /* 'reg_varbind' is the operation result and can also |
| * become the expression value for top level assignments |
| * such as: "var x; x += y;". |
| */ |
| DUK_DD(DUK_DDPRINT("<op>= expression is top level, write directly to reg_varbind")); |
| reg_res = reg_varbind; |
| } else { |
| /* Not safe to use 'reg_varbind' as assignment expression |
| * value, so go through a temp. |
| */ |
| DUK_DD(DUK_DDPRINT("<op>= expression is not top level, write to reg_temp")); |
| reg_res = reg_temp; /* reg_res should be smallest possible */ |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| } |
| |
| /* Try to optimize X <op>= Y for reg-bound |
| * variables. Detect side-effect free RHS |
| * narrowly by seeing whether it emits code. |
| * If not, rewind the code emitter and overwrite |
| * the unnecessary temp reg load. |
| */ |
| |
| pc_temp_load = duk__get_current_pc(comp_ctx); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_LDREG, |
| (duk_regconst_t) reg_temp, |
| reg_varbind); |
| |
| pc_before_rhs = duk__get_current_pc(comp_ctx); |
| duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/); |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST); |
| pc_after_rhs = duk__get_current_pc(comp_ctx); |
| |
| DUK_DD(DUK_DDPRINT("pc_temp_load=%ld, pc_before_rhs=%ld, pc_after_rhs=%ld", |
| (long) pc_temp_load, (long) pc_before_rhs, |
| (long) pc_after_rhs)); |
| |
| if (pc_after_rhs == pc_before_rhs) { |
| /* Note: if the reg_temp load generated shuffling |
| * instructions, we may need to rewind more than |
| * one instruction, so use explicit PC computation. |
| */ |
| DUK_DD(DUK_DDPRINT("rhs is side effect free, rewind and avoid unnecessary temp for reg-based <op>=")); |
| DUK_BW_ADD_PTR(comp_ctx->thr, &comp_ctx->curr_func.bw_code, (pc_temp_load - pc_before_rhs) * sizeof(duk_compiler_instr)); |
| reg_src = reg_varbind; |
| } else { |
| DUK_DD(DUK_DDPRINT("rhs evaluation emitted code, not sure if rhs is side effect free; use temp reg for LHS")); |
| reg_src = reg_temp; |
| } |
| |
| duk__emit_a_b_c(comp_ctx, |
| args_op, |
| (duk_regconst_t) reg_res, |
| (duk_regconst_t) reg_src, |
| res->x1.regconst); |
| |
| res->x1.regconst = (duk_regconst_t) reg_res; |
| |
| /* Ensure compact use of temps. */ |
| if (DUK__ISTEMP(comp_ctx, reg_res)) { |
| DUK__SETTEMP(comp_ctx, reg_res + 1); |
| } |
| } else { |
| /* When LHS is not register bound, always go through a |
| * temporary. No optimization for top level assignment. |
| */ |
| |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_GETVAR, |
| (duk_regconst_t) reg_temp, |
| rc_varname); |
| |
| duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/); |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST); |
| |
| duk__emit_a_b_c(comp_ctx, |
| args_op, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) reg_temp, |
| res->x1.regconst); |
| res->x1.regconst = (duk_regconst_t) reg_temp; |
| } |
| |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST); |
| } |
| |
| /* At this point 'res' holds the potential expression value. |
| * It can be basically any ivalue here, including a reg-bound |
| * identifier (if code above deems it safe) or a unary/binary |
| * operation. Operations must be resolved to a side effect free |
| * plain value, and the side effects must happen exactly once. |
| */ |
| |
| if (reg_varbind >= 0) { |
| if (res->t != DUK_IVAL_PLAIN) { |
| /* Resolve 'res' directly into the LHS binding, and use |
| * that as the expression value if safe. If not safe, |
| * resolve to a temp/const and copy to LHS. |
| */ |
| if (toplevel_assign) { |
| duk__ivalue_toforcedreg(comp_ctx, res, (duk_int_t) reg_varbind); |
| } else { |
| duk__ivalue_totempconst(comp_ctx, res); |
| duk__copy_ivalue(comp_ctx, res, left); /* use 'left' as a temp */ |
| duk__ivalue_toforcedreg(comp_ctx, left, (duk_int_t) reg_varbind); |
| } |
| } else { |
| /* Use 'res' as the expression value (it's side effect |
| * free and may be a plain value, a register, or a |
| * constant) and write it to the LHS binding too. |
| */ |
| duk__copy_ivalue(comp_ctx, res, left); /* use 'left' as a temp */ |
| duk__ivalue_toforcedreg(comp_ctx, left, (duk_int_t) reg_varbind); |
| } |
| } else { |
| /* Only a reg fits into 'A' so coerce 'res' into a register |
| * for PUTVAR. |
| * |
| * XXX: here the current A/B/C split is suboptimal: we could |
| * just use 9 bits for reg_res (and support constants) and 17 |
| * instead of 18 bits for the varname const index. |
| */ |
| |
| duk__ivalue_toreg(comp_ctx, res); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE, |
| res->x1.regconst, |
| rc_varname); |
| } |
| |
| /* 'res' contains expression value */ |
| } else if (left->t == DUK_IVAL_PROP) { |
| /* E5 Section 11.13.1 (and others) step 4 never matches for prop writes -> no check */ |
| duk_reg_t reg_obj; |
| duk_regconst_t rc_key; |
| duk_regconst_t rc_res; |
| duk_reg_t reg_temp; |
| |
| /* Property access expressions ('a[b]') are critical to correct |
| * LHS evaluation ordering, see test-dev-assign-eval-order*.js. |
| * We must make sure that the LHS target slot (base object and |
| * key) don't change during RHS evaluation. The only concrete |
| * problem is a register reference to a variable-bound register |
| * (i.e., non-temp). Require temp regs for both key and base. |
| * |
| * Don't allow a constant for the object (even for a number |
| * etc), as it goes into the 'A' field of the opcode. |
| */ |
| |
| reg_obj = duk__ispec_toregconst_raw(comp_ctx, |
| &left->x1, |
| -1 /*forced_reg*/, |
| DUK__IVAL_FLAG_REQUIRE_TEMP /*flags*/); |
| |
| rc_key = duk__ispec_toregconst_raw(comp_ctx, |
| &left->x2, |
| -1 /*forced_reg*/, |
| DUK__IVAL_FLAG_REQUIRE_TEMP | DUK__IVAL_FLAG_ALLOW_CONST /*flags*/); |
| |
| /* Evaluate RHS only when LHS is safe. */ |
| |
| if (args_op == DUK_OP_NONE) { |
| duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/); |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST); |
| rc_res = res->x1.regconst; |
| } else { |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_GETPROP, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) reg_obj, |
| rc_key); |
| |
| duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/); |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST); |
| |
| duk__emit_a_b_c(comp_ctx, |
| args_op, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) reg_temp, |
| res->x1.regconst); |
| rc_res = (duk_regconst_t) reg_temp; |
| } |
| |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_PUTPROP | DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) reg_obj, |
| rc_key, |
| rc_res); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = rc_res; |
| } else { |
| /* No support for lvalues returned from new or function call expressions. |
| * However, these must NOT cause compile-time SyntaxErrors, but run-time |
| * ReferenceErrors. Both left and right sides of the assignment must be |
| * evaluated before throwing a ReferenceError. For instance: |
| * |
| * f() = g(); |
| * |
| * must result in f() being evaluated, then g() being evaluated, and |
| * finally, a ReferenceError being thrown. See E5 Section 11.13.1. |
| */ |
| |
| duk_regconst_t rc_res; |
| |
| /* First evaluate LHS fully to ensure all side effects are out. */ |
| duk__ivalue_toplain_ignore(comp_ctx, left); |
| |
| /* Then evaluate RHS fully (its value becomes the expression value too). |
| * Technically we'd need the side effect safety check here too, but because |
| * we always throw using INVLHS the result doesn't matter. |
| */ |
| rc_res = duk__expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/); |
| |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_INVLHS); |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = rc_res; |
| } |
| |
| return; |
| } |
| |
| postincdec: |
| { |
| /* |
| * Post-increment/decrement will return the original value as its |
| * result value. However, even that value will be coerced using |
| * ToNumber() which is quite awkward. Specific bytecode opcodes |
| * are used to handle these semantics. |
| * |
| * Note that post increment/decrement has a "no LineTerminator here" |
| * restriction. This is handled by duk__expr_lbp(), which forcibly terminates |
| * the previous expression if a LineTerminator occurs before '++'/'--'. |
| */ |
| |
| duk_reg_t reg_res; |
| duk_small_uint_t args_op = args >> 8; |
| |
| /* Specific assumptions for opcode numbering. */ |
| DUK_ASSERT(DUK_OP_POSTINCR + 4 == DUK_OP_POSTINCV); |
| DUK_ASSERT(DUK_OP_POSTDECR + 4 == DUK_OP_POSTDECV); |
| DUK_ASSERT(DUK_OP_POSTINCR + 8 == DUK_OP_POSTINCP); |
| DUK_ASSERT(DUK_OP_POSTDECR + 8 == DUK_OP_POSTDECP); |
| |
| reg_res = DUK__ALLOCTEMP(comp_ctx); |
| |
| if (left->t == DUK_IVAL_VAR) { |
| duk_hstring *h_varname; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| h_varname = duk_get_hstring(ctx, left->x1.valstack_idx); |
| DUK_ASSERT(h_varname != NULL); |
| |
| if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) { |
| goto syntax_error; |
| } |
| |
| duk_dup(ctx, left->x1.valstack_idx); |
| if (duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| duk__emit_a_bc(comp_ctx, |
| args_op, /* e.g. DUK_OP_POSTINCR */ |
| (duk_regconst_t) reg_res, |
| (duk_regconst_t) reg_varbind); |
| } else { |
| duk__emit_a_bc(comp_ctx, |
| args_op + 4, /* e.g. DUK_OP_POSTINCV */ |
| (duk_regconst_t) reg_res, |
| rc_varname); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("postincdec to '%!O' -> reg_varbind=%ld, rc_varname=%ld", |
| (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname)); |
| } else if (left->t == DUK_IVAL_PROP) { |
| duk_reg_t reg_obj; /* allocate to reg only (not const) */ |
| duk_regconst_t rc_key; |
| |
| reg_obj = duk__ispec_toregconst_raw(comp_ctx, &left->x1, -1 /*forced_reg*/, 0 /*flags*/); /* don't allow const */ |
| rc_key = duk__ispec_toregconst_raw(comp_ctx, &left->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/); |
| duk__emit_a_b_c(comp_ctx, |
| args_op + 8, /* e.g. DUK_OP_POSTINCP */ |
| (duk_regconst_t) reg_res, |
| (duk_regconst_t) reg_obj, |
| rc_key); |
| } else { |
| /* Technically return value is not needed because INVLHS will |
| * unconditially throw a ReferenceError. Coercion is necessary |
| * for proper semantics (consider ToNumber() called for an object). |
| * Use DUK_EXTRAOP_UNP with a dummy register to get ToNumber(). |
| */ |
| duk__ivalue_toforcedreg(comp_ctx, left, reg_res); |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_UNP, |
| reg_res); /* for side effects, result ignored */ |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_INVLHS); |
| } |
| |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_REGCONST; |
| res->x1.regconst = (duk_regconst_t) reg_res; |
| DUK__SETTEMP(comp_ctx, reg_res + 1); |
| return; |
| } |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_EXPRESSION); |
| return; |
| |
| syntax_error_lvalue: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_LVALUE); |
| return; |
| } |
| |
| DUK_LOCAL duk_small_uint_t duk__expr_lbp(duk_compiler_ctx *comp_ctx) { |
| duk_small_int_t tok = comp_ctx->curr_token.t; |
| |
| DUK_ASSERT(tok >= DUK_TOK_MINVAL && tok <= DUK_TOK_MAXVAL); |
| DUK_ASSERT(sizeof(duk__token_lbp) == DUK_TOK_MAXVAL + 1); |
| |
| /* XXX: integrate support for this into led() instead? |
| * Similar issue as post-increment/post-decrement. |
| */ |
| |
| /* prevent duk__expr_led() by using a binding power less than anything valid */ |
| if (tok == DUK_TOK_IN && !comp_ctx->curr_func.allow_in) { |
| return 0; |
| } |
| |
| if ((tok == DUK_TOK_DECREMENT || tok == DUK_TOK_INCREMENT) && |
| (comp_ctx->curr_token.lineterm)) { |
| /* '++' or '--' in a post-increment/decrement position, |
| * and a LineTerminator occurs between the operator and |
| * the preceding expression. Force the previous expr |
| * to terminate, in effect treating e.g. "a,b\n++" as |
| * "a,b;++" (= SyntaxError). |
| */ |
| return 0; |
| } |
| |
| return DUK__TOKEN_LBP_GET_BP(duk__token_lbp[tok]); /* format is bit packed */ |
| } |
| |
| /* |
| * Expression parsing. |
| * |
| * Upon entry to 'expr' and its variants, 'curr_tok' is assumed to be the |
| * first token of the expression. Upon exit, 'curr_tok' will be the first |
| * token not part of the expression (e.g. semicolon terminating an expression |
| * statement). |
| */ |
| |
| #define DUK__EXPR_RBP_MASK 0xff |
| #define DUK__EXPR_FLAG_REJECT_IN (1 << 8) /* reject 'in' token (used for for-in) */ |
| #define DUK__EXPR_FLAG_ALLOW_EMPTY (1 << 9) /* allow empty expression */ |
| #define DUK__EXPR_FLAG_REQUIRE_INIT (1 << 10) /* require initializer for var/const */ |
| |
| /* main expression parser function */ |
| DUK_LOCAL void duk__expr(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_ivalue tmp_alloc; /* 'res' is used for "left", and 'tmp' for "right" */ |
| duk_ivalue *tmp = &tmp_alloc; |
| duk_small_uint_t rbp; |
| |
| DUK__RECURSION_INCREASE(comp_ctx, thr); |
| |
| duk_require_stack(ctx, DUK__PARSE_EXPR_SLOTS); |
| |
| /* filter out flags from exprtop rbp_flags here to save space */ |
| rbp = rbp_flags & DUK__EXPR_RBP_MASK; |
| |
| DUK_DDD(DUK_DDDPRINT("duk__expr(), rbp_flags=%ld, rbp=%ld, allow_in=%ld, paren_level=%ld", |
| (long) rbp_flags, (long) rbp, (long) comp_ctx->curr_func.allow_in, |
| (long) comp_ctx->curr_func.paren_level)); |
| |
| DUK_MEMZERO(&tmp_alloc, sizeof(tmp_alloc)); |
| tmp->x1.valstack_idx = duk_get_top(ctx); |
| tmp->x2.valstack_idx = tmp->x1.valstack_idx + 1; |
| duk_push_undefined(ctx); |
| duk_push_undefined(ctx); |
| |
| /* XXX: where to release temp regs in intermediate expressions? |
| * e.g. 1+2+3 -> don't inflate temp register count when parsing this. |
| * that particular expression temp regs can be forced here. |
| */ |
| |
| /* XXX: increase ctx->expr_tokens here for every consumed token |
| * (this would be a nice statistic)? |
| */ |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON || comp_ctx->curr_token.t == DUK_TOK_RPAREN) { |
| /* XXX: possibly incorrect handling of empty expression */ |
| DUK_DDD(DUK_DDDPRINT("empty expression")); |
| if (!(rbp_flags & DUK__EXPR_FLAG_ALLOW_EMPTY)) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_EMPTY_EXPR_NOT_ALLOWED); |
| } |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_VALUE; |
| duk_push_undefined(ctx); |
| duk_replace(ctx, res->x1.valstack_idx); |
| goto cleanup; |
| } |
| |
| duk__advance(comp_ctx); |
| duk__expr_nud(comp_ctx, res); /* reuse 'res' as 'left' */ |
| while (rbp < duk__expr_lbp(comp_ctx)) { |
| duk__advance(comp_ctx); |
| duk__expr_led(comp_ctx, res, tmp); |
| duk__copy_ivalue(comp_ctx, tmp, res); /* tmp -> res */ |
| } |
| |
| cleanup: |
| /* final result is already in 'res' */ |
| |
| duk_pop_2(ctx); |
| |
| DUK__RECURSION_DECREASE(comp_ctx, thr); |
| } |
| |
| DUK_LOCAL void duk__exprtop(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk_hthread *thr = comp_ctx->thr; |
| |
| /* Note: these variables must reside in 'curr_func' instead of the global |
| * context: when parsing function expressions, expression parsing is nested. |
| */ |
| comp_ctx->curr_func.nud_count = 0; |
| comp_ctx->curr_func.led_count = 0; |
| comp_ctx->curr_func.paren_level = 0; |
| comp_ctx->curr_func.expr_lhs = 1; |
| comp_ctx->curr_func.allow_in = (rbp_flags & DUK__EXPR_FLAG_REJECT_IN ? 0 : 1); |
| |
| duk__expr(comp_ctx, res, rbp_flags); |
| |
| if (!(rbp_flags & DUK__EXPR_FLAG_ALLOW_EMPTY) && duk__expr_is_empty(comp_ctx)) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_EMPTY_EXPR_NOT_ALLOWED); |
| } |
| } |
| |
| /* A bunch of helpers (for size optimization) that combine duk__expr()/duk__exprtop() |
| * and result conversions. |
| * |
| * Each helper needs at least 2-3 calls to make it worth while to wrap. |
| */ |
| |
| #if 0 /* unused */ |
| DUK_LOCAL duk_reg_t duk__expr_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__expr(comp_ctx, res, rbp_flags); |
| return duk__ivalue_toreg(comp_ctx, res); |
| } |
| #endif |
| |
| #if 0 /* unused */ |
| DUK_LOCAL duk_reg_t duk__expr_totemp(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__expr(comp_ctx, res, rbp_flags); |
| return duk__ivalue_totemp(comp_ctx, res); |
| } |
| #endif |
| |
| DUK_LOCAL void duk__expr_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg) { |
| DUK_ASSERT(forced_reg >= 0); |
| duk__expr(comp_ctx, res, rbp_flags); |
| duk__ivalue_toforcedreg(comp_ctx, res, forced_reg); |
| } |
| |
| DUK_LOCAL duk_regconst_t duk__expr_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__expr(comp_ctx, res, rbp_flags); |
| return duk__ivalue_toregconst(comp_ctx, res); |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL duk_regconst_t duk__expr_totempconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__expr(comp_ctx, res, rbp_flags); |
| return duk__ivalue_totempconst(comp_ctx, res); |
| } |
| #endif |
| |
| DUK_LOCAL void duk__expr_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__expr(comp_ctx, res, rbp_flags); |
| duk__ivalue_toplain(comp_ctx, res); |
| } |
| |
| DUK_LOCAL void duk__expr_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__expr(comp_ctx, res, rbp_flags); |
| duk__ivalue_toplain_ignore(comp_ctx, res); |
| } |
| |
| DUK_LOCAL duk_reg_t duk__exprtop_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__exprtop(comp_ctx, res, rbp_flags); |
| return duk__ivalue_toreg(comp_ctx, res); |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL duk_reg_t duk__exprtop_totemp(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__exprtop(comp_ctx, res, rbp_flags); |
| return duk__ivalue_totemp(comp_ctx, res); |
| } |
| #endif |
| |
| DUK_LOCAL void duk__exprtop_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags, duk_reg_t forced_reg) { |
| DUK_ASSERT(forced_reg >= 0); |
| duk__exprtop(comp_ctx, res, rbp_flags); |
| duk__ivalue_toforcedreg(comp_ctx, res, forced_reg); |
| } |
| |
| DUK_LOCAL duk_regconst_t duk__exprtop_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t rbp_flags) { |
| duk__exprtop(comp_ctx, res, rbp_flags); |
| return duk__ivalue_toregconst(comp_ctx, res); |
| } |
| |
| #if 0 /* unused */ |
| DUK_LOCAL void duk__exprtop_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) { |
| duk__exprtop(comp_ctx, res, rbp_flags); |
| duk__ivalue_toplain_ignore(comp_ctx, res); |
| } |
| #endif |
| |
| /* |
| * Parse an individual source element (top level statement) or a statement. |
| * |
| * Handles labeled statements automatically (peeling away labels before |
| * parsing an expression that follows the label(s)). |
| * |
| * Upon entry, 'curr_tok' contains the first token of the statement (parsed |
| * in "allow regexp literal" mode). Upon exit, 'curr_tok' contains the first |
| * token following the statement (if the statement has a terminator, this is |
| * the token after the terminator). |
| */ |
| |
| #ifdef DUK__HAS_VAL |
| #undef DUK__HAS_VAL |
| #endif |
| #ifdef DUK__HAS_TERM |
| #undef DUK__HAS_TERM |
| #endif |
| #ifdef DUK__ALLOW_AUTO_SEMI_ALWAYS |
| #undef DUK__ALLOW_AUTO_SEMI_ALWAYS |
| #endif |
| #ifdef DUK__STILL_PROLOGUE |
| #undef DUK__STILL_PROLOGUE |
| #endif |
| #ifdef DUK__IS_TERMINAL |
| #undef DUK__IS_TERMINAL |
| #endif |
| |
| #define DUK__HAS_VAL (1 << 0) /* stmt has non-empty value */ |
| #define DUK__HAS_TERM (1 << 1) /* stmt has explicit/implicit semicolon terminator */ |
| #define DUK__ALLOW_AUTO_SEMI_ALWAYS (1 << 2) /* allow automatic semicolon even without lineterm (compatibility) */ |
| #define DUK__STILL_PROLOGUE (1 << 3) /* statement does not terminate directive prologue */ |
| #define DUK__IS_TERMINAL (1 << 4) /* statement is guaranteed to be terminal (control doesn't flow to next statement) */ |
| |
| /* Parse a single variable declaration (e.g. "i" or "i=10"). A leading 'var' |
| * has already been eaten. These is no return value in 'res', it is used only |
| * as a temporary. |
| * |
| * When called from 'for-in' statement parser, the initializer expression must |
| * not allow the 'in' token. The caller supply additional expression parsing |
| * flags (like DUK__EXPR_FLAG_REJECT_IN) in 'expr_flags'. |
| * |
| * Finally, out_rc_varname and out_reg_varbind are updated to reflect where |
| * the identifier is bound: |
| * |
| * If register bound: out_reg_varbind >= 0, out_rc_varname == 0 (ignore) |
| * If not register bound: out_reg_varbind < 0, out_rc_varname >= 0 |
| * |
| * These allow the caller to use the variable for further assignment, e.g. |
| * as is done in 'for-in' parsing. |
| */ |
| |
| DUK_LOCAL void duk__parse_var_decl(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t expr_flags, duk_reg_t *out_reg_varbind, duk_regconst_t *out_rc_varname) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_hstring *h_varname; |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| /* assume 'var' has been eaten */ |
| |
| /* Note: Identifier rejects reserved words */ |
| if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) { |
| goto syntax_error; |
| } |
| h_varname = comp_ctx->curr_token.str1; |
| |
| DUK_ASSERT(h_varname != NULL); |
| |
| /* strict mode restrictions (E5 Section 12.2.1) */ |
| if (duk__hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) { |
| goto syntax_error; |
| } |
| |
| /* register declarations in first pass */ |
| if (comp_ctx->curr_func.in_scanning) { |
| duk_uarridx_t n; |
| DUK_DDD(DUK_DDDPRINT("register variable declaration %!O in pass 1", |
| (duk_heaphdr *) h_varname)); |
| n = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.decls_idx); |
| duk_push_hstring(ctx, h_varname); |
| duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n); |
| duk_push_int(ctx, DUK_DECL_TYPE_VAR + (0 << 8)); |
| duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n + 1); |
| } |
| |
| duk_push_hstring(ctx, h_varname); /* push before advancing to keep reachable */ |
| |
| /* register binding lookup is based on varmap (even in first pass) */ |
| duk_dup_top(ctx); |
| (void) duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname); |
| |
| duk__advance(comp_ctx); /* eat identifier */ |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_EQUALSIGN) { |
| duk__advance(comp_ctx); |
| |
| DUK_DDD(DUK_DDDPRINT("vardecl, assign to '%!O' -> reg_varbind=%ld, rc_varname=%ld", |
| (duk_heaphdr *) h_varname, (long) reg_varbind, (long) rc_varname)); |
| |
| duk__exprtop(comp_ctx, res, DUK__BP_COMMA | expr_flags /*rbp_flags*/); /* AssignmentExpression */ |
| |
| if (reg_varbind >= 0) { |
| duk__ivalue_toforcedreg(comp_ctx, res, reg_varbind); |
| } else { |
| duk_reg_t reg_val; |
| reg_val = duk__ivalue_toreg(comp_ctx, res); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) reg_val, |
| rc_varname); |
| } |
| } else { |
| if (expr_flags & DUK__EXPR_FLAG_REQUIRE_INIT) { |
| /* Used for minimal 'const': initializer required. */ |
| goto syntax_error; |
| } |
| } |
| |
| duk_pop(ctx); /* pop varname */ |
| |
| *out_rc_varname = rc_varname; |
| *out_reg_varbind = reg_varbind; |
| |
| return; |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_VAR_DECLARATION); |
| } |
| |
| DUK_LOCAL void duk__parse_var_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_small_uint_t expr_flags) { |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| duk__advance(comp_ctx); /* eat 'var' */ |
| |
| for (;;) { |
| /* rc_varname and reg_varbind are ignored here */ |
| duk__parse_var_decl(comp_ctx, res, 0 | expr_flags, ®_varbind, &rc_varname); |
| |
| if (comp_ctx->curr_token.t != DUK_TOK_COMMA) { |
| break; |
| } |
| duk__advance(comp_ctx); |
| } |
| } |
| |
| DUK_LOCAL void duk__parse_for_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_int_t pc_v34_lhs; /* start variant 3/4 left-hand-side code (L1 in doc/compiler.rst example) */ |
| duk_reg_t temp_reset; /* knock back "next temp" to this whenever possible */ |
| duk_reg_t reg_temps; /* preallocated temporaries (2) for variants 3 and 4 */ |
| |
| DUK_DDD(DUK_DDDPRINT("start parsing a for/for-in statement")); |
| |
| /* Two temporaries are preallocated here for variants 3 and 4 which need |
| * registers which are never clobbered by expressions in the loop |
| * (concretely: for the enumerator object and the next enumerated value). |
| * Variants 1 and 2 "release" these temps. |
| */ |
| |
| reg_temps = DUK__ALLOCTEMPS(comp_ctx, 2); |
| |
| temp_reset = DUK__GETTEMP(comp_ctx); |
| |
| /* |
| * For/for-in main variants are: |
| * |
| * 1. for (ExpressionNoIn_opt; Expression_opt; Expression_opt) Statement |
| * 2. for (var VariableDeclarationNoIn; Expression_opt; Expression_opt) Statement |
| * 3. for (LeftHandSideExpression in Expression) Statement |
| * 4. for (var VariableDeclarationNoIn in Expression) Statement |
| * |
| * Parsing these without arbitrary lookahead or backtracking is relatively |
| * tricky but we manage to do so for now. |
| * |
| * See doc/compiler.rst for a detailed discussion of control flow |
| * issues, evaluation order issues, etc. |
| */ |
| |
| duk__advance(comp_ctx); /* eat 'for' */ |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| |
| DUK_DDD(DUK_DDDPRINT("detecting for/for-in loop variant, pc=%ld", (long) duk__get_current_pc(comp_ctx))); |
| |
| /* a label site has been emitted by duk__parse_stmt() automatically |
| * (it will also emit the ENDLABEL). |
| */ |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_VAR) { |
| /* |
| * Variant 2 or 4 |
| */ |
| |
| duk_reg_t reg_varbind; /* variable binding register if register-bound (otherwise < 0) */ |
| duk_regconst_t rc_varname; /* variable name reg/const, if variable not register-bound */ |
| |
| duk__advance(comp_ctx); /* eat 'var' */ |
| duk__parse_var_decl(comp_ctx, res, DUK__EXPR_FLAG_REJECT_IN, ®_varbind, &rc_varname); |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_IN) { |
| /* |
| * Variant 4 |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("detected for variant 4: for (var VariableDeclarationNoIn in Expression) Statement")); |
| pc_v34_lhs = duk__get_current_pc(comp_ctx); /* jump is inserted here */ |
| if (reg_varbind >= 0) { |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_LDREG, |
| (duk_regconst_t) reg_varbind, |
| (duk_regconst_t) (reg_temps + 0)); |
| } else { |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) (reg_temps + 0), |
| rc_varname); |
| } |
| goto parse_3_or_4; |
| } else { |
| /* |
| * Variant 2 |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("detected for variant 2: for (var VariableDeclarationNoIn; Expression_opt; Expression_opt) Statement")); |
| for (;;) { |
| /* more initializers */ |
| if (comp_ctx->curr_token.t != DUK_TOK_COMMA) { |
| break; |
| } |
| DUK_DDD(DUK_DDDPRINT("variant 2 has another variable initializer")); |
| |
| duk__advance(comp_ctx); /* eat comma */ |
| duk__parse_var_decl(comp_ctx, res, DUK__EXPR_FLAG_REJECT_IN, ®_varbind, &rc_varname); |
| } |
| goto parse_1_or_2; |
| } |
| } else { |
| /* |
| * Variant 1 or 3 |
| */ |
| |
| pc_v34_lhs = duk__get_current_pc(comp_ctx); /* jump is inserted here (variant 3) */ |
| |
| /* Note that duk__exprtop() here can clobber any reg above current temp_next, |
| * so any loop variables (e.g. enumerator) must be "preallocated". |
| */ |
| |
| /* don't coerce yet to a plain value (variant 3 needs special handling) */ |
| duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR | DUK__EXPR_FLAG_REJECT_IN | DUK__EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/); /* Expression */ |
| if (comp_ctx->curr_token.t == DUK_TOK_IN) { |
| /* |
| * Variant 3 |
| */ |
| |
| /* XXX: need to determine LHS type, and check that it is LHS compatible */ |
| DUK_DDD(DUK_DDDPRINT("detected for variant 3: for (LeftHandSideExpression in Expression) Statement")); |
| if (duk__expr_is_empty(comp_ctx)) { |
| goto syntax_error; /* LeftHandSideExpression does not allow empty expression */ |
| } |
| |
| if (res->t == DUK_IVAL_VAR) { |
| duk_reg_t reg_varbind; |
| duk_regconst_t rc_varname; |
| |
| duk_dup(ctx, res->x1.valstack_idx); |
| if (duk__lookup_lhs(comp_ctx, ®_varbind, &rc_varname)) { |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_LDREG, |
| (duk_regconst_t) reg_varbind, |
| (duk_regconst_t) (reg_temps + 0)); |
| } else { |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) (reg_temps + 0), |
| rc_varname); |
| } |
| } else if (res->t == DUK_IVAL_PROP) { |
| /* Don't allow a constant for the object (even for a number etc), as |
| * it goes into the 'A' field of the opcode. |
| */ |
| duk_reg_t reg_obj; |
| duk_regconst_t rc_key; |
| reg_obj = duk__ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*flags*/); /* don't allow const */ |
| rc_key = duk__ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, DUK__IVAL_FLAG_ALLOW_CONST /*flags*/); |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_PUTPROP | DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) reg_obj, |
| rc_key, |
| (duk_regconst_t) (reg_temps + 0)); |
| } else { |
| duk__ivalue_toplain_ignore(comp_ctx, res); /* just in case */ |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_INVLHS); |
| } |
| goto parse_3_or_4; |
| } else { |
| /* |
| * Variant 1 |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("detected for variant 1: for (ExpressionNoIn_opt; Expression_opt; Expression_opt) Statement")); |
| duk__ivalue_toplain_ignore(comp_ctx, res); |
| goto parse_1_or_2; |
| } |
| } |
| |
| parse_1_or_2: |
| /* |
| * Parse variant 1 or 2. The first part expression (which differs |
| * in the variants) has already been parsed and its code emitted. |
| * |
| * reg_temps + 0: unused |
| * reg_temps + 1: unused |
| */ |
| { |
| duk_regconst_t rc_cond; |
| duk_int_t pc_l1, pc_l2, pc_l3, pc_l4; |
| duk_int_t pc_jumpto_l3, pc_jumpto_l4; |
| duk_bool_t expr_c_empty; |
| |
| DUK_DDD(DUK_DDDPRINT("shared code for parsing variants 1 and 2")); |
| |
| /* "release" preallocated temps since we won't need them */ |
| temp_reset = reg_temps + 0; |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_SEMICOLON); |
| |
| pc_l1 = duk__get_current_pc(comp_ctx); |
| duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR | DUK__EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/); /* Expression_opt */ |
| if (duk__expr_is_empty(comp_ctx)) { |
| /* no need to coerce */ |
| pc_jumpto_l3 = duk__emit_jump_empty(comp_ctx); /* to body */ |
| pc_jumpto_l4 = -1; /* omitted */ |
| } else { |
| rc_cond = duk__ivalue_toregconst(comp_ctx, res); |
| duk__emit_if_false_skip(comp_ctx, rc_cond); |
| pc_jumpto_l3 = duk__emit_jump_empty(comp_ctx); /* to body */ |
| pc_jumpto_l4 = duk__emit_jump_empty(comp_ctx); /* to exit */ |
| } |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_SEMICOLON); |
| |
| pc_l2 = duk__get_current_pc(comp_ctx); |
| duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR | DUK__EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/); /* Expression_opt */ |
| if (duk__expr_is_empty(comp_ctx)) { |
| /* no need to coerce */ |
| expr_c_empty = 1; |
| /* JUMP L1 omitted */ |
| } else { |
| duk__ivalue_toplain_ignore(comp_ctx, res); |
| expr_c_empty = 0; |
| duk__emit_jump(comp_ctx, pc_l1); |
| } |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| pc_l3 = duk__get_current_pc(comp_ctx); |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| if (expr_c_empty) { |
| duk__emit_jump(comp_ctx, pc_l1); |
| } else { |
| duk__emit_jump(comp_ctx, pc_l2); |
| } |
| /* temp reset is not necessary after duk__parse_stmt(), which already does it */ |
| |
| pc_l4 = duk__get_current_pc(comp_ctx); |
| |
| DUK_DDD(DUK_DDDPRINT("patching jumps: jumpto_l3: %ld->%ld, jumpto_l4: %ld->%ld, " |
| "break: %ld->%ld, continue: %ld->%ld", |
| (long) pc_jumpto_l3, (long) pc_l3, (long) pc_jumpto_l4, (long) pc_l4, |
| (long) (pc_label_site + 1), (long) pc_l4, (long) (pc_label_site + 2), (long) pc_l2)); |
| |
| duk__patch_jump(comp_ctx, pc_jumpto_l3, pc_l3); |
| duk__patch_jump(comp_ctx, pc_jumpto_l4, pc_l4); |
| duk__patch_jump(comp_ctx, |
| pc_label_site + 1, |
| pc_l4); /* break jump */ |
| duk__patch_jump(comp_ctx, |
| pc_label_site + 2, |
| expr_c_empty ? pc_l1 : pc_l2); /* continue jump */ |
| } |
| goto finished; |
| |
| parse_3_or_4: |
| /* |
| * Parse variant 3 or 4. |
| * |
| * For variant 3 (e.g. "for (A in C) D;") the code for A (except the |
| * final property/variable write) has already been emitted. The first |
| * instruction of that code is at pc_v34_lhs; a JUMP needs to be inserted |
| * there to satisfy control flow needs. |
| * |
| * For variant 4, if the variable declaration had an initializer |
| * (e.g. "for (var A = B in C) D;") the code for the assignment |
| * (B) has already been emitted. |
| * |
| * Variables set before entering here: |
| * |
| * pc_v34_lhs: insert a "JUMP L2" here (see doc/compiler.rst example). |
| * reg_temps + 0: iteration target value (written to LHS) |
| * reg_temps + 1: enumerator object |
| */ |
| { |
| duk_int_t pc_l1, pc_l2, pc_l3, pc_l4, pc_l5; |
| duk_int_t pc_jumpto_l2, pc_jumpto_l3, pc_jumpto_l4, pc_jumpto_l5; |
| duk_reg_t reg_target; |
| |
| DUK_DDD(DUK_DDDPRINT("shared code for parsing variants 3 and 4, pc_v34_lhs=%ld", (long) pc_v34_lhs)); |
| |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| /* First we need to insert a jump in the middle of previously |
| * emitted code to get the control flow right. No jumps can |
| * cross the position where the jump is inserted. See doc/compiler.rst |
| * for discussion on the intricacies of control flow and side effects |
| * for variants 3 and 4. |
| */ |
| |
| duk__insert_jump_entry(comp_ctx, pc_v34_lhs); |
| pc_jumpto_l2 = pc_v34_lhs; /* inserted jump */ |
| pc_l1 = pc_v34_lhs + 1; /* +1, right after inserted jump */ |
| |
| /* The code for writing reg_temps + 0 to the left hand side has already |
| * been emitted. |
| */ |
| |
| pc_jumpto_l3 = duk__emit_jump_empty(comp_ctx); /* -> loop body */ |
| |
| duk__advance(comp_ctx); /* eat 'in' */ |
| |
| /* Parse enumeration target and initialize enumerator. For 'null' and 'undefined', |
| * INITENUM will creates a 'null' enumerator which works like an empty enumerator |
| * (E5 Section 12.6.4, step 3). Note that INITENUM requires the value to be in a |
| * register (constant not allowed). |
| */ |
| |
| pc_l2 = duk__get_current_pc(comp_ctx); |
| reg_target = duk__exprtop_toreg(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); /* Expression */ |
| duk__emit_extraop_b_c(comp_ctx, |
| DUK_EXTRAOP_INITENUM | DUK__EMIT_FLAG_B_IS_TARGET, |
| (duk_regconst_t) (reg_temps + 1), |
| (duk_regconst_t) reg_target); |
| pc_jumpto_l4 = duk__emit_jump_empty(comp_ctx); |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| pc_l3 = duk__get_current_pc(comp_ctx); |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| /* temp reset is not necessary after duk__parse_stmt(), which already does it */ |
| |
| /* NEXTENUM needs a jump slot right after the main opcode. |
| * We need the code emitter to reserve the slot: if there's |
| * target shuffling, the target shuffle opcodes must happen |
| * after the jump slot (for NEXTENUM the shuffle opcodes are |
| * not needed if the enum is finished). |
| */ |
| pc_l4 = duk__get_current_pc(comp_ctx); |
| duk__emit_extraop_b_c(comp_ctx, |
| DUK_EXTRAOP_NEXTENUM | DUK__EMIT_FLAG_B_IS_TARGET | DUK__EMIT_FLAG_RESERVE_JUMPSLOT, |
| (duk_regconst_t) (reg_temps + 0), |
| (duk_regconst_t) (reg_temps + 1)); |
| pc_jumpto_l5 = comp_ctx->emit_jumpslot_pc; /* NEXTENUM jump slot: executed when enum finished */ |
| duk__emit_jump(comp_ctx, pc_l1); /* jump to next loop, using reg_v34_iter as iterated value */ |
| |
| pc_l5 = duk__get_current_pc(comp_ctx); |
| |
| /* XXX: since the enumerator may be a memory expensive object, |
| * perhaps clear it explicitly here? If so, break jump must |
| * go through this clearing operation. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("patching jumps: jumpto_l2: %ld->%ld, jumpto_l3: %ld->%ld, " |
| "jumpto_l4: %ld->%ld, jumpto_l5: %ld->%ld, " |
| "break: %ld->%ld, continue: %ld->%ld", |
| (long) pc_jumpto_l2, (long) pc_l2, (long) pc_jumpto_l3, (long) pc_l3, |
| (long) pc_jumpto_l4, (long) pc_l4, (long) pc_jumpto_l5, (long) pc_l5, |
| (long) (pc_label_site + 1), (long) pc_l5, (long) (pc_label_site + 2), (long) pc_l4)); |
| |
| duk__patch_jump(comp_ctx, pc_jumpto_l2, pc_l2); |
| duk__patch_jump(comp_ctx, pc_jumpto_l3, pc_l3); |
| duk__patch_jump(comp_ctx, pc_jumpto_l4, pc_l4); |
| duk__patch_jump(comp_ctx, pc_jumpto_l5, pc_l5); |
| duk__patch_jump(comp_ctx, pc_label_site + 1, pc_l5); /* break jump */ |
| duk__patch_jump(comp_ctx, pc_label_site + 2, pc_l4); /* continue jump */ |
| } |
| goto finished; |
| |
| finished: |
| DUK_DDD(DUK_DDDPRINT("end parsing a for/for-in statement")); |
| return; |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_FOR); |
| } |
| |
| DUK_LOCAL void duk__parse_switch_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_reg_t temp_at_loop; |
| duk_regconst_t rc_switch; /* reg/const for switch value */ |
| duk_regconst_t rc_case; /* reg/const for case value */ |
| duk_reg_t reg_temp; /* general temp register */ |
| duk_int_t pc_prevcase = -1; |
| duk_int_t pc_prevstmt = -1; |
| duk_int_t pc_default = -1; /* -1 == not set, -2 == pending (next statement list) */ |
| |
| /* Note: negative pc values are ignored when patching jumps, so no explicit checks needed */ |
| |
| /* |
| * Switch is pretty complicated because of several conflicting concerns: |
| * |
| * - Want to generate code without an intermediate representation, |
| * i.e., in one go |
| * |
| * - Case selectors are expressions, not values, and may thus e.g. throw |
| * exceptions (which causes evaluation order concerns) |
| * |
| * - Evaluation semantics of case selectors and default clause need to be |
| * carefully implemented to provide correct behavior even with case value |
| * side effects |
| * |
| * - Fall through case and default clauses; avoiding dead JUMPs if case |
| * ends with an unconditional jump (a break or a continue) |
| * |
| * - The same case value may occur multiple times, but evaluation rules |
| * only process the first match before switching to a "propagation" mode |
| * where case values are no longer evaluated |
| * |
| * See E5 Section 12.11. Also see doc/compiler.rst for compilation |
| * discussion. |
| */ |
| |
| duk__advance(comp_ctx); |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| rc_switch = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| duk__advance_expect(comp_ctx, DUK_TOK_LCURLY); |
| |
| DUK_DDD(DUK_DDDPRINT("switch value in register %ld", (long) rc_switch)); |
| |
| temp_at_loop = DUK__GETTEMP(comp_ctx); |
| |
| for (;;) { |
| duk_int_t num_stmts; |
| duk_small_int_t tok; |
| |
| /* sufficient for keeping temp reg numbers in check */ |
| DUK__SETTEMP(comp_ctx, temp_at_loop); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) { |
| break; |
| } |
| |
| /* |
| * Parse a case or default clause. |
| */ |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_CASE) { |
| /* |
| * Case clause. |
| * |
| * Note: cannot use reg_case as a temp register (for SEQ target) |
| * because it may be a constant. |
| */ |
| |
| duk__patch_jump_here(comp_ctx, pc_prevcase); /* chain jumps for case |
| * evaluation and checking |
| */ |
| |
| duk__advance(comp_ctx); |
| rc_case = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| duk__advance_expect(comp_ctx, DUK_TOK_COLON); |
| |
| reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_SEQ, |
| (duk_regconst_t) reg_temp, |
| rc_switch, |
| rc_case); |
| duk__emit_if_true_skip(comp_ctx, (duk_regconst_t) reg_temp); |
| |
| /* jump to next case clause */ |
| pc_prevcase = duk__emit_jump_empty(comp_ctx); /* no match, next case */ |
| |
| /* statements go here (if any) on next loop */ |
| } else if (comp_ctx->curr_token.t == DUK_TOK_DEFAULT) { |
| /* |
| * Default clause. |
| */ |
| |
| if (pc_default >= 0) { |
| goto syntax_error; |
| } |
| duk__advance(comp_ctx); |
| duk__advance_expect(comp_ctx, DUK_TOK_COLON); |
| |
| /* Fix for https://github.com/svaarala/duktape/issues/155: |
| * If 'default' is first clause (detected by pc_prevcase < 0) |
| * we need to ensure we stay in the matching chain. |
| */ |
| if (pc_prevcase < 0) { |
| DUK_DD(DUK_DDPRINT("default clause is first, emit prevcase jump")); |
| pc_prevcase = duk__emit_jump_empty(comp_ctx); |
| } |
| |
| /* default clause matches next statement list (if any) */ |
| pc_default = -2; |
| } else { |
| /* Code is not accepted before the first case/default clause */ |
| goto syntax_error; |
| } |
| |
| /* |
| * Parse code after the clause. Possible terminators are |
| * 'case', 'default', and '}'. |
| * |
| * Note that there may be no code at all, not even an empty statement, |
| * between case clauses. This must be handled just like an empty statement |
| * (omitting seemingly pointless JUMPs), to avoid situations like |
| * test-bug-case-fallthrough.js. |
| */ |
| |
| num_stmts = 0; |
| if (pc_default == -2) { |
| pc_default = duk__get_current_pc(comp_ctx); |
| } |
| |
| /* Note: this is correct even for default clause statements: |
| * they participate in 'fall-through' behavior even if the |
| * default clause is in the middle. |
| */ |
| duk__patch_jump_here(comp_ctx, pc_prevstmt); /* chain jumps for 'fall-through' |
| * after a case matches. |
| */ |
| |
| for (;;) { |
| tok = comp_ctx->curr_token.t; |
| if (tok == DUK_TOK_CASE || tok == DUK_TOK_DEFAULT || |
| tok == DUK_TOK_RCURLY) { |
| break; |
| } |
| num_stmts++; |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| } |
| |
| /* fall-through jump to next code of next case (backpatched) */ |
| pc_prevstmt = duk__emit_jump_empty(comp_ctx); |
| |
| /* XXX: would be nice to omit this jump when the jump is not |
| * reachable, at least in the obvious cases (such as the case |
| * ending with a 'break'. |
| * |
| * Perhaps duk__parse_stmt() could provide some info on whether |
| * the statement is a "dead end"? |
| * |
| * If implemented, just set pc_prevstmt to -1 when not needed. |
| */ |
| } |
| |
| DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY); |
| duk__advance(comp_ctx); |
| |
| /* default case control flow patchup; note that if pc_prevcase < 0 |
| * (i.e. no case clauses), control enters default case automatically. |
| */ |
| if (pc_default >= 0) { |
| /* default case exists: go there if no case matches */ |
| duk__patch_jump(comp_ctx, pc_prevcase, pc_default); |
| } else { |
| /* default case does not exist, or no statements present |
| * after default case: finish case evaluation |
| */ |
| duk__patch_jump_here(comp_ctx, pc_prevcase); |
| } |
| |
| /* fall-through control flow patchup; note that pc_prevstmt may be |
| * < 0 (i.e. no case clauses), in which case this is a no-op. |
| */ |
| duk__patch_jump_here(comp_ctx, pc_prevstmt); |
| |
| /* continue jump not patched, an INVALID opcode remains there */ |
| duk__patch_jump_here(comp_ctx, pc_label_site + 1); /* break jump */ |
| |
| /* Note: 'fast' breaks will jump to pc_label_site + 1, which will |
| * then jump here. The double jump will be eliminated by a |
| * peephole pass, resulting in an optimal jump here. The label |
| * site jumps will remain in bytecode and will waste code size. |
| */ |
| |
| return; |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_SWITCH); |
| } |
| |
| DUK_LOCAL void duk__parse_if_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_reg_t temp_reset; |
| duk_regconst_t rc_cond; |
| duk_int_t pc_jump_false; |
| |
| DUK_DDD(DUK_DDDPRINT("begin parsing if statement")); |
| |
| temp_reset = DUK__GETTEMP(comp_ctx); |
| |
| duk__advance(comp_ctx); /* eat 'if' */ |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| |
| rc_cond = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| duk__emit_if_true_skip(comp_ctx, rc_cond); |
| pc_jump_false = duk__emit_jump_empty(comp_ctx); /* jump to end or else part */ |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| |
| /* The 'else' ambiguity is resolved by 'else' binding to the innermost |
| * construct, so greedy matching is correct here. |
| */ |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_ELSE) { |
| duk_int_t pc_jump_end; |
| |
| DUK_DDD(DUK_DDDPRINT("if has else part")); |
| |
| duk__advance(comp_ctx); |
| |
| pc_jump_end = duk__emit_jump_empty(comp_ctx); /* jump from true part to end */ |
| duk__patch_jump_here(comp_ctx, pc_jump_false); |
| |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| |
| duk__patch_jump_here(comp_ctx, pc_jump_end); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("if does not have else part")); |
| |
| duk__patch_jump_here(comp_ctx, pc_jump_false); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("end parsing if statement")); |
| } |
| |
| DUK_LOCAL void duk__parse_do_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) { |
| duk_regconst_t rc_cond; |
| duk_int_t pc_start; |
| |
| DUK_DDD(DUK_DDDPRINT("begin parsing do statement")); |
| |
| duk__advance(comp_ctx); /* eat 'do' */ |
| |
| pc_start = duk__get_current_pc(comp_ctx); |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| duk__patch_jump_here(comp_ctx, pc_label_site + 2); /* continue jump */ |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_WHILE); |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| |
| rc_cond = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| duk__emit_if_false_skip(comp_ctx, rc_cond); |
| duk__emit_jump(comp_ctx, pc_start); |
| /* no need to reset temps, as we're finished emitting code */ |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| duk__patch_jump_here(comp_ctx, pc_label_site + 1); /* break jump */ |
| |
| DUK_DDD(DUK_DDDPRINT("end parsing do statement")); |
| } |
| |
| DUK_LOCAL void duk__parse_while_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_int_t pc_label_site) { |
| duk_reg_t temp_reset; |
| duk_regconst_t rc_cond; |
| duk_int_t pc_start; |
| duk_int_t pc_jump_false; |
| |
| DUK_DDD(DUK_DDDPRINT("begin parsing while statement")); |
| |
| temp_reset = DUK__GETTEMP(comp_ctx); |
| |
| duk__advance(comp_ctx); /* eat 'while' */ |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| |
| pc_start = duk__get_current_pc(comp_ctx); |
| duk__patch_jump_here(comp_ctx, pc_label_site + 2); /* continue jump */ |
| |
| rc_cond = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| duk__emit_if_true_skip(comp_ctx, rc_cond); |
| pc_jump_false = duk__emit_jump_empty(comp_ctx); |
| DUK__SETTEMP(comp_ctx, temp_reset); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| duk__emit_jump(comp_ctx, pc_start); |
| |
| duk__patch_jump_here(comp_ctx, pc_jump_false); |
| duk__patch_jump_here(comp_ctx, pc_label_site + 1); /* break jump */ |
| |
| DUK_DDD(DUK_DDDPRINT("end parsing while statement")); |
| } |
| |
| DUK_LOCAL void duk__parse_break_or_continue_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_bool_t is_break = (comp_ctx->curr_token.t == DUK_TOK_BREAK); |
| duk_int_t label_id; |
| duk_int_t label_catch_depth; |
| duk_int_t label_pc; /* points to LABEL; pc+1 = jump site for break; pc+2 = jump site for continue */ |
| duk_bool_t label_is_closest; |
| |
| DUK_UNREF(res); |
| |
| duk__advance(comp_ctx); /* eat 'break' or 'continue' */ |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON || /* explicit semi follows */ |
| comp_ctx->curr_token.lineterm || /* automatic semi will be inserted */ |
| comp_ctx->curr_token.allow_auto_semi) { /* automatic semi will be inserted */ |
| /* break/continue without label */ |
| |
| duk__lookup_active_label(comp_ctx, DUK_HTHREAD_STRING_EMPTY_STRING(thr), is_break, &label_id, &label_catch_depth, &label_pc, &label_is_closest); |
| } else if (comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER) { |
| /* break/continue with label (label cannot be a reserved word, production is 'Identifier' */ |
| DUK_ASSERT(comp_ctx->curr_token.str1 != NULL); |
| duk__lookup_active_label(comp_ctx, comp_ctx->curr_token.str1, is_break, &label_id, &label_catch_depth, &label_pc, &label_is_closest); |
| duk__advance(comp_ctx); |
| } else { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_BREAK_CONT_LABEL); |
| } |
| |
| /* Use a fast break/continue when possible. A fast break/continue is |
| * just a jump to the LABEL break/continue jump slot, which then jumps |
| * to an appropriate place (for break, going through ENDLABEL correctly). |
| * The peephole optimizer will optimize the jump to a direct one. |
| */ |
| |
| if (label_catch_depth == comp_ctx->curr_func.catch_depth && |
| label_is_closest) { |
| DUK_DDD(DUK_DDDPRINT("break/continue: is_break=%ld, label_id=%ld, label_is_closest=%ld, " |
| "label_catch_depth=%ld, catch_depth=%ld " |
| "-> use fast variant (direct jump)", |
| (long) is_break, (long) label_id, (long) label_is_closest, |
| (long) label_catch_depth, (long) comp_ctx->curr_func.catch_depth)); |
| |
| duk__emit_jump(comp_ctx, label_pc + (is_break ? 1 : 2)); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("break/continue: is_break=%ld, label_id=%ld, label_is_closest=%ld, " |
| "label_catch_depth=%ld, catch_depth=%ld " |
| "-> use slow variant (longjmp)", |
| (long) is_break, (long) label_id, (long) label_is_closest, |
| (long) label_catch_depth, (long) comp_ctx->curr_func.catch_depth)); |
| |
| duk__emit_extraop_bc(comp_ctx, |
| is_break ? DUK_EXTRAOP_BREAK : DUK_EXTRAOP_CONTINUE, |
| (duk_regconst_t) label_id); |
| } |
| } |
| |
| DUK_LOCAL void duk__parse_return_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_regconst_t rc_val; |
| duk_small_uint_t ret_flags; |
| |
| duk__advance(comp_ctx); /* eat 'return' */ |
| |
| /* A 'return' statement is only allowed inside an actual function body, |
| * not as part of eval or global code. |
| */ |
| if (!comp_ctx->curr_func.is_function) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_RETURN); |
| } |
| |
| ret_flags = 0; |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON || /* explicit semi follows */ |
| comp_ctx->curr_token.lineterm || /* automatic semi will be inserted */ |
| comp_ctx->curr_token.allow_auto_semi) { /* automatic semi will be inserted */ |
| DUK_DDD(DUK_DDDPRINT("empty return value -> undefined")); |
| rc_val = 0; |
| } else { |
| duk_int_t pc_before_expr; |
| duk_int_t pc_after_expr; |
| |
| DUK_DDD(DUK_DDDPRINT("return with a value")); |
| |
| DUK_UNREF(pc_before_expr); |
| DUK_UNREF(pc_after_expr); |
| |
| pc_before_expr = duk__get_current_pc(comp_ctx); |
| rc_val = duk__exprtop_toregconst(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| pc_after_expr = duk__get_current_pc(comp_ctx); |
| |
| /* Tail call check: if last opcode emitted was CALL(I), and |
| * the context allows it, change the CALL(I) to a tail call. |
| * This doesn't guarantee that a tail call will be allowed at |
| * runtime, so the RETURN must still be emitted. (Duktape |
| * 0.10.0 avoided this and simulated a RETURN if a tail call |
| * couldn't be used at runtime; but this didn't work |
| * correctly with a thread yield/resume, see |
| * test-bug-tailcall-thread-yield-resume.js for discussion.) |
| * |
| * In addition to the last opcode being CALL, we also need to |
| * be sure that 'rc_val' is the result register of the CALL(I). |
| * For instance, for the expression 'return 0, (function () |
| * { return 1; }), 2' the last opcode emitted is CALL (no |
| * bytecode is emitted for '2') but 'rc_val' indicates |
| * constant '2'. Similarly if '2' is replaced by a register |
| * bound variable, no opcodes are emitted but tail call would |
| * be incorrect. |
| * |
| * This is tricky and easy to get wrong. It would be best to |
| * track enough expression metadata to check that 'rc_val' came |
| * from that last CALL instruction. We don't have that metadata |
| * now, so we check that 'rc_val' is a temporary register result |
| * (not a constant or a register bound variable). There should |
| * be no way currently for 'rc_val' to be a temporary for an |
| * expression following the CALL instruction without emitting |
| * some opcodes following the CALL. This proxy check is used |
| * below. |
| * |
| * See: test-bug-comma-expr-gh131.js. |
| * |
| * The non-standard 'caller' property disables tail calls |
| * because they pose some special cases which haven't been |
| * fixed yet. |
| */ |
| |
| #if defined(DUK_USE_TAILCALL) |
| if (comp_ctx->curr_func.catch_depth == 0 && /* no catchers */ |
| pc_after_expr > pc_before_expr) { /* at least one opcode emitted */ |
| duk_compiler_instr *instr; |
| duk_small_uint_t op; |
| |
| instr = duk__get_instr_ptr(comp_ctx, pc_after_expr - 1); |
| DUK_ASSERT(instr != NULL); |
| |
| op = (duk_small_uint_t) DUK_DEC_OP(instr->ins); |
| if ((op == DUK_OP_CALL || op == DUK_OP_CALLI) && |
| DUK__ISTEMP(comp_ctx, rc_val) /* see above */) { |
| DUK_DDD(DUK_DDDPRINT("return statement detected a tail call opportunity: " |
| "catch depth is 0, duk__exprtop() emitted >= 1 instructions, " |
| "and last instruction is a CALL " |
| "-> set TAILCALL flag")); |
| /* Just flip the single bit. */ |
| instr->ins |= DUK_ENC_OP_A_B_C(0, DUK_BC_CALL_FLAG_TAILCALL, 0, 0); |
| } |
| } |
| #endif /* DUK_USE_TAILCALL */ |
| |
| ret_flags = DUK_BC_RETURN_FLAG_HAVE_RETVAL; |
| } |
| |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_RETURN | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) ret_flags /*flags*/, |
| rc_val /*reg*/); |
| } |
| |
| DUK_LOCAL void duk__parse_throw_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_reg_t reg_val; |
| |
| duk__advance(comp_ctx); /* eat 'throw' */ |
| |
| /* Unlike break/continue, throw statement does not allow an empty value. */ |
| |
| if (comp_ctx->curr_token.lineterm) { |
| DUK_ERROR_SYNTAX(comp_ctx->thr, DUK_STR_INVALID_THROW); |
| } |
| |
| reg_val = duk__exprtop_toreg(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_THROW, |
| (duk_regconst_t) reg_val); |
| } |
| |
| DUK_LOCAL void duk__parse_try_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_reg_t reg_catch; /* reg_catch+0 and reg_catch+1 are reserved for TRYCATCH */ |
| duk_regconst_t rc_varname = 0; |
| duk_small_uint_t trycatch_flags = 0; |
| duk_int_t pc_ldconst = -1; |
| duk_int_t pc_trycatch = -1; |
| duk_int_t pc_catch = -1; |
| duk_int_t pc_finally = -1; |
| |
| DUK_UNREF(res); |
| |
| /* |
| * See the following documentation for discussion: |
| * |
| * doc/execution.rst: control flow details |
| * |
| * Try, catch, and finally "parts" are Blocks, not Statements, so |
| * they must always be delimited by curly braces. This is unlike e.g. |
| * the if statement, which accepts any Statement. This eliminates any |
| * questions of matching parts of nested try statements. The Block |
| * parsing is implemented inline here (instead of calling out). |
| * |
| * Finally part has a 'let scoped' variable, which requires a few kinks |
| * here. |
| */ |
| |
| comp_ctx->curr_func.catch_depth++; |
| |
| duk__advance(comp_ctx); /* eat 'try' */ |
| |
| reg_catch = DUK__ALLOCTEMPS(comp_ctx, 2); |
| |
| /* The target for this LDCONST may need output shuffling, but we assume |
| * that 'pc_ldconst' will be the LDCONST that we can patch later. This |
| * should be the case because there's no input shuffling. (If there's |
| * no catch clause, this LDCONST will be replaced with a NOP.) |
| */ |
| pc_ldconst = duk__get_current_pc(comp_ctx); |
| duk__emit_a_bc(comp_ctx, DUK_OP_LDCONST, reg_catch, 0 /*patched later*/); |
| |
| pc_trycatch = duk__get_current_pc(comp_ctx); |
| duk__emit_invalid(comp_ctx); /* TRYCATCH, cannot emit now (not enough info) */ |
| duk__emit_invalid(comp_ctx); /* jump for 'catch' case */ |
| duk__emit_invalid(comp_ctx); /* jump for 'finally' case or end (if no finally) */ |
| |
| /* try part */ |
| duk__advance_expect(comp_ctx, DUK_TOK_LCURLY); |
| duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/); |
| /* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */ |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_ENDTRY); |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_CATCH) { |
| /* |
| * The catch variable must be updated to reflect the new allocated |
| * register for the duration of the catch clause. We need to store |
| * and restore the original value for the varmap entry (if any). |
| */ |
| |
| /* |
| * Note: currently register bindings must be fixed for the entire |
| * function. So, even though the catch variable is in a register |
| * we know, we must use an explicit environment record and slow path |
| * accesses to read/write the catch binding to make closures created |
| * within the catch clause work correctly. This restriction should |
| * be fixable (at least in common cases) later. |
| * |
| * See: test-bug-catch-binding-2.js. |
| * |
| * XXX: improve to get fast path access to most catch clauses. |
| */ |
| |
| duk_hstring *h_var; |
| duk_int_t varmap_value; /* for storing/restoring the varmap binding for catch variable */ |
| |
| DUK_DDD(DUK_DDDPRINT("stack top at start of catch clause: %ld", (long) duk_get_top(ctx))); |
| |
| trycatch_flags |= DUK_BC_TRYCATCH_FLAG_HAVE_CATCH; |
| |
| pc_catch = duk__get_current_pc(comp_ctx); |
| |
| duk__advance(comp_ctx); |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| |
| if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) { |
| /* Identifier, i.e. don't allow reserved words */ |
| goto syntax_error; |
| } |
| h_var = comp_ctx->curr_token.str1; |
| DUK_ASSERT(h_var != NULL); |
| |
| duk_push_hstring(ctx, h_var); /* keep in on valstack, use borrowed ref below */ |
| |
| if (comp_ctx->curr_func.is_strict && |
| ((h_var == DUK_HTHREAD_STRING_EVAL(thr)) || |
| (h_var == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)))) { |
| DUK_DDD(DUK_DDDPRINT("catch identifier 'eval' or 'arguments' in strict mode -> SyntaxError")); |
| goto syntax_error; |
| } |
| |
| duk_dup_top(ctx); |
| rc_varname = duk__getconst(comp_ctx); |
| DUK_DDD(DUK_DDDPRINT("catch clause, rc_varname=0x%08lx (%ld)", |
| (unsigned long) rc_varname, (long) rc_varname)); |
| |
| duk__advance(comp_ctx); |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_LCURLY); |
| |
| DUK_DDD(DUK_DDDPRINT("varmap before modifying for catch clause: %!iT", |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx))); |
| |
| duk_dup_top(ctx); |
| duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| if (duk_is_undefined(ctx, -1)) { |
| varmap_value = -2; |
| } else if (duk_is_null(ctx, -1)) { |
| varmap_value = -1; |
| } else { |
| DUK_ASSERT(duk_is_number(ctx, -1)); |
| varmap_value = duk_get_int(ctx, -1); |
| DUK_ASSERT(varmap_value >= 0); |
| } |
| duk_pop(ctx); |
| |
| #if 0 |
| /* It'd be nice to do something like this - but it doesn't |
| * work for closures created inside the catch clause. |
| */ |
| duk_dup_top(ctx); |
| duk_push_int(ctx, (duk_int_t) (reg_catch + 0)); |
| duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| #endif |
| duk_dup_top(ctx); |
| duk_push_null(ctx); |
| duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_PUTVAR | DUK__EMIT_FLAG_A_IS_SOURCE, |
| (duk_regconst_t) (reg_catch + 0) /*value*/, |
| rc_varname /*varname*/); |
| |
| DUK_DDD(DUK_DDDPRINT("varmap before parsing catch clause: %!iT", |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx))); |
| |
| duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/); |
| /* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */ |
| |
| if (varmap_value == -2) { |
| /* not present */ |
| duk_del_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| } else { |
| if (varmap_value == -1) { |
| duk_push_null(ctx); |
| } else { |
| DUK_ASSERT(varmap_value >= 0); |
| duk_push_int(ctx, varmap_value); |
| } |
| duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| } |
| /* varname is popped by above code */ |
| |
| DUK_DDD(DUK_DDDPRINT("varmap after restore catch clause: %!iT", |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx))); |
| |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_ENDCATCH); |
| |
| /* |
| * XXX: for now, indicate that an expensive catch binding |
| * declarative environment is always needed. If we don't |
| * need it, we don't need the const_varname either. |
| */ |
| |
| trycatch_flags |= DUK_BC_TRYCATCH_FLAG_CATCH_BINDING; |
| |
| DUK_DDD(DUK_DDDPRINT("stack top at end of catch clause: %ld", (long) duk_get_top(ctx))); |
| } |
| |
| if (comp_ctx->curr_token.t == DUK_TOK_FINALLY) { |
| trycatch_flags |= DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY; |
| |
| pc_finally = duk__get_current_pc(comp_ctx); |
| |
| duk__advance(comp_ctx); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_LCURLY); |
| duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/); |
| /* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */ |
| duk__emit_extraop_b(comp_ctx, |
| DUK_EXTRAOP_ENDFIN, |
| reg_catch); /* rethrow */ |
| } |
| |
| if (!(trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) && |
| !(trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY)) { |
| /* must have catch and/or finally */ |
| goto syntax_error; |
| } |
| |
| /* If there's no catch block, rc_varname will be 0 and duk__patch_trycatch() |
| * will replace the LDCONST with a NOP. For any actual constant (including |
| * constant 0) the DUK__CONST_MARKER flag will be set in rc_varname. |
| */ |
| |
| duk__patch_trycatch(comp_ctx, |
| pc_ldconst, |
| pc_trycatch, |
| reg_catch, |
| rc_varname, |
| trycatch_flags); |
| |
| if (trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) { |
| DUK_ASSERT(pc_catch >= 0); |
| duk__patch_jump(comp_ctx, pc_trycatch + 1, pc_catch); |
| } |
| |
| if (trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) { |
| DUK_ASSERT(pc_finally >= 0); |
| duk__patch_jump(comp_ctx, pc_trycatch + 2, pc_finally); |
| } else { |
| /* without finally, the second jump slot is used to jump to end of stmt */ |
| duk__patch_jump_here(comp_ctx, pc_trycatch + 2); |
| } |
| |
| comp_ctx->curr_func.catch_depth--; |
| return; |
| |
| syntax_error: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_TRY); |
| } |
| |
| DUK_LOCAL void duk__parse_with_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res) { |
| duk_int_t pc_trycatch; |
| duk_int_t pc_finished; |
| duk_reg_t reg_catch; |
| duk_small_uint_t trycatch_flags; |
| |
| if (comp_ctx->curr_func.is_strict) { |
| DUK_ERROR_SYNTAX(comp_ctx->thr, DUK_STR_WITH_IN_STRICT_MODE); |
| } |
| |
| comp_ctx->curr_func.catch_depth++; |
| |
| duk__advance(comp_ctx); /* eat 'with' */ |
| |
| reg_catch = DUK__ALLOCTEMPS(comp_ctx, 2); |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| duk__exprtop_toforcedreg(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/, reg_catch); |
| duk__advance_expect(comp_ctx, DUK_TOK_RPAREN); |
| |
| pc_trycatch = duk__get_current_pc(comp_ctx); |
| trycatch_flags = DUK_BC_TRYCATCH_FLAG_WITH_BINDING; |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_TRYCATCH | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) trycatch_flags /*a*/, |
| (duk_regconst_t) reg_catch /*bc*/); |
| duk__emit_invalid(comp_ctx); /* catch jump */ |
| duk__emit_invalid(comp_ctx); /* finished jump */ |
| |
| duk__parse_stmt(comp_ctx, res, 0 /*allow_source_elem*/); |
| duk__emit_extraop_only(comp_ctx, |
| DUK_EXTRAOP_ENDTRY); |
| |
| pc_finished = duk__get_current_pc(comp_ctx); |
| |
| duk__patch_jump(comp_ctx, pc_trycatch + 2, pc_finished); |
| |
| comp_ctx->curr_func.catch_depth--; |
| } |
| |
| DUK_LOCAL duk_int_t duk__stmt_label_site(duk_compiler_ctx *comp_ctx, duk_int_t label_id) { |
| /* if a site already exists, nop: max one label site per statement */ |
| if (label_id >= 0) { |
| return label_id; |
| } |
| |
| label_id = comp_ctx->curr_func.label_next++; |
| DUK_DDD(DUK_DDDPRINT("allocated new label id for label site: %ld", (long) label_id)); |
| |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_LABEL, |
| (duk_regconst_t) label_id); |
| duk__emit_invalid(comp_ctx); |
| duk__emit_invalid(comp_ctx); |
| |
| return label_id; |
| } |
| |
| /* Parse a single statement. |
| * |
| * Creates a label site (with an empty label) automatically for iteration |
| * statements. Also "peels off" any label statements for explicit labels. |
| */ |
| DUK_LOCAL void duk__parse_stmt(duk_compiler_ctx *comp_ctx, duk_ivalue *res, duk_bool_t allow_source_elem) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_bool_t dir_prol_at_entry; /* directive prologue status at entry */ |
| duk_reg_t temp_at_entry; |
| duk_uarridx_t labels_len_at_entry; |
| duk_int_t pc_at_entry; /* assumed to also be PC of "LABEL" */ |
| duk_int_t stmt_id; |
| duk_small_uint_t stmt_flags = 0; |
| duk_int_t label_id = -1; |
| duk_small_uint_t tok; |
| |
| DUK__RECURSION_INCREASE(comp_ctx, thr); |
| |
| temp_at_entry = DUK__GETTEMP(comp_ctx); |
| pc_at_entry = duk__get_current_pc(comp_ctx); |
| labels_len_at_entry = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.labelnames_idx); |
| stmt_id = comp_ctx->curr_func.stmt_next++; |
| dir_prol_at_entry = comp_ctx->curr_func.in_directive_prologue; |
| |
| DUK_UNREF(stmt_id); |
| |
| DUK_DDD(DUK_DDDPRINT("parsing a statement, stmt_id=%ld, temp_at_entry=%ld, labels_len_at_entry=%ld, " |
| "is_strict=%ld, in_directive_prologue=%ld, catch_depth=%ld", |
| (long) stmt_id, (long) temp_at_entry, (long) labels_len_at_entry, |
| (long) comp_ctx->curr_func.is_strict, (long) comp_ctx->curr_func.in_directive_prologue, |
| (long) comp_ctx->curr_func.catch_depth)); |
| |
| /* The directive prologue flag is cleared by default so that it is |
| * unset for any recursive statement parsing. It is only "revived" |
| * if a directive is detected. (We could also make directives only |
| * allowed if 'allow_source_elem' was true.) |
| */ |
| comp_ctx->curr_func.in_directive_prologue = 0; |
| |
| retry_parse: |
| |
| DUK_DDD(DUK_DDDPRINT("try stmt parse, stmt_id=%ld, label_id=%ld, allow_source_elem=%ld, catch_depth=%ld", |
| (long) stmt_id, (long) label_id, (long) allow_source_elem, |
| (long) comp_ctx->curr_func.catch_depth)); |
| |
| /* |
| * Detect iteration statements; if encountered, establish an |
| * empty label. |
| */ |
| |
| tok = comp_ctx->curr_token.t; |
| if (tok == DUK_TOK_FOR || tok == DUK_TOK_DO || tok == DUK_TOK_WHILE || |
| tok == DUK_TOK_SWITCH) { |
| DUK_DDD(DUK_DDDPRINT("iteration/switch statement -> add empty label")); |
| |
| label_id = duk__stmt_label_site(comp_ctx, label_id); |
| duk__add_label(comp_ctx, |
| DUK_HTHREAD_STRING_EMPTY_STRING(thr), |
| pc_at_entry /*pc_label*/, |
| label_id); |
| } |
| |
| /* |
| * Main switch for statement / source element type. |
| */ |
| |
| switch (comp_ctx->curr_token.t) { |
| case DUK_TOK_FUNCTION: { |
| /* |
| * Function declaration, function expression, or (non-standard) |
| * function statement. |
| * |
| * The E5 specification only allows function declarations at |
| * the top level (in "source elements"). An ExpressionStatement |
| * is explicitly not allowed to begin with a "function" keyword |
| * (E5 Section 12.4). Hence any non-error semantics for such |
| * non-top-level statements are non-standard. Duktape semantics |
| * for function statements are modelled after V8, see |
| * test-dev-func-decl-outside-top.js. |
| */ |
| |
| #if defined(DUK_USE_NONSTD_FUNC_STMT) |
| /* Lenient: allow function declarations outside top level in |
| * non-strict mode but reject them in strict mode. |
| */ |
| if (allow_source_elem || !comp_ctx->curr_func.is_strict) |
| #else /* DUK_USE_NONSTD_FUNC_STMT */ |
| /* Strict: never allow function declarations outside top level. */ |
| if (allow_source_elem) |
| #endif /* DUK_USE_NONSTD_FUNC_STMT */ |
| { |
| /* FunctionDeclaration: not strictly a statement but handled as such. |
| * |
| * O(depth^2) parse count for inner functions is handled by recording a |
| * lexer offset on the first compilation pass, so that the function can |
| * be efficiently skipped on the second pass. This is encapsulated into |
| * duk__parse_func_like_fnum(). |
| */ |
| |
| duk_int_t fnum; |
| |
| DUK_DDD(DUK_DDDPRINT("function declaration statement")); |
| |
| duk__advance(comp_ctx); /* eat 'function' */ |
| fnum = duk__parse_func_like_fnum(comp_ctx, 1 /*is_decl*/, 0 /*is_setget*/); |
| |
| if (comp_ctx->curr_func.in_scanning) { |
| duk_uarridx_t n; |
| duk_hstring *h_funcname; |
| |
| duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, fnum * 3); |
| duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME); /* -> [ ... func name ] */ |
| h_funcname = duk_get_hstring(ctx, -1); |
| DUK_ASSERT(h_funcname != NULL); |
| |
| DUK_DDD(DUK_DDDPRINT("register function declaration %!O in pass 1, fnum %ld", |
| (duk_heaphdr *) h_funcname, (long) fnum)); |
| n = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.decls_idx); |
| duk_push_hstring(ctx, h_funcname); |
| duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n); |
| duk_push_int(ctx, (duk_int_t) (DUK_DECL_TYPE_FUNC + (fnum << 8))); |
| duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n + 1); |
| |
| duk_pop_n(ctx, 2); |
| } |
| |
| /* no statement value (unlike function expression) */ |
| stmt_flags = 0; |
| break; |
| } else { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_FUNC_STMT_NOT_ALLOWED); |
| } |
| break; |
| } |
| case DUK_TOK_LCURLY: { |
| DUK_DDD(DUK_DDDPRINT("block statement")); |
| duk__advance(comp_ctx); |
| duk__parse_stmts(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/); |
| /* the DUK_TOK_RCURLY is eaten by duk__parse_stmts() */ |
| if (label_id >= 0) { |
| duk__patch_jump_here(comp_ctx, pc_at_entry + 1); /* break jump */ |
| } |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_CONST: { |
| DUK_DDD(DUK_DDDPRINT("constant declaration statement")); |
| duk__parse_var_stmt(comp_ctx, res, DUK__EXPR_FLAG_REQUIRE_INIT /*expr_flags*/); |
| stmt_flags = DUK__HAS_TERM; |
| break; |
| } |
| case DUK_TOK_VAR: { |
| DUK_DDD(DUK_DDDPRINT("variable declaration statement")); |
| duk__parse_var_stmt(comp_ctx, res, 0 /*expr_flags*/); |
| stmt_flags = DUK__HAS_TERM; |
| break; |
| } |
| case DUK_TOK_SEMICOLON: { |
| /* empty statement with an explicit semicolon */ |
| DUK_DDD(DUK_DDDPRINT("empty statement")); |
| stmt_flags = DUK__HAS_TERM; |
| break; |
| } |
| case DUK_TOK_IF: { |
| DUK_DDD(DUK_DDDPRINT("if statement")); |
| duk__parse_if_stmt(comp_ctx, res); |
| if (label_id >= 0) { |
| duk__patch_jump_here(comp_ctx, pc_at_entry + 1); /* break jump */ |
| } |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_DO: { |
| /* |
| * Do-while statement is mostly trivial, but there is special |
| * handling for automatic semicolon handling (triggered by the |
| * DUK__ALLOW_AUTO_SEMI_ALWAYS) flag related to a bug filed at: |
| * |
| * https://bugs.ecmascript.org/show_bug.cgi?id=8 |
| * |
| * See doc/compiler.rst for details. |
| */ |
| DUK_DDD(DUK_DDDPRINT("do statement")); |
| DUK_ASSERT(label_id >= 0); |
| duk__update_label_flags(comp_ctx, |
| label_id, |
| DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE); |
| duk__parse_do_stmt(comp_ctx, res, pc_at_entry); |
| stmt_flags = DUK__HAS_TERM | DUK__ALLOW_AUTO_SEMI_ALWAYS; /* DUK__ALLOW_AUTO_SEMI_ALWAYS workaround */ |
| break; |
| } |
| case DUK_TOK_WHILE: { |
| DUK_DDD(DUK_DDDPRINT("while statement")); |
| DUK_ASSERT(label_id >= 0); |
| duk__update_label_flags(comp_ctx, |
| label_id, |
| DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE); |
| duk__parse_while_stmt(comp_ctx, res, pc_at_entry); |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_FOR: { |
| /* |
| * For/for-in statement is complicated to parse because |
| * determining the statement type (three-part for vs. a |
| * for-in) requires potential backtracking. |
| * |
| * See the helper for the messy stuff. |
| */ |
| DUK_DDD(DUK_DDDPRINT("for/for-in statement")); |
| DUK_ASSERT(label_id >= 0); |
| duk__update_label_flags(comp_ctx, |
| label_id, |
| DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE); |
| duk__parse_for_stmt(comp_ctx, res, pc_at_entry); |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_CONTINUE: |
| case DUK_TOK_BREAK: { |
| DUK_DDD(DUK_DDDPRINT("break/continue statement")); |
| duk__parse_break_or_continue_stmt(comp_ctx, res); |
| stmt_flags = DUK__HAS_TERM | DUK__IS_TERMINAL; |
| break; |
| } |
| case DUK_TOK_RETURN: { |
| DUK_DDD(DUK_DDDPRINT("return statement")); |
| duk__parse_return_stmt(comp_ctx, res); |
| stmt_flags = DUK__HAS_TERM | DUK__IS_TERMINAL; |
| break; |
| } |
| case DUK_TOK_WITH: { |
| DUK_DDD(DUK_DDDPRINT("with statement")); |
| comp_ctx->curr_func.with_depth++; |
| duk__parse_with_stmt(comp_ctx, res); |
| if (label_id >= 0) { |
| duk__patch_jump_here(comp_ctx, pc_at_entry + 1); /* break jump */ |
| } |
| comp_ctx->curr_func.with_depth--; |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_SWITCH: { |
| /* |
| * The switch statement is pretty messy to compile. |
| * See the helper for details. |
| */ |
| DUK_DDD(DUK_DDDPRINT("switch statement")); |
| DUK_ASSERT(label_id >= 0); |
| duk__update_label_flags(comp_ctx, |
| label_id, |
| DUK_LABEL_FLAG_ALLOW_BREAK); /* don't allow continue */ |
| duk__parse_switch_stmt(comp_ctx, res, pc_at_entry); |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_THROW: { |
| DUK_DDD(DUK_DDDPRINT("throw statement")); |
| duk__parse_throw_stmt(comp_ctx, res); |
| stmt_flags = DUK__HAS_TERM | DUK__IS_TERMINAL; |
| break; |
| } |
| case DUK_TOK_TRY: { |
| DUK_DDD(DUK_DDDPRINT("try statement")); |
| duk__parse_try_stmt(comp_ctx, res); |
| stmt_flags = 0; |
| break; |
| } |
| case DUK_TOK_DEBUGGER: { |
| duk__advance(comp_ctx); |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| DUK_DDD(DUK_DDDPRINT("debugger statement: debugging enabled, emit debugger opcode")); |
| duk__emit_extraop_only(comp_ctx, DUK_EXTRAOP_DEBUGGER); |
| #else |
| DUK_DDD(DUK_DDDPRINT("debugger statement: ignored")); |
| #endif |
| stmt_flags = DUK__HAS_TERM; |
| break; |
| } |
| default: { |
| /* |
| * Else, must be one of: |
| * - ExpressionStatement, possibly a directive (String) |
| * - LabelledStatement (Identifier followed by ':') |
| * |
| * Expressions beginning with 'function' keyword are covered by a case |
| * above (such expressions are not allowed in standard E5 anyway). |
| * Also expressions starting with '{' are interpreted as block |
| * statements. See E5 Section 12.4. |
| * |
| * Directive detection is tricky; see E5 Section 14.1 on directive |
| * prologue. A directive is an expression statement with a single |
| * string literal and an explicit or automatic semicolon. Escape |
| * characters are significant and no parens etc are allowed: |
| * |
| * 'use strict'; // valid 'use strict' directive |
| * 'use\u0020strict'; // valid directive, not a 'use strict' directive |
| * ('use strict'); // not a valid directive |
| * |
| * The expression is determined to consist of a single string literal |
| * based on duk__expr_nud() and duk__expr_led() call counts. The string literal |
| * of a 'use strict' directive is determined to lack any escapes based |
| * num_escapes count from the lexer. Note that other directives may be |
| * allowed to contain escapes, so a directive with escapes does not |
| * terminate a directive prologue. |
| * |
| * We rely on the fact that the expression parser will not emit any |
| * code for a single token expression. However, it will generate an |
| * intermediate value which we will then successfully ignore. |
| * |
| * A similar approach is used for labels. |
| */ |
| |
| duk_bool_t single_token; |
| |
| DUK_DDD(DUK_DDDPRINT("expression statement")); |
| duk__exprtop(comp_ctx, res, DUK__BP_FOR_EXPR /*rbp_flags*/); |
| |
| single_token = (comp_ctx->curr_func.nud_count == 1 && /* one token */ |
| comp_ctx->curr_func.led_count == 0); /* no operators */ |
| |
| if (single_token && |
| comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER && |
| comp_ctx->curr_token.t == DUK_TOK_COLON) { |
| /* |
| * Detected label |
| */ |
| |
| duk_hstring *h_lab; |
| |
| /* expected ival */ |
| DUK_ASSERT(res->t == DUK_IVAL_VAR); |
| DUK_ASSERT(res->x1.t == DUK_ISPEC_VALUE); |
| DUK_ASSERT(DUK_TVAL_IS_STRING(duk_get_tval(ctx, res->x1.valstack_idx))); |
| h_lab = comp_ctx->prev_token.str1; |
| DUK_ASSERT(h_lab != NULL); |
| |
| DUK_DDD(DUK_DDDPRINT("explicit label site for label '%!O'", |
| (duk_heaphdr *) h_lab)); |
| |
| duk__advance(comp_ctx); /* eat colon */ |
| |
| label_id = duk__stmt_label_site(comp_ctx, label_id); |
| |
| duk__add_label(comp_ctx, |
| h_lab, |
| pc_at_entry /*pc_label*/, |
| label_id); |
| |
| /* a statement following a label cannot be a source element |
| * (a function declaration). |
| */ |
| allow_source_elem = 0; |
| |
| DUK_DDD(DUK_DDDPRINT("label handled, retry statement parsing")); |
| goto retry_parse; |
| } |
| |
| stmt_flags = 0; |
| |
| if (dir_prol_at_entry && /* still in prologue */ |
| single_token && /* single string token */ |
| comp_ctx->prev_token.t == DUK_TOK_STRING) { |
| /* |
| * Detected a directive |
| */ |
| duk_hstring *h_dir; |
| |
| /* expected ival */ |
| DUK_ASSERT(res->t == DUK_IVAL_PLAIN); |
| DUK_ASSERT(res->x1.t == DUK_ISPEC_VALUE); |
| DUK_ASSERT(DUK_TVAL_IS_STRING(duk_get_tval(ctx, res->x1.valstack_idx))); |
| h_dir = comp_ctx->prev_token.str1; |
| DUK_ASSERT(h_dir != NULL); |
| |
| DUK_DDD(DUK_DDDPRINT("potential directive: %!O", h_dir)); |
| |
| stmt_flags |= DUK__STILL_PROLOGUE; |
| |
| /* Note: escaped characters differentiate directives */ |
| |
| if (comp_ctx->prev_token.num_escapes > 0) { |
| DUK_DDD(DUK_DDDPRINT("directive contains escapes: valid directive " |
| "but we ignore such directives")); |
| } else { |
| /* |
| * The length comparisons are present to handle |
| * strings like "use strict\u0000foo" as required. |
| */ |
| |
| if (DUK_HSTRING_GET_BYTELEN(h_dir) == 10 && |
| DUK_STRNCMP((const char *) DUK_HSTRING_GET_DATA(h_dir), "use strict", 10) == 0) { |
| #if defined(DUK_USE_STRICT_DECL) |
| DUK_DDD(DUK_DDDPRINT("use strict directive detected: strict flag %ld -> %ld", |
| (long) comp_ctx->curr_func.is_strict, (long) 1)); |
| comp_ctx->curr_func.is_strict = 1; |
| #else |
| DUK_DDD(DUK_DDDPRINT("use strict detected but strict declarations disabled, ignoring")); |
| #endif |
| } else if (DUK_HSTRING_GET_BYTELEN(h_dir) == 14 && |
| DUK_STRNCMP((const char *) DUK_HSTRING_GET_DATA(h_dir), "use duk notail", 14) == 0) { |
| DUK_DDD(DUK_DDDPRINT("use duk notail directive detected: notail flag %ld -> %ld", |
| (long) comp_ctx->curr_func.is_notail, (long) 1)); |
| comp_ctx->curr_func.is_notail = 1; |
| } else { |
| DUK_DD(DUK_DDPRINT("unknown directive: '%!O', ignoring but not terminating " |
| "directive prologue", (duk_hobject *) h_dir)); |
| } |
| } |
| } else { |
| DUK_DDD(DUK_DDDPRINT("non-directive expression statement or no longer in prologue; " |
| "prologue terminated if still active")); |
| } |
| |
| stmt_flags |= DUK__HAS_VAL | DUK__HAS_TERM; |
| } |
| } /* end switch (tok) */ |
| |
| /* |
| * Statement value handling. |
| * |
| * Global code and eval code has an implicit return value |
| * which comes from the last statement with a value |
| * (technically a non-"empty" continuation, which is |
| * different from an empty statement). |
| * |
| * Since we don't know whether a later statement will |
| * override the value of the current statement, we need |
| * to coerce the statement value to a register allocated |
| * for implicit return values. In other cases we need |
| * to coerce the statement value to a plain value to get |
| * any side effects out (consider e.g. "foo.bar;"). |
| */ |
| |
| /* XXX: what about statements which leave a half-cooked value in 'res' |
| * but have no stmt value? Any such statements? |
| */ |
| |
| if (stmt_flags & DUK__HAS_VAL) { |
| duk_reg_t reg_stmt_value = comp_ctx->curr_func.reg_stmt_value; |
| if (reg_stmt_value >= 0) { |
| duk__ivalue_toforcedreg(comp_ctx, res, reg_stmt_value); |
| } else { |
| duk__ivalue_toplain_ignore(comp_ctx, res); |
| } |
| } else { |
| ; |
| } |
| |
| /* |
| * Statement terminator check, including automatic semicolon |
| * handling. After this step, 'curr_tok' should be the first |
| * token after a possible statement terminator. |
| */ |
| |
| if (stmt_flags & DUK__HAS_TERM) { |
| if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON) { |
| DUK_DDD(DUK_DDDPRINT("explicit semicolon terminates statement")); |
| duk__advance(comp_ctx); |
| } else { |
| if (comp_ctx->curr_token.allow_auto_semi) { |
| DUK_DDD(DUK_DDDPRINT("automatic semicolon terminates statement")); |
| } else if (stmt_flags & DUK__ALLOW_AUTO_SEMI_ALWAYS) { |
| /* XXX: make this lenience dependent on flags or strictness? */ |
| DUK_DDD(DUK_DDDPRINT("automatic semicolon terminates statement (allowed for compatibility " |
| "even though no lineterm present before next token)")); |
| } else { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_UNTERMINATED_STMT); |
| } |
| } |
| } else { |
| DUK_DDD(DUK_DDDPRINT("statement has no terminator")); |
| } |
| |
| /* |
| * Directive prologue tracking. |
| */ |
| |
| if (stmt_flags & DUK__STILL_PROLOGUE) { |
| DUK_DDD(DUK_DDDPRINT("setting in_directive_prologue")); |
| comp_ctx->curr_func.in_directive_prologue = 1; |
| } |
| |
| /* |
| * Cleanups (all statement parsing flows through here). |
| * |
| * Pop label site and reset labels. Reset 'next temp' to value at |
| * entry to reuse temps. |
| */ |
| |
| if (label_id >= 0) { |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_ENDLABEL, |
| (duk_regconst_t) label_id); |
| } |
| |
| DUK__SETTEMP(comp_ctx, temp_at_entry); |
| |
| duk__reset_labels_to_length(comp_ctx, labels_len_at_entry); |
| |
| /* XXX: return indication of "terminalness" (e.g. a 'throw' is terminal) */ |
| |
| DUK__RECURSION_DECREASE(comp_ctx, thr); |
| } |
| |
| #undef DUK__HAS_VAL |
| #undef DUK__HAS_TERM |
| #undef DUK__ALLOW_AUTO_SEMI_ALWAYS |
| |
| /* |
| * Parse a statement list. |
| * |
| * Handles automatic semicolon insertion and implicit return value. |
| * |
| * Upon entry, 'curr_tok' should contain the first token of the first |
| * statement (parsed in the "allow regexp literal" mode). Upon exit, |
| * 'curr_tok' contains the token following the statement list terminator |
| * (EOF or closing brace). |
| */ |
| |
| DUK_LOCAL void duk__parse_stmts(duk_compiler_ctx *comp_ctx, duk_bool_t allow_source_elem, duk_bool_t expect_eof) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_ivalue res_alloc; |
| duk_ivalue *res = &res_alloc; |
| |
| /* Setup state. Initial ivalue is 'undefined'. */ |
| |
| duk_require_stack(ctx, DUK__PARSE_STATEMENTS_SLOTS); |
| |
| /* XXX: 'res' setup can be moved to function body level; in fact, two 'res' |
| * intermediate values suffice for parsing of each function. Nesting is needed |
| * for nested functions (which may occur inside expressions). |
| */ |
| |
| DUK_MEMZERO(&res_alloc, sizeof(res_alloc)); |
| res->t = DUK_IVAL_PLAIN; |
| res->x1.t = DUK_ISPEC_VALUE; |
| res->x1.valstack_idx = duk_get_top(ctx); |
| res->x2.valstack_idx = res->x1.valstack_idx + 1; |
| duk_push_undefined(ctx); |
| duk_push_undefined(ctx); |
| |
| /* Parse statements until a closing token (EOF or '}') is found. */ |
| |
| for (;;) { |
| /* Check whether statement list ends. */ |
| |
| if (expect_eof) { |
| if (comp_ctx->curr_token.t == DUK_TOK_EOF) { |
| break; |
| } |
| } else { |
| if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) { |
| break; |
| } |
| } |
| |
| /* Check statement type based on the first token type. |
| * |
| * Note: expression parsing helpers expect 'curr_tok' to |
| * contain the first token of the expression upon entry. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("TOKEN %ld (non-whitespace, non-comment)", (long) comp_ctx->curr_token.t)); |
| |
| duk__parse_stmt(comp_ctx, res, allow_source_elem); |
| } |
| |
| duk__advance(comp_ctx); |
| |
| /* Tear down state. */ |
| |
| duk_pop_2(ctx); |
| } |
| |
| /* |
| * Declaration binding instantiation conceptually happens when calling a |
| * function; for us it essentially means that function prologue. The |
| * conceptual process is described in E5 Section 10.5. |
| * |
| * We need to keep track of all encountered identifiers to (1) create an |
| * identifier-to-register map ("varmap"); and (2) detect duplicate |
| * declarations. Identifiers which are not bound to registers still need |
| * to be tracked for detecting duplicates. Currently such identifiers |
| * are put into the varmap with a 'null' value, which is later cleaned up. |
| * |
| * To support functions with a large number of variable and function |
| * declarations, registers are not allocated beyond a certain limit; |
| * after that limit, variables and functions need slow path access. |
| * Arguments are currently always register bound, which imposes a hard |
| * (and relatively small) argument count limit. |
| * |
| * Some bindings in E5 are not configurable (= deletable) and almost all |
| * are mutable (writable). Exceptions are: |
| * |
| * - The 'arguments' binding, established only if no shadowing argument |
| * or function declaration exists. We handle 'arguments' creation |
| * and binding through an explicit slow path environment record. |
| * |
| * - The "name" binding for a named function expression. This is also |
| * handled through an explicit slow path environment record. |
| */ |
| |
| /* XXX: add support for variables to not be register bound always, to |
| * handle cases with a very large number of variables? |
| */ |
| |
| DUK_LOCAL void duk__init_varmap_and_prologue_for_pass2(duk_compiler_ctx *comp_ctx, duk_reg_t *out_stmt_value_reg) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_hstring *h_name; |
| duk_bool_t configurable_bindings; |
| duk_uarridx_t num_args; |
| duk_uarridx_t num_decls; |
| duk_regconst_t rc_name; |
| duk_small_uint_t declvar_flags; |
| duk_uarridx_t i; |
| #ifdef DUK_USE_ASSERTIONS |
| duk_idx_t entry_top; |
| #endif |
| |
| #ifdef DUK_USE_ASSERTIONS |
| entry_top = duk_get_top(ctx); |
| #endif |
| |
| /* |
| * Preliminaries |
| */ |
| |
| configurable_bindings = comp_ctx->curr_func.is_eval; |
| DUK_DDD(DUK_DDDPRINT("configurable_bindings=%ld", (long) configurable_bindings)); |
| |
| /* varmap is already in comp_ctx->curr_func.varmap_idx */ |
| |
| /* |
| * Function formal arguments, always bound to registers |
| * (there's no support for shuffling them now). |
| */ |
| |
| num_args = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.argnames_idx); |
| DUK_DDD(DUK_DDDPRINT("num_args=%ld", (long) num_args)); |
| /* XXX: check num_args */ |
| |
| for (i = 0; i < num_args; i++) { |
| duk_get_prop_index(ctx, comp_ctx->curr_func.argnames_idx, i); |
| h_name = duk_get_hstring(ctx, -1); |
| DUK_ASSERT(h_name != NULL); |
| |
| if (comp_ctx->curr_func.is_strict) { |
| if (duk__hstring_is_eval_or_arguments(comp_ctx, h_name)) { |
| DUK_DDD(DUK_DDDPRINT("arg named 'eval' or 'arguments' in strict mode -> SyntaxError")); |
| goto error_argname; |
| } |
| duk_dup_top(ctx); |
| if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) { |
| DUK_DDD(DUK_DDDPRINT("duplicate arg name in strict mode -> SyntaxError")); |
| goto error_argname; |
| } |
| |
| /* Ensure argument name is not a reserved word in current |
| * (final) strictness. Formal argument parsing may not |
| * catch reserved names if strictness changes during |
| * parsing. |
| * |
| * We only need to do this in strict mode because non-strict |
| * keyword are always detected in formal argument parsing. |
| */ |
| |
| if (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(h_name)) { |
| goto error_argname; |
| } |
| } |
| |
| /* overwrite any previous binding of the same name; the effect is |
| * that last argument of a certain name wins. |
| */ |
| |
| /* only functions can have arguments */ |
| DUK_ASSERT(comp_ctx->curr_func.is_function); |
| duk_push_uarridx(ctx, i); /* -> [ ... name index ] */ |
| duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); /* -> [ ... ] */ |
| |
| /* no code needs to be emitted, the regs already have values */ |
| } |
| |
| /* use temp_next for tracking register allocations */ |
| DUK__SETTEMP_CHECKMAX(comp_ctx, (duk_reg_t) num_args); |
| |
| /* |
| * After arguments, allocate special registers (like shuffling temps) |
| */ |
| |
| if (out_stmt_value_reg) { |
| *out_stmt_value_reg = DUK__ALLOCTEMP(comp_ctx); |
| } |
| if (comp_ctx->curr_func.needs_shuffle) { |
| duk_reg_t shuffle_base = DUK__ALLOCTEMPS(comp_ctx, 3); |
| comp_ctx->curr_func.shuffle1 = shuffle_base; |
| comp_ctx->curr_func.shuffle2 = shuffle_base + 1; |
| comp_ctx->curr_func.shuffle3 = shuffle_base + 2; |
| DUK_D(DUK_DPRINT("shuffle registers needed by function, allocated: %ld %ld %ld", |
| (long) comp_ctx->curr_func.shuffle1, |
| (long) comp_ctx->curr_func.shuffle2, |
| (long) comp_ctx->curr_func.shuffle3)); |
| } |
| if (comp_ctx->curr_func.temp_next > 0x100) { |
| DUK_D(DUK_DPRINT("not enough 8-bit regs: temp_next=%ld", (long) comp_ctx->curr_func.temp_next)); |
| goto error_outofregs; |
| } |
| |
| /* |
| * Function declarations |
| */ |
| |
| num_decls = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.decls_idx); |
| DUK_DDD(DUK_DDDPRINT("num_decls=%ld -> %!T", |
| (long) num_decls, |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.decls_idx))); |
| for (i = 0; i < num_decls; i += 2) { |
| duk_int_t decl_type; |
| duk_int_t fnum; |
| |
| duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i + 1); /* decl type */ |
| decl_type = duk_to_int(ctx, -1); |
| fnum = decl_type >> 8; /* XXX: macros */ |
| decl_type = decl_type & 0xff; |
| duk_pop(ctx); |
| |
| if (decl_type != DUK_DECL_TYPE_FUNC) { |
| continue; |
| } |
| |
| duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i); /* decl name */ |
| |
| /* XXX: spilling */ |
| if (comp_ctx->curr_func.is_function) { |
| duk_reg_t reg_bind; |
| duk_dup_top(ctx); |
| if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) { |
| /* shadowed; update value */ |
| duk_dup_top(ctx); |
| duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx); |
| reg_bind = duk_to_int(ctx, -1); /* [ ... name reg_bind ] */ |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_CLOSURE, |
| (duk_regconst_t) reg_bind, |
| (duk_regconst_t) fnum); |
| } else { |
| /* function: always register bound */ |
| reg_bind = DUK__ALLOCTEMP(comp_ctx); |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_CLOSURE, |
| (duk_regconst_t) reg_bind, |
| (duk_regconst_t) fnum); |
| duk_push_int(ctx, (duk_int_t) reg_bind); |
| } |
| } else { |
| /* Function declaration for global/eval code is emitted even |
| * for duplicates, because of E5 Section 10.5, step 5.e of |
| * E5.1 (special behavior for variable bound to global object). |
| * |
| * DECLVAR will not re-declare a variable as such, but will |
| * update the binding value. |
| */ |
| |
| duk_reg_t reg_temp = DUK__ALLOCTEMP(comp_ctx); |
| duk_dup_top(ctx); |
| rc_name = duk__getconst(comp_ctx); |
| duk_push_null(ctx); |
| |
| duk__emit_a_bc(comp_ctx, |
| DUK_OP_CLOSURE, |
| (duk_regconst_t) reg_temp, |
| (duk_regconst_t) fnum); |
| |
| declvar_flags = DUK_PROPDESC_FLAG_WRITABLE | |
| DUK_PROPDESC_FLAG_ENUMERABLE | |
| DUK_BC_DECLVAR_FLAG_FUNC_DECL; |
| |
| if (configurable_bindings) { |
| declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE; |
| } |
| |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_DECLVAR | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) declvar_flags /*flags*/, |
| rc_name /*name*/, |
| (duk_regconst_t) reg_temp /*value*/); |
| |
| DUK__SETTEMP(comp_ctx, reg_temp); /* forget temp */ |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("function declaration to varmap: %!T -> %!T", |
| (duk_tval *) duk_get_tval(ctx, -2), |
| (duk_tval *) duk_get_tval(ctx, -1))); |
| |
| duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); /* [ ... name reg/null ] -> [ ... ] */ |
| } |
| |
| /* |
| * 'arguments' binding is special; if a shadowing argument or |
| * function declaration exists, an arguments object will |
| * definitely not be needed, regardless of whether the identifier |
| * 'arguments' is referenced inside the function body. |
| */ |
| |
| if (duk_has_prop_stridx(ctx, comp_ctx->curr_func.varmap_idx, DUK_STRIDX_LC_ARGUMENTS)) { |
| DUK_DDD(DUK_DDDPRINT("'arguments' is shadowed by argument or function declaration " |
| "-> arguments object creation can be skipped")); |
| comp_ctx->curr_func.is_arguments_shadowed = 1; |
| } |
| |
| /* |
| * Variable declarations. |
| * |
| * Unlike function declarations, variable declaration values don't get |
| * assigned on entry. If a binding of the same name already exists, just |
| * ignore it silently. |
| */ |
| |
| for (i = 0; i < num_decls; i += 2) { |
| duk_int_t decl_type; |
| |
| duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i + 1); /* decl type */ |
| decl_type = duk_to_int(ctx, -1); |
| decl_type = decl_type & 0xff; |
| duk_pop(ctx); |
| |
| if (decl_type != DUK_DECL_TYPE_VAR) { |
| continue; |
| } |
| |
| duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i); /* decl name */ |
| |
| if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) { |
| /* shadowed, ignore */ |
| } else { |
| duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i); /* decl name */ |
| h_name = duk_get_hstring(ctx, -1); |
| DUK_ASSERT(h_name != NULL); |
| |
| if (h_name == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr) && |
| !comp_ctx->curr_func.is_arguments_shadowed) { |
| /* E5 Section steps 7-8 */ |
| DUK_DDD(DUK_DDDPRINT("'arguments' not shadowed by a function declaration, " |
| "but appears as a variable declaration -> treat as " |
| "a no-op for variable declaration purposes")); |
| duk_pop(ctx); |
| continue; |
| } |
| |
| /* XXX: spilling */ |
| if (comp_ctx->curr_func.is_function) { |
| duk_reg_t reg_bind = DUK__ALLOCTEMP(comp_ctx); |
| /* no need to init reg, it will be undefined on entry */ |
| duk_push_int(ctx, (duk_int_t) reg_bind); |
| } else { |
| duk_dup_top(ctx); |
| rc_name = duk__getconst(comp_ctx); |
| duk_push_null(ctx); |
| |
| declvar_flags = DUK_PROPDESC_FLAG_WRITABLE | |
| DUK_PROPDESC_FLAG_ENUMERABLE | |
| DUK_BC_DECLVAR_FLAG_UNDEF_VALUE; |
| if (configurable_bindings) { |
| declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE; |
| } |
| |
| duk__emit_a_b_c(comp_ctx, |
| DUK_OP_DECLVAR | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) declvar_flags /*flags*/, |
| rc_name /*name*/, |
| (duk_regconst_t) 0 /*value*/); |
| } |
| |
| duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); /* [ ... name reg/null ] -> [ ... ] */ |
| } |
| } |
| |
| /* |
| * Wrap up |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("varmap: %!T, is_arguments_shadowed=%ld", |
| (duk_tval *) duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx), |
| (long) comp_ctx->curr_func.is_arguments_shadowed)); |
| |
| DUK_ASSERT_TOP(ctx, entry_top); |
| return; |
| |
| error_outofregs: |
| DUK_ERROR_RANGE(thr, DUK_STR_REG_LIMIT); |
| DUK_UNREACHABLE(); |
| return; |
| |
| error_argname: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_ARG_NAME); |
| DUK_UNREACHABLE(); |
| return; |
| } |
| |
| /* |
| * Parse a function-body-like expression (FunctionBody or Program |
| * in E5 grammar) using a two-pass parse. The productions appear |
| * in the following contexts: |
| * |
| * - function expression |
| * - function statement |
| * - function declaration |
| * - getter in object literal |
| * - setter in object literal |
| * - global code |
| * - eval code |
| * - Function constructor body |
| * |
| * This function only parses the statement list of the body; the argument |
| * list and possible function name must be initialized by the caller. |
| * For instance, for Function constructor, the argument names are originally |
| * on the value stack. The parsing of statements ends either at an EOF or |
| * a closing brace; this is controlled by an input flag. |
| * |
| * Note that there are many differences affecting parsing and even code |
| * generation: |
| * |
| * - Global and eval code have an implicit return value generated |
| * by the last statement; function code does not |
| * |
| * - Global code, eval code, and Function constructor body end in |
| * an EOF, other bodies in a closing brace ('}') |
| * |
| * Upon entry, 'curr_tok' is ignored and the function will pull in the |
| * first token on its own. Upon exit, 'curr_tok' is the terminating |
| * token (EOF or closing brace). |
| */ |
| |
| DUK_LOCAL void duk__parse_func_body(duk_compiler_ctx *comp_ctx, duk_bool_t expect_eof, duk_bool_t implicit_return_value, duk_small_int_t expect_token) { |
| duk_compiler_func *func; |
| duk_hthread *thr; |
| duk_context *ctx; |
| duk_reg_t reg_stmt_value = -1; |
| duk_lexer_point lex_pt; |
| duk_reg_t temp_first; |
| duk_small_int_t compile_round = 1; |
| |
| DUK_ASSERT(comp_ctx != NULL); |
| |
| thr = comp_ctx->thr; |
| ctx = (duk_context *) thr; |
| DUK_ASSERT(thr != NULL); |
| |
| func = &comp_ctx->curr_func; |
| DUK_ASSERT(func != NULL); |
| |
| DUK__RECURSION_INCREASE(comp_ctx, thr); |
| |
| duk_require_stack(ctx, DUK__FUNCTION_BODY_REQUIRE_SLOTS); |
| |
| /* |
| * Store lexer position for a later rewind |
| */ |
| |
| DUK_LEXER_GETPOINT(&comp_ctx->lex, &lex_pt); |
| |
| /* |
| * Program code (global and eval code) has an implicit return value |
| * from the last statement value (e.g. eval("1; 2+3;") returns 3). |
| * This is not the case with functions. If implicit statement return |
| * value is requested, all statements are coerced to a register |
| * allocated here, and used in the implicit return statement below. |
| */ |
| |
| /* XXX: this is pointless here because pass 1 is throw-away */ |
| if (implicit_return_value) { |
| reg_stmt_value = DUK__ALLOCTEMP(comp_ctx); |
| |
| /* If an implicit return value is needed by caller, it must be |
| * initialized to 'undefined' because we don't know whether any |
| * non-empty (where "empty" is a continuation type, and different |
| * from an empty statement) statements will be executed. |
| * |
| * However, since 1st pass is a throwaway one, no need to emit |
| * it here. |
| */ |
| #if 0 |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_LDUNDEF, |
| 0); |
| #endif |
| } |
| |
| /* |
| * First pass. |
| * |
| * Gather variable/function declarations needed for second pass. |
| * Code generated is dummy and discarded. |
| */ |
| |
| func->in_directive_prologue = 1; |
| func->in_scanning = 1; |
| func->may_direct_eval = 0; |
| func->id_access_arguments = 0; |
| func->id_access_slow = 0; |
| func->reg_stmt_value = reg_stmt_value; |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| func->min_line = DUK_INT_MAX; |
| func->max_line = 0; |
| #endif |
| |
| /* duk__parse_stmts() expects curr_tok to be set; parse in "allow regexp literal" mode with current strictness */ |
| if (expect_token >= 0) { |
| /* Eating a left curly; regexp mode is allowed by left curly |
| * based on duk__token_lbp[] automatically. |
| */ |
| DUK_ASSERT(expect_token == DUK_TOK_LCURLY); |
| duk__update_lineinfo_currtoken(comp_ctx); |
| duk__advance_expect(comp_ctx, expect_token); |
| } else { |
| /* Need to set curr_token.t because lexing regexp mode depends on current |
| * token type. Zero value causes "allow regexp" mode. |
| */ |
| comp_ctx->curr_token.t = 0; |
| duk__advance(comp_ctx); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("begin 1st pass")); |
| duk__parse_stmts(comp_ctx, |
| 1, /* allow source elements */ |
| expect_eof); /* expect EOF instead of } */ |
| DUK_DDD(DUK_DDDPRINT("end 1st pass")); |
| |
| /* |
| * Second (and possibly third) pass. |
| * |
| * Generate actual code. In most cases the need for shuffle |
| * registers is detected during pass 1, but in some corner cases |
| * we'll only detect it during pass 2 and a third pass is then |
| * needed (see GH-115). |
| */ |
| |
| for (;;) { |
| duk_bool_t needs_shuffle_before = comp_ctx->curr_func.needs_shuffle; |
| compile_round++; |
| |
| /* |
| * Rewind lexer. |
| * |
| * duk__parse_stmts() expects curr_tok to be set; parse in "allow regexp |
| * literal" mode with current strictness. |
| * |
| * curr_token line number info should be initialized for pass 2 before |
| * generating prologue, to ensure prologue bytecode gets nice line numbers. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("rewind lexer")); |
| DUK_LEXER_SETPOINT(&comp_ctx->lex, &lex_pt); |
| comp_ctx->curr_token.t = 0; /* this is needed for regexp mode */ |
| comp_ctx->curr_token.start_line = 0; /* needed for line number tracking (becomes prev_token.start_line) */ |
| duk__advance(comp_ctx); |
| |
| /* |
| * Reset function state and perform register allocation, which creates |
| * 'varmap' for second pass. Function prologue for variable declarations, |
| * binding value initializations etc is emitted as a by-product. |
| * |
| * Strict mode restrictions for duplicate and invalid argument |
| * names are checked here now that we know whether the function |
| * is actually strict. See: test-dev-strict-mode-boundary.js. |
| * |
| * Inner functions are compiled during pass 1 and are not reset. |
| */ |
| |
| duk__reset_func_for_pass2(comp_ctx); |
| func->in_directive_prologue = 1; |
| func->in_scanning = 0; |
| |
| /* must be able to emit code, alloc consts, etc. */ |
| |
| duk__init_varmap_and_prologue_for_pass2(comp_ctx, |
| (implicit_return_value ? ®_stmt_value : NULL)); |
| func->reg_stmt_value = reg_stmt_value; |
| |
| temp_first = DUK__GETTEMP(comp_ctx); |
| |
| func->temp_first = temp_first; |
| func->temp_next = temp_first; |
| func->stmt_next = 0; |
| func->label_next = 0; |
| |
| /* XXX: init or assert catch depth etc -- all values */ |
| func->id_access_arguments = 0; |
| func->id_access_slow = 0; |
| |
| /* |
| * Check function name validity now that we know strictness. |
| * This only applies to function declarations and expressions, |
| * not setter/getter name. |
| * |
| * See: test-dev-strict-mode-boundary.js |
| */ |
| |
| if (func->is_function && !func->is_setget && func->h_name != NULL) { |
| if (func->is_strict) { |
| if (duk__hstring_is_eval_or_arguments(comp_ctx, func->h_name)) { |
| DUK_DDD(DUK_DDDPRINT("func name is 'eval' or 'arguments' in strict mode")); |
| goto error_funcname; |
| } |
| if (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(func->h_name)) { |
| DUK_DDD(DUK_DDDPRINT("func name is a reserved word in strict mode")); |
| goto error_funcname; |
| } |
| } else { |
| if (DUK_HSTRING_HAS_RESERVED_WORD(func->h_name) && |
| !DUK_HSTRING_HAS_STRICT_RESERVED_WORD(func->h_name)) { |
| DUK_DDD(DUK_DDDPRINT("func name is a reserved word in non-strict mode")); |
| goto error_funcname; |
| } |
| } |
| } |
| |
| /* |
| * Second pass parsing. |
| */ |
| |
| if (implicit_return_value) { |
| /* Default implicit return value. */ |
| duk__emit_extraop_bc(comp_ctx, |
| DUK_EXTRAOP_LDUNDEF, |
| 0); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("begin 2nd pass")); |
| duk__parse_stmts(comp_ctx, |
| 1, /* allow source elements */ |
| expect_eof); /* expect EOF instead of } */ |
| DUK_DDD(DUK_DDDPRINT("end 2nd pass")); |
| |
| duk__update_lineinfo_currtoken(comp_ctx); |
| |
| if (needs_shuffle_before == comp_ctx->curr_func.needs_shuffle) { |
| /* Shuffle decision not changed. */ |
| break; |
| } |
| if (compile_round >= 3) { |
| /* Should never happen but avoid infinite loop just in case. */ |
| DUK_D(DUK_DPRINT("more than 3 compile passes needed, should never happen")); |
| DUK_ERROR_INTERNAL_DEFMSG(thr); |
| } |
| DUK_D(DUK_DPRINT("need additional round to compile function, round now %d", (int) compile_round)); |
| } |
| |
| /* |
| * Emit a final RETURN. |
| * |
| * It would be nice to avoid emitting an unnecessary "return" opcode |
| * if the current PC is not reachable. However, this cannot be reliably |
| * detected; even if the previous instruction is an unconditional jump, |
| * there may be a previous jump which jumps to current PC (which is the |
| * case for iteration and conditional statements, for instance). |
| */ |
| |
| /* XXX: request a "last statement is terminal" from duk__parse_stmt() and duk__parse_stmts(); |
| * we could avoid the last RETURN if we could ensure there is no way to get here |
| * (directly or via a jump) |
| */ |
| |
| DUK_ASSERT(comp_ctx->curr_func.catch_depth == 0); |
| if (reg_stmt_value >= 0) { |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_RETURN | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) DUK_BC_RETURN_FLAG_HAVE_RETVAL /*flags*/, |
| (duk_regconst_t) reg_stmt_value /*reg*/); |
| } else { |
| duk__emit_a_b(comp_ctx, |
| DUK_OP_RETURN | DUK__EMIT_FLAG_NO_SHUFFLE_A, |
| (duk_regconst_t) 0 /*flags*/, |
| (duk_regconst_t) 0 /*reg(ignored)*/); |
| } |
| |
| /* |
| * Peephole optimize JUMP chains. |
| */ |
| |
| duk__peephole_optimize_bytecode(comp_ctx); |
| |
| /* |
| * comp_ctx->curr_func is now ready to be converted into an actual |
| * function template. |
| */ |
| |
| DUK__RECURSION_DECREASE(comp_ctx, thr); |
| return; |
| |
| error_funcname: |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_FUNC_NAME); |
| } |
| |
| /* |
| * Parse a function-like expression: |
| * |
| * - function expression |
| * - function declaration |
| * - function statement (non-standard) |
| * - setter/getter |
| * |
| * Adds the function to comp_ctx->curr_func function table and returns the |
| * function number. |
| * |
| * On entry, curr_token points to: |
| * |
| * - the token after 'function' for function expression/declaration/statement |
| * - the token after 'set' or 'get' for setter/getter |
| */ |
| |
| /* Parse formals. */ |
| DUK_LOCAL void duk__parse_func_formals(duk_compiler_ctx *comp_ctx) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_bool_t first = 1; |
| duk_uarridx_t n; |
| |
| for (;;) { |
| if (comp_ctx->curr_token.t == DUK_TOK_RPAREN) { |
| break; |
| } |
| |
| if (first) { |
| /* no comma */ |
| first = 0; |
| } else { |
| duk__advance_expect(comp_ctx, DUK_TOK_COMMA); |
| } |
| |
| /* Note: when parsing a formal list in non-strict context, e.g. |
| * "implements" is parsed as an identifier. When the function is |
| * later detected to be strict, the argument list must be rechecked |
| * against a larger set of reserved words (that of strict mode). |
| * This is handled by duk__parse_func_body(). Here we recognize |
| * whatever tokens are considered reserved in current strictness |
| * (which is not always enough). |
| */ |
| |
| if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) { |
| DUK_ERROR_SYNTAX(thr, "expected identifier"); |
| } |
| DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER); |
| DUK_ASSERT(comp_ctx->curr_token.str1 != NULL); |
| DUK_DDD(DUK_DDDPRINT("formal argument: %!O", |
| (duk_heaphdr *) comp_ctx->curr_token.str1)); |
| |
| /* XXX: append primitive */ |
| duk_push_hstring(ctx, comp_ctx->curr_token.str1); |
| n = (duk_uarridx_t) duk_get_length(ctx, comp_ctx->curr_func.argnames_idx); |
| duk_put_prop_index(ctx, comp_ctx->curr_func.argnames_idx, n); |
| |
| duk__advance(comp_ctx); /* eat identifier */ |
| } |
| } |
| |
| /* Parse a function-like expression, assuming that 'comp_ctx->curr_func' is |
| * correctly set up. Assumes that curr_token is just after 'function' (or |
| * 'set'/'get' etc). |
| */ |
| DUK_LOCAL void duk__parse_func_like_raw(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| |
| DUK_ASSERT(comp_ctx->curr_func.num_formals == 0); |
| DUK_ASSERT(comp_ctx->curr_func.is_function == 1); |
| DUK_ASSERT(comp_ctx->curr_func.is_eval == 0); |
| DUK_ASSERT(comp_ctx->curr_func.is_global == 0); |
| DUK_ASSERT(comp_ctx->curr_func.is_setget == is_setget); |
| DUK_ASSERT(comp_ctx->curr_func.is_decl == is_decl); |
| |
| duk__update_lineinfo_currtoken(comp_ctx); |
| |
| /* |
| * Function name (if any) |
| * |
| * We don't check for prohibited names here, because we don't |
| * yet know whether the function will be strict. Function body |
| * parsing handles this retroactively. |
| * |
| * For function expressions and declarations function name must |
| * be an Identifer (excludes reserved words). For setter/getter |
| * it is a PropertyName which allows reserved words and also |
| * strings and numbers (e.g. "{ get 1() { ... } }"). |
| */ |
| |
| if (is_setget) { |
| /* PropertyName -> IdentifierName | StringLiteral | NumericLiteral */ |
| if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER || |
| comp_ctx->curr_token.t == DUK_TOK_STRING) { |
| duk_push_hstring(ctx, comp_ctx->curr_token.str1); /* keep in valstack */ |
| } else if (comp_ctx->curr_token.t == DUK_TOK_NUMBER) { |
| duk_push_number(ctx, comp_ctx->curr_token.num); |
| duk_to_string(ctx, -1); |
| } else { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_GETSET_NAME); |
| } |
| comp_ctx->curr_func.h_name = duk_get_hstring(ctx, -1); /* borrowed reference */ |
| DUK_ASSERT(comp_ctx->curr_func.h_name != NULL); |
| duk__advance(comp_ctx); |
| } else { |
| /* Function name is an Identifier (not IdentifierName), but we get |
| * the raw name (not recognizing keywords) here and perform the name |
| * checks only after pass 1. |
| */ |
| if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER) { |
| duk_push_hstring(ctx, comp_ctx->curr_token.str1); /* keep in valstack */ |
| comp_ctx->curr_func.h_name = duk_get_hstring(ctx, -1); /* borrowed reference */ |
| DUK_ASSERT(comp_ctx->curr_func.h_name != NULL); |
| duk__advance(comp_ctx); |
| } else { |
| /* valstack will be unbalanced, which is OK */ |
| DUK_ASSERT(!is_setget); |
| if (is_decl) { |
| DUK_ERROR_SYNTAX(thr, DUK_STR_FUNC_NAME_REQUIRED); |
| } |
| } |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("function name: %!O", |
| (duk_heaphdr *) comp_ctx->curr_func.h_name)); |
| |
| /* |
| * Formal argument list |
| * |
| * We don't check for prohibited names or for duplicate argument |
| * names here, becase we don't yet know whether the function will |
| * be strict. Function body parsing handles this retroactively. |
| */ |
| |
| duk__advance_expect(comp_ctx, DUK_TOK_LPAREN); |
| |
| duk__parse_func_formals(comp_ctx); |
| |
| DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RPAREN); |
| duk__advance(comp_ctx); |
| |
| /* |
| * Parse function body |
| */ |
| |
| duk__parse_func_body(comp_ctx, |
| 0, /* expect_eof */ |
| 0, /* implicit_return_value */ |
| DUK_TOK_LCURLY); /* expect_token */ |
| |
| /* |
| * Convert duk_compiler_func to a function template and add it |
| * to the parent function table. |
| */ |
| |
| duk__convert_to_func_template(comp_ctx, is_setget /*force_no_namebind*/); /* -> [ ... func ] */ |
| } |
| |
| /* Parse an inner function, adding the function template to the current function's |
| * function table. Return a function number to be used by the outer function. |
| * |
| * Avoiding O(depth^2) inner function parsing is handled here. On the first pass, |
| * compile and register the function normally into the 'funcs' array, also recording |
| * a lexer point (offset/line) to the closing brace of the function. On the second |
| * pass, skip the function and return the same 'fnum' as on the first pass by using |
| * a running counter. |
| * |
| * An unfortunate side effect of this is that when parsing the inner function, almost |
| * nothing is known of the outer function, i.e. the inner function's scope. We don't |
| * need that information at the moment, but it would allow some optimizations if it |
| * were used. |
| */ |
| DUK_LOCAL duk_int_t duk__parse_func_like_fnum(duk_compiler_ctx *comp_ctx, duk_bool_t is_decl, duk_bool_t is_setget) { |
| duk_hthread *thr = comp_ctx->thr; |
| duk_context *ctx = (duk_context *) thr; |
| duk_compiler_func old_func; |
| duk_idx_t entry_top; |
| duk_int_t fnum; |
| |
| /* |
| * On second pass, skip the function. |
| */ |
| |
| if (!comp_ctx->curr_func.in_scanning) { |
| duk_lexer_point lex_pt; |
| |
| fnum = comp_ctx->curr_func.fnum_next++; |
| duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 1)); |
| lex_pt.offset = duk_to_int(ctx, -1); |
| duk_pop(ctx); |
| duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 2)); |
| lex_pt.line = duk_to_int(ctx, -1); |
| duk_pop(ctx); |
| |
| DUK_DDD(DUK_DDDPRINT("second pass of an inner func, skip the function, reparse closing brace; lex offset=%ld, line=%ld", |
| (long) lex_pt.offset, (long) lex_pt.line)); |
| |
| DUK_LEXER_SETPOINT(&comp_ctx->lex, &lex_pt); |
| comp_ctx->curr_token.t = 0; /* this is needed for regexp mode */ |
| comp_ctx->curr_token.start_line = 0; /* needed for line number tracking (becomes prev_token.start_line) */ |
| duk__advance(comp_ctx); |
| duk__advance_expect(comp_ctx, DUK_TOK_RCURLY); |
| |
| return fnum; |
| } |
| |
| /* |
| * On first pass, perform actual parsing. Remember valstack top on entry |
| * to restore it later, and switch to using a new function in comp_ctx. |
| */ |
| |
| entry_top = duk_get_top(ctx); |
| DUK_DDD(DUK_DDDPRINT("before func: entry_top=%ld, curr_tok.start_offset=%ld", |
| (long) entry_top, (long) comp_ctx->curr_token.start_offset)); |
| |
| DUK_MEMCPY(&old_func, &comp_ctx->curr_func, sizeof(duk_compiler_func)); |
| |
| DUK_MEMZERO(&comp_ctx->curr_func, sizeof(duk_compiler_func)); |
| duk__init_func_valstack_slots(comp_ctx); |
| DUK_ASSERT(comp_ctx->curr_func.num_formals == 0); |
| |
| /* inherit initial strictness from parent */ |
| comp_ctx->curr_func.is_strict = old_func.is_strict; |
| |
| DUK_ASSERT(comp_ctx->curr_func.is_notail == 0); |
| comp_ctx->curr_func.is_function = 1; |
| DUK_ASSERT(comp_ctx->curr_func.is_eval == 0); |
| DUK_ASSERT(comp_ctx->curr_func.is_global == 0); |
| comp_ctx->curr_func.is_setget = is_setget; |
| comp_ctx->curr_func.is_decl = is_decl; |
| |
| /* |
| * Parse inner function |
| */ |
| |
| duk__parse_func_like_raw(comp_ctx, is_decl, is_setget); /* pushes function template */ |
| |
| /* prev_token.start_offset points to the closing brace here; when skipping |
| * we're going to reparse the closing brace to ensure semicolon insertion |
| * etc work as expected. |
| */ |
| DUK_DDD(DUK_DDDPRINT("after func: prev_tok.start_offset=%ld, curr_tok.start_offset=%ld", |
| (long) comp_ctx->prev_token.start_offset, (long) comp_ctx->curr_token.start_offset)); |
| DUK_ASSERT(comp_ctx->lex.input[comp_ctx->prev_token.start_offset] == (duk_uint8_t) DUK_ASC_RCURLY); |
| |
| /* XXX: append primitive */ |
| DUK_ASSERT(duk_get_length(ctx, old_func.funcs_idx) == (duk_size_t) (old_func.fnum_next * 3)); |
| fnum = old_func.fnum_next++; |
| |
| if (fnum > DUK__MAX_FUNCS) { |
| DUK_ERROR_RANGE(comp_ctx->thr, DUK_STR_FUNC_LIMIT); |
| } |
| |
| /* array writes autoincrement length */ |
| (void) duk_put_prop_index(ctx, old_func.funcs_idx, (duk_uarridx_t) (fnum * 3)); |
| duk_push_size_t(ctx, comp_ctx->prev_token.start_offset); |
| (void) duk_put_prop_index(ctx, old_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 1)); |
| duk_push_int(ctx, comp_ctx->prev_token.start_line); |
| (void) duk_put_prop_index(ctx, old_func.funcs_idx, (duk_uarridx_t) (fnum * 3 + 2)); |
| |
| /* |
| * Cleanup: restore original function, restore valstack state. |
| */ |
| |
| DUK_MEMCPY((void *) &comp_ctx->curr_func, (void *) &old_func, sizeof(duk_compiler_func)); |
| duk_set_top(ctx, entry_top); |
| |
| DUK_ASSERT_TOP(ctx, entry_top); |
| |
| return fnum; |
| } |
| |
| /* |
| * Compile input string into an executable function template without |
| * arguments. |
| * |
| * The string is parsed as the "Program" production of Ecmascript E5. |
| * Compilation context can be either global code or eval code (see E5 |
| * Sections 14 and 15.1.2.1). |
| * |
| * Input stack: [ ... filename ] |
| * Output stack: [ ... func_template ] |
| */ |
| |
| /* XXX: source code property */ |
| |
| DUK_LOCAL duk_ret_t duk__js_compile_raw(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hstring *h_filename; |
| duk__compiler_stkstate *comp_stk; |
| duk_compiler_ctx *comp_ctx; |
| duk_lexer_point *lex_pt; |
| duk_compiler_func *func; |
| duk_idx_t entry_top; |
| duk_bool_t is_strict; |
| duk_bool_t is_eval; |
| duk_bool_t is_funcexpr; |
| duk_small_uint_t flags; |
| |
| DUK_ASSERT(thr != NULL); |
| |
| /* |
| * Arguments check |
| */ |
| |
| entry_top = duk_get_top(ctx); |
| DUK_ASSERT(entry_top >= 2); |
| |
| comp_stk = (duk__compiler_stkstate *) duk_require_pointer(ctx, -1); |
| comp_ctx = &comp_stk->comp_ctx_alloc; |
| lex_pt = &comp_stk->lex_pt_alloc; |
| DUK_ASSERT(comp_ctx != NULL); |
| DUK_ASSERT(lex_pt != NULL); |
| |
| flags = comp_stk->flags; |
| is_eval = (flags & DUK_JS_COMPILE_FLAG_EVAL ? 1 : 0); |
| is_strict = (flags & DUK_JS_COMPILE_FLAG_STRICT ? 1 : 0); |
| is_funcexpr = (flags & DUK_JS_COMPILE_FLAG_FUNCEXPR ? 1 : 0); |
| |
| h_filename = duk_get_hstring(ctx, -2); /* may be undefined */ |
| |
| /* |
| * Init compiler and lexer contexts |
| */ |
| |
| func = &comp_ctx->curr_func; |
| #ifdef DUK_USE_EXPLICIT_NULL_INIT |
| comp_ctx->thr = NULL; |
| comp_ctx->h_filename = NULL; |
| comp_ctx->prev_token.str1 = NULL; |
| comp_ctx->prev_token.str2 = NULL; |
| comp_ctx->curr_token.str1 = NULL; |
| comp_ctx->curr_token.str2 = NULL; |
| #endif |
| |
| duk_require_stack(ctx, DUK__COMPILE_ENTRY_SLOTS); |
| |
| duk_push_dynamic_buffer(ctx, 0); /* entry_top + 0 */ |
| duk_push_undefined(ctx); /* entry_top + 1 */ |
| duk_push_undefined(ctx); /* entry_top + 2 */ |
| duk_push_undefined(ctx); /* entry_top + 3 */ |
| duk_push_undefined(ctx); /* entry_top + 4 */ |
| |
| comp_ctx->thr = thr; |
| comp_ctx->h_filename = h_filename; |
| comp_ctx->tok11_idx = entry_top + 1; |
| comp_ctx->tok12_idx = entry_top + 2; |
| comp_ctx->tok21_idx = entry_top + 3; |
| comp_ctx->tok22_idx = entry_top + 4; |
| comp_ctx->recursion_limit = DUK_USE_COMPILER_RECLIMIT; |
| |
| /* comp_ctx->lex has been pre-initialized by caller: it has been |
| * zeroed and input/input_length has been set. |
| */ |
| comp_ctx->lex.thr = thr; |
| /* comp_ctx->lex.input and comp_ctx->lex.input_length filled by caller */ |
| comp_ctx->lex.slot1_idx = comp_ctx->tok11_idx; |
| comp_ctx->lex.slot2_idx = comp_ctx->tok12_idx; |
| comp_ctx->lex.buf_idx = entry_top + 0; |
| comp_ctx->lex.buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 0); |
| DUK_ASSERT(comp_ctx->lex.buf != NULL); |
| DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(comp_ctx->lex.buf) && !DUK_HBUFFER_HAS_EXTERNAL(comp_ctx->lex.buf)); |
| comp_ctx->lex.token_limit = DUK_COMPILER_TOKEN_LIMIT; |
| |
| lex_pt->offset = 0; |
| lex_pt->line = 1; |
| DUK_LEXER_SETPOINT(&comp_ctx->lex, lex_pt); /* fills window */ |
| comp_ctx->curr_token.start_line = 0; /* needed for line number tracking (becomes prev_token.start_line) */ |
| |
| /* |
| * Initialize function state for a zero-argument function |
| */ |
| |
| duk__init_func_valstack_slots(comp_ctx); |
| DUK_ASSERT(func->num_formals == 0); |
| |
| if (is_funcexpr) { |
| /* Name will be filled from function expression, not by caller. |
| * This case is used by Function constructor and duk_compile() |
| * API with the DUK_COMPILE_FUNCTION option. |
| */ |
| DUK_ASSERT(func->h_name == NULL); |
| } else { |
| duk_push_hstring_stridx(ctx, (is_eval ? DUK_STRIDX_EVAL : |
| DUK_STRIDX_GLOBAL)); |
| func->h_name = duk_get_hstring(ctx, -1); |
| } |
| |
| /* |
| * Parse a function body or a function-like expression, depending |
| * on flags. |
| */ |
| |
| func->is_strict = is_strict; |
| func->is_setget = 0; |
| func->is_decl = 0; |
| |
| if (is_funcexpr) { |
| func->is_function = 1; |
| func->is_eval = 0; |
| func->is_global = 0; |
| |
| duk__advance(comp_ctx); /* init 'curr_token' */ |
| duk__advance_expect(comp_ctx, DUK_TOK_FUNCTION); |
| (void) duk__parse_func_like_raw(comp_ctx, |
| 0, /* is_decl */ |
| 0); /* is_setget */ |
| } else { |
| func->is_function = 0; |
| func->is_eval = is_eval; |
| func->is_global = !is_eval; |
| |
| duk__parse_func_body(comp_ctx, |
| 1, /* expect_eof */ |
| 1, /* implicit_return_value */ |
| -1); /* expect_token */ |
| } |
| |
| /* |
| * Convert duk_compiler_func to a function template |
| */ |
| |
| duk__convert_to_func_template(comp_ctx, 0 /*force_no_namebind*/); |
| |
| /* |
| * Wrapping duk_safe_call() will mangle the stack, just return stack top |
| */ |
| |
| /* [ ... filename (temps) func ] */ |
| |
| return 1; |
| } |
| |
| DUK_INTERNAL void duk_js_compile(duk_hthread *thr, const duk_uint8_t *src_buffer, duk_size_t src_length, duk_small_uint_t flags) { |
| duk_context *ctx = (duk_context *) thr; |
| duk__compiler_stkstate comp_stk; |
| duk_compiler_ctx *prev_ctx; |
| duk_ret_t safe_rc; |
| |
| /* XXX: this illustrates that a C catchpoint implemented using duk_safe_call() |
| * is a bit heavy at the moment. The wrapper compiles to ~180 bytes on x64. |
| * Alternatives would be nice. |
| */ |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(src_buffer != NULL); |
| |
| /* preinitialize lexer state partially */ |
| DUK_MEMZERO(&comp_stk, sizeof(comp_stk)); |
| comp_stk.flags = flags; |
| DUK_LEXER_INITCTX(&comp_stk.comp_ctx_alloc.lex); |
| comp_stk.comp_ctx_alloc.lex.input = src_buffer; |
| comp_stk.comp_ctx_alloc.lex.input_length = src_length; |
| |
| duk_push_pointer(ctx, (void *) &comp_stk); |
| |
| /* [ ... filename &comp_stk ] */ |
| |
| prev_ctx = thr->compile_ctx; |
| thr->compile_ctx = &comp_stk.comp_ctx_alloc; /* for duk_error_augment.c */ |
| safe_rc = duk_safe_call(ctx, duk__js_compile_raw, 2 /*nargs*/, 1 /*nret*/); |
| thr->compile_ctx = prev_ctx; /* must restore reliably before returning */ |
| |
| if (safe_rc != DUK_EXEC_SUCCESS) { |
| duk_throw(ctx); |
| } |
| |
| /* [ ... template ] */ |
| } |