mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-18 09:50:27 +00:00
The asm interpreter already inlines ECMAScript calls, but builtin calls still went through the generic C++ Call slow path even when the callee was a plain native function pointer. That added an avoidable boundary around hot builtin calls and kept asm from taking full advantage of the new RawNativeFunction representation. Teach the asm Call handler to recognize RawNativeFunction, allocate the callee frame on the interpreter stack, copy the call-site arguments, and jump straight to the stored C++ entry point. NativeJavaScriptBackedFunction and other non-raw callees keep falling through to the existing C++ slow path unchanged.
2783 lines
93 KiB
NASM
2783 lines
93 KiB
NASM
# AsmInterpreter DSL source
|
|
# Each handler implements one bytecode instruction.
|
|
# Instructions not listed here fall through to the C++ fallback handler.
|
|
#
|
|
# For the full DSL instruction reference, see AsmIntGen/src/main.rs.
|
|
#
|
|
# Register conventions (pinned in callee-saved regs, survive C++ calls):
|
|
# pc = program counter (byte offset into bytecode, 32-bit)
|
|
# pb = bytecode base pointer (u8 const*)
|
|
# values = pointer to Value[] array (registers+constants+locals+arguments)
|
|
# exec_ctx = running ExecutionContext*
|
|
# dispatch = dispatch table base pointer (256 entries, 8 bytes each)
|
|
#
|
|
# Temporary registers (caller-saved, clobbered by C++ calls):
|
|
# t0-t8 = general-purpose scratch
|
|
# ft0-ft3 = floating-point scratch (scalar double)
|
|
#
|
|
# NaN-boxing encoding:
|
|
# Every JS Value is a 64-bit NaN-boxed value. The upper 16 bits encode the type tag.
|
|
# If tag == 0x0000..0xFFF8, the value is a double (IEEE 754 double-precision).
|
|
# Otherwise, the tag identifies the type:
|
|
# INT32_TAG (0xFFF9) - Lower 32 bits are a sign-extended int32
|
|
# BOOLEAN_TAG - Bit 0 = true/false
|
|
# UNDEFINED_TAG - undefined (0x7FFE)
|
|
# NULL_TAG - null (0x7FFF)
|
|
# OBJECT_TAG - Lower 48 bits are a sign-extended Object*
|
|
# STRING_TAG - Lower 48 bits are a sign-extended PrimitiveString*
|
|
# SYMBOL_TAG - Lower 48 bits are a sign-extended Symbol*
|
|
# BIGINT_TAG - Lower 48 bits are a sign-extended BigInt*
|
|
# ACCESSOR_TAG - Internal accessor marker
|
|
# EMPTY_TAG - Internal empty/hole marker (used for TDZ)
|
|
# A double whose exponent+mantissa bits match CANON_NAN_BITS would collide
|
|
# with the tag space, so all NaN values are canonicalized to CANON_NAN_BITS.
|
|
# The *_SHIFTED constants (e.g. INT32_TAG_SHIFTED, UNDEFINED_SHIFTED) are
|
|
# the tag shifted left by 48 bits, used for quick full-value comparisons.
|
|
#
|
|
# Instruction field references:
|
|
# Inside a handler, m_fieldname (e.g. m_dst, m_src, m_lhs) resolves to the
|
|
# byte offset of that field within the current handler's bytecode instruction.
|
|
# Field offsets are computed from Bytecode.def by the asmintgen compiler.
|
|
#
|
|
# Handler structure:
|
|
# Each handler follows a common pattern:
|
|
# 1. Load operands (load_operand)
|
|
# 2. Type-check via tag extraction (extract_tag + branch)
|
|
# 3. Fast path for int32 and/or double
|
|
# 4. Slow path fallback to C++ (call_slow_path)
|
|
# call_slow_path is TERMINAL: control does not return to the handler.
|
|
# call_helper and call_interp are NON-TERMINAL: the handler continues after.
|
|
|
|
|
|
# NOTE: extract_tag, unbox_int32, unbox_object, box_int32, and
|
|
# box_int32_clean are codegen instructions (not macros), allowing each
|
|
# backend to emit optimal platform-specific code.
|
|
#
|
|
# extract_tag dst, src -- Extract upper 16-bit NaN-boxing tag.
|
|
# unbox_int32 dst, src -- Sign-extend low 32 bits to 64.
|
|
# unbox_object dst, src -- Zero-extend lower 48 bits (extract pointer).
|
|
# box_int32 dst, src -- NaN-box a raw int32 (masks low 32, sets tag).
|
|
# box_int32_clean dst, src -- NaN-box an already zero-extended int32.
|
|
|
|
# Check if a value is a double (not a NaN-boxed tagged value).
|
|
# All tagged types have (tag & NAN_BASE_TAG) == NAN_BASE_TAG in their upper 16 bits.
|
|
# Clobbers t3. Jumps to fail if not a double.
|
|
macro check_is_double(reg, fail)
|
|
extract_tag t3, reg
|
|
and t3, NAN_BASE_TAG
|
|
branch_eq t3, NAN_BASE_TAG, fail
|
|
end
|
|
|
|
# Check if an already-extracted tag represents a non-double type.
|
|
# Clobbers the tag register. Jumps to fail if not a double.
|
|
macro check_tag_is_double(tag, fail)
|
|
and tag, NAN_BASE_TAG
|
|
branch_eq tag, NAN_BASE_TAG, fail
|
|
end
|
|
|
|
# Check if both values are doubles.
|
|
# Clobbers t3, t4. Jumps to fail if either is not a double.
|
|
macro check_both_double(lhs, rhs, fail)
|
|
extract_tag t3, lhs
|
|
and t3, NAN_BASE_TAG
|
|
branch_eq t3, NAN_BASE_TAG, fail
|
|
extract_tag t4, rhs
|
|
and t4, NAN_BASE_TAG
|
|
branch_eq t4, NAN_BASE_TAG, fail
|
|
end
|
|
|
|
# Coerce two operands (already in t1/t2) to numeric types for arithmetic/comparison.
|
|
# If both are int32: jumps to both_int_label with t3=sign-extended lhs, t4=sign-extended rhs.
|
|
# If one or both are double: falls through with ft0=lhs as double, ft1=rhs as double.
|
|
# If either is not a number (int32 or double): jumps to fail.
|
|
# Clobbers t3, t4.
|
|
macro coerce_to_doubles(both_int_label, fail)
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .lhs_not_int
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .int_rhs_maybe_double
|
|
# Both int32
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
jmp both_int_label
|
|
.int_rhs_maybe_double:
|
|
# t4 already has rhs tag, known != INT32_TAG
|
|
check_tag_is_double t4, fail
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
fp_mov ft1, t2
|
|
jmp .coerced
|
|
.lhs_not_int:
|
|
# t3 already has lhs tag, known != INT32_TAG
|
|
check_tag_is_double t3, fail
|
|
extract_tag t4, t2
|
|
branch_eq t4, INT32_TAG, .double_rhs_int
|
|
# t4 already has rhs tag, known != INT32_TAG
|
|
check_tag_is_double t4, fail
|
|
fp_mov ft0, t1
|
|
fp_mov ft1, t2
|
|
jmp .coerced
|
|
.double_rhs_int:
|
|
fp_mov ft0, t1
|
|
unbox_int32 t4, t2
|
|
int_to_double ft1, t4
|
|
.coerced:
|
|
end
|
|
|
|
# NOTE: canonicalize_nan is a codegen instruction, not a macro.
|
|
# canonicalize_nan dst_gpr, src_fpr
|
|
# If src_fpr is NaN, writes CANON_NAN_BITS to dst_gpr.
|
|
# Otherwise bitwise-copies src_fpr to dst_gpr.
|
|
|
|
# Box a double result as a JS::Value, preferring Int32 when the double is a
|
|
# whole number in [INT32_MIN, INT32_MAX] and not -0.0. This mirrors the
|
|
# JS::Value(double) constructor so that downstream int32 fast paths fire.
|
|
# dst: destination GPR for the boxed value.
|
|
# src_fpr: source FPR containing the double result.
|
|
# Clobbers: t1 (x86-64), t3, ft3 (x86-64).
|
|
macro box_double_or_int32(dst, src_fpr)
|
|
double_to_int32 t3, src_fpr, .bdi_not_int
|
|
branch_zero t3, .bdi_check_neg_zero
|
|
box_int32 dst, t3
|
|
jmp .bdi_done
|
|
.bdi_check_neg_zero:
|
|
fp_mov dst, src_fpr
|
|
branch_negative dst, .bdi_not_int
|
|
box_int32 dst, t3
|
|
jmp .bdi_done
|
|
.bdi_not_int:
|
|
canonicalize_nan dst, src_fpr
|
|
.bdi_done:
|
|
end
|
|
|
|
# Shared same-tag equality dispatch.
|
|
# Expects t3=lhs_tag (known equal to rhs_tag), t1=lhs, t2=rhs.
|
|
# For int32, boolean, object, symbol, undefined, null: bitwise compare.
|
|
# For string: pointer shortcut, else slow. For bigint: always slow.
|
|
# Falls through to .double_compare for doubles.
|
|
macro equality_same_tag(equal_label, not_equal_label, slow_label)
|
|
branch_any_eq t3, INT32_TAG, BOOLEAN_TAG, .fast_compare
|
|
branch_any_eq t3, OBJECT_TAG, SYMBOL_TAG, .fast_compare
|
|
branch_eq t3, STRING_TAG, .string_compare
|
|
branch_eq t3, BIGINT_TAG, slow_label
|
|
# Check undefined/null: (tag & 0xFFFE) == UNDEFINED_TAG matches both.
|
|
# Safe to clobber t3 here since all other tagged types are handled above.
|
|
and t3, 0xFFFE
|
|
branch_eq t3, UNDEFINED_TAG, .fast_compare
|
|
# Must be a double
|
|
jmp .double_compare
|
|
.string_compare:
|
|
branch_eq t1, t2, equal_label
|
|
jmp slow_label
|
|
.fast_compare:
|
|
branch_eq t1, t2, equal_label
|
|
jmp not_equal_label
|
|
end
|
|
|
|
# Compare t1/t2 as doubles with NaN awareness.
|
|
# Defines .double_compare label (referenced by equality_same_tag).
|
|
macro double_equality_compare(equal_label, not_equal_label)
|
|
.double_compare:
|
|
fp_mov ft0, t1
|
|
fp_mov ft1, t2
|
|
branch_fp_unordered ft0, ft1, not_equal_label
|
|
branch_fp_equal ft0, ft1, equal_label
|
|
jmp not_equal_label
|
|
end
|
|
|
|
# Strict equality check core logic.
|
|
# Expects t1=lhs, t2=rhs. Extracts tags into t3/t4.
|
|
# Jumps to equal_label if definitely equal, not_equal_label if definitely not,
|
|
# or slow_label if we can't determine quickly.
|
|
# Handles: int32, boolean, undefined/null, object, symbol (bitwise compare),
|
|
# string (pointer shortcut), bigint (slow), doubles.
|
|
macro strict_equality_core(equal_label, not_equal_label, slow_label)
|
|
extract_tag t3, t1
|
|
extract_tag t4, t2
|
|
branch_ne t3, t4, .diff_tags
|
|
equality_same_tag equal_label, not_equal_label, slow_label
|
|
double_equality_compare equal_label, not_equal_label
|
|
.diff_tags:
|
|
# Different tags but possibly equal: int32(1) === double(1.0) is true.
|
|
# Handle int32 vs double inline; all other tag mismatches are not equal.
|
|
branch_eq t3, INT32_TAG, .lhs_int32_diff
|
|
branch_eq t4, INT32_TAG, .rhs_int32_diff
|
|
# Neither is int32. If both are doubles, compare. Otherwise not equal.
|
|
# t3/t4 already have the tags, check them directly.
|
|
check_tag_is_double t3, not_equal_label
|
|
check_tag_is_double t4, not_equal_label
|
|
jmp .double_compare
|
|
.lhs_int32_diff:
|
|
# t4 already has rhs tag
|
|
check_tag_is_double t4, not_equal_label
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
fp_mov ft1, t2
|
|
branch_fp_equal ft0, ft1, equal_label
|
|
jmp not_equal_label
|
|
.rhs_int32_diff:
|
|
# t3 already has lhs tag
|
|
check_tag_is_double t3, not_equal_label
|
|
fp_mov ft0, t1
|
|
unbox_int32 t4, t2
|
|
int_to_double ft1, t4
|
|
branch_fp_equal ft0, ft1, equal_label
|
|
jmp not_equal_label
|
|
end
|
|
|
|
# Loose equality check core logic.
|
|
# Same as strict_equality_core but with null==undefined cross-type handling.
|
|
macro loose_equality_core(equal_label, not_equal_label, slow_label)
|
|
extract_tag t3, t1
|
|
extract_tag t4, t2
|
|
branch_ne t3, t4, .diff_tags
|
|
equality_same_tag equal_label, not_equal_label, slow_label
|
|
double_equality_compare equal_label, not_equal_label
|
|
.diff_tags:
|
|
# null == undefined (and vice versa): (tag & 0xFFFE) == UNDEFINED_TAG
|
|
and t3, 0xFFFE
|
|
branch_ne t3, UNDEFINED_TAG, .try_double
|
|
and t4, 0xFFFE
|
|
branch_eq t4, UNDEFINED_TAG, equal_label
|
|
jmp slow_label
|
|
.try_double:
|
|
check_both_double t1, t2, slow_label
|
|
jmp .double_compare
|
|
end
|
|
|
|
# Numeric compare with coercion (for jump variants).
|
|
# Uses coerce_to_doubles to handle mixed int32+double operands.
|
|
# Expects t1=lhs, t2=rhs (NaN-boxed values).
|
|
# int_cc: signed comparison branch for int32 (branch_lt_signed, etc.)
|
|
# double_cc: unsigned comparison branch for doubles (branch_fp_less, etc.)
|
|
# Jumps to true_label/false_label/slow_label.
|
|
macro numeric_compare_coerce(int_cc, double_cc, true_label, false_label, slow_label)
|
|
coerce_to_doubles .both_int, slow_label
|
|
branch_fp_unordered ft0, ft1, false_label
|
|
double_cc ft0, ft1, true_label
|
|
jmp false_label
|
|
.both_int:
|
|
int_cc t3, t4, true_label
|
|
jmp false_label
|
|
end
|
|
|
|
# Numeric compare without coercion (for non-jump variants).
|
|
# Only handles both-int32 or both-double fast paths.
|
|
# Expects t1=lhs, t2=rhs (NaN-boxed values).
|
|
macro numeric_compare(int_cc, double_cc, true_label, false_label, slow_label)
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .try_double
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, slow_label
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
int_cc t3, t4, true_label
|
|
jmp false_label
|
|
.try_double:
|
|
# t3 already has lhs tag
|
|
check_tag_is_double t3, slow_label
|
|
check_is_double t2, slow_label
|
|
fp_mov ft0, t1
|
|
fp_mov ft1, t2
|
|
branch_fp_unordered ft0, ft1, false_label
|
|
double_cc ft0, ft1, true_label
|
|
jmp false_label
|
|
end
|
|
|
|
# Epilogue for comparison/equality handlers that produce a boolean result.
|
|
# Defines .store_true, .store_false, and .slow labels.
|
|
macro boolean_result_epilogue(slow_path_func)
|
|
.store_true:
|
|
mov t0, BOOLEAN_TRUE
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.store_false:
|
|
mov t0, BOOLEAN_FALSE
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path slow_path_func
|
|
end
|
|
|
|
# Epilogue for jump comparison/equality handlers.
|
|
# Defines .take_true, .take_false, and .slow labels.
|
|
macro jump_binary_epilogue(slow_path_func)
|
|
.slow:
|
|
call_slow_path slow_path_func
|
|
.take_true:
|
|
load_label t0, m_true_target
|
|
goto_handler t0
|
|
.take_false:
|
|
load_label t0, m_false_target
|
|
goto_handler t0
|
|
end
|
|
|
|
# Coerce two operands (already in t1/t2) to int32 for bitwise operations.
|
|
# On success: t3 = lhs as int32, t4 = rhs as int32. Falls through.
|
|
# If either operand is not a number (int32, boolean, or double): jumps to fail.
|
|
# Clobbers t1 (on x86_64, js_to_int32 clobbers rcx=t1), t3, t4.
|
|
macro coerce_to_int32s(fail)
|
|
extract_tag t3, t1
|
|
branch_any_eq t3, INT32_TAG, BOOLEAN_TAG, .lhs_is_int
|
|
check_tag_is_double t3, fail
|
|
fp_mov ft0, t1
|
|
js_to_int32 t3, ft0, fail
|
|
jmp .lhs_done
|
|
.lhs_is_int:
|
|
unbox_int32 t3, t1
|
|
.lhs_done:
|
|
extract_tag t4, t2
|
|
branch_any_eq t4, INT32_TAG, BOOLEAN_TAG, .rhs_is_int
|
|
check_tag_is_double t4, fail
|
|
fp_mov ft0, t2
|
|
js_to_int32 t4, ft0, fail
|
|
jmp .rhs_done
|
|
.rhs_is_int:
|
|
unbox_int32 t4, t2
|
|
.rhs_done:
|
|
end
|
|
|
|
# Fast path for bitwise binary operations on int32/boolean/double operands.
|
|
# op_insn: the bitwise instruction to apply (xor, and, or).
|
|
macro bitwise_op(op_insn, slow_path_func)
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
coerce_to_int32s .slow
|
|
op_insn t3, t4
|
|
box_int32 t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path slow_path_func
|
|
end
|
|
|
|
# Validate that the callee still points at the expected builtin function.
|
|
# Jumps to fail if the call target has been replaced or is not a function.
|
|
macro validate_callee_builtin(expected_builtin, fail)
|
|
load_operand t2, m_callee
|
|
extract_tag t3, t2
|
|
branch_ne t3, OBJECT_TAG, fail
|
|
unbox_object t2, t2
|
|
load8 t3, [t2, OBJECT_FLAGS]
|
|
and t3, OBJECT_FLAG_IS_FUNCTION
|
|
branch_zero t3, fail
|
|
load8 t3, [t2, FUNCTION_OBJECT_BUILTIN_HAS_VALUE]
|
|
branch_zero t3, fail
|
|
load8 t3, [t2, FUNCTION_OBJECT_BUILTIN_VALUE]
|
|
branch_ne t3, expected_builtin, fail
|
|
end
|
|
|
|
# Load a UTF-16 code unit from a primitive string with resident UTF-16 data.
|
|
# Input:
|
|
# t2 = PrimitiveString*
|
|
# t4 = non-negative code-unit index
|
|
# Output:
|
|
# t0 = zero-extended code unit
|
|
# Clobbers:
|
|
# t3, t5
|
|
# Jumps to out_of_bounds if index >= string length.
|
|
# Jumps to fail if the string would require resolving deferred data.
|
|
macro load_primitive_string_utf16_code_unit(out_of_bounds, fail)
|
|
load8 t3, [t2, PRIMITIVE_STRING_DEFERRED_KIND]
|
|
branch_ne t3, PRIMITIVE_STRING_DEFERRED_KIND_NONE, fail
|
|
|
|
load64 t5, [t2, PRIMITIVE_STRING_UTF16_STRING]
|
|
branch_zero t5, fail
|
|
|
|
load8 t3, [t2, PRIMITIVE_STRING_UTF16_SHORT_STRING_BYTE_COUNT_AND_FLAG]
|
|
and t3, UTF16_SHORT_STRING_FLAG
|
|
branch_zero t3, .long_storage
|
|
|
|
load8 t3, [t2, PRIMITIVE_STRING_UTF16_SHORT_STRING_BYTE_COUNT_AND_FLAG]
|
|
shr t3, UTF16_SHORT_STRING_BYTE_COUNT_SHIFT_COUNT
|
|
branch_ge_unsigned t4, t3, out_of_bounds
|
|
mov t0, t2
|
|
add t0, PRIMITIVE_STRING_UTF16_SHORT_STRING_STORAGE
|
|
load8 t0, [t0, t4]
|
|
jmp .done
|
|
|
|
.long_storage:
|
|
load64 t3, [t5, UTF16_STRING_DATA_LENGTH_IN_CODE_UNITS]
|
|
branch_negative t3, .utf16_storage
|
|
branch_ge_unsigned t4, t3, out_of_bounds
|
|
add t5, UTF16_STRING_DATA_STRING_STORAGE
|
|
load8 t0, [t5, t4]
|
|
jmp .done
|
|
|
|
.utf16_storage:
|
|
shl t3, 1
|
|
shr t3, 1
|
|
branch_ge_unsigned t4, t3, out_of_bounds
|
|
mov t0, t4
|
|
add t0, t4
|
|
add t5, UTF16_STRING_DATA_STRING_STORAGE
|
|
load16 t0, [t5, t0]
|
|
|
|
.done:
|
|
end
|
|
|
|
# Dispatch the instruction at current pc (without advancing).
|
|
# Clobbers t0.
|
|
macro dispatch_current()
|
|
load8 t0, [pb, pc]
|
|
jmp [dispatch, t0, 8]
|
|
end
|
|
|
|
# Walk the environment chain using a cached EnvironmentCoordinate.
|
|
# Input: m_cache field offset for the EnvironmentCoordinate in the instruction.
|
|
# Output: t3 = target environment, t2 = binding index.
|
|
# On failure (invalid cache, screwed by eval): jumps to fail_label.
|
|
# Clobbers t0, t1, t2, t3, t4.
|
|
macro walk_env_chain(m_cache_field, fail_label)
|
|
lea t0, [pb, pc]
|
|
add t0, m_cache_field
|
|
load_pair32 t1, t2, [t0, ENVIRONMENT_COORDINATE_HOPS], [t0, ENVIRONMENT_COORDINATE_INDEX]
|
|
mov t4, ENVIRONMENT_COORDINATE_INVALID
|
|
branch_eq t1, t4, fail_label
|
|
load64 t3, [exec_ctx, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT]
|
|
branch_zero t1, .walk_done
|
|
.walk_loop:
|
|
load8 t0, [t3, ENVIRONMENT_SCREWED_BY_EVAL]
|
|
branch_nonzero t0, fail_label
|
|
load64 t3, [t3, ENVIRONMENT_OUTER]
|
|
sub t1, 1
|
|
branch_nonzero t1, .walk_loop
|
|
.walk_done:
|
|
load8 t0, [t3, ENVIRONMENT_SCREWED_BY_EVAL]
|
|
branch_nonzero t0, fail_label
|
|
end
|
|
|
|
# Pop an inline frame and resume the caller without bouncing through C++.
|
|
# The asm-managed JS-to-JS call fast path currently only inlines Call, never
|
|
# CallConstruct, so caller_is_construct is always false for asm-managed inline
|
|
# frames.
|
|
#
|
|
# This mirrors VM::pop_inline_frame():
|
|
# 1. Read the caller's destination register from the callee frame.
|
|
# 2. Publish the caller's resume pc and returned value.
|
|
# 3. Deallocate the callee by rewinding InterpreterStack::top to exec_ctx.
|
|
# 4. Make the caller the running execution context again.
|
|
# 5. Advance execution_generation so WeakRef and similar observers still see
|
|
# the same boundary they would have seen through the C++ helper.
|
|
#
|
|
# The macro expects exec_ctx/pb/values/pc to still describe the callee frame.
|
|
# Input:
|
|
# caller_frame = ExecutionContext* of the caller
|
|
# value_reg = NaN-boxed return value
|
|
# Clobbers:
|
|
# t2, t3, t4
|
|
macro pop_inline_frame_and_resume(caller_frame, value_reg)
|
|
load_pair32 t2, t4, [exec_ctx, EXECUTION_CONTEXT_CALLER_RETURN_PC], [exec_ctx, EXECUTION_CONTEXT_CALLER_DST_RAW]
|
|
store32 [caller_frame, EXECUTION_CONTEXT_PROGRAM_COUNTER], t2
|
|
lea t3, [caller_frame, SIZEOF_EXECUTION_CONTEXT]
|
|
store64 [t3, t4, 8], value_reg
|
|
|
|
load_vm t3
|
|
store64 [t3, VM_RUNNING_EXECUTION_CONTEXT], caller_frame
|
|
store64 [t3, VM_INTERPRETER_STACK_TOP], exec_ctx
|
|
inc32_mem [t3, VM_EXECUTION_GENERATION]
|
|
|
|
mov exec_ctx, caller_frame
|
|
load64 t3, [exec_ctx, EXECUTION_CONTEXT_EXECUTABLE]
|
|
load64 pb, [t3, EXECUTABLE_BYTECODE_DATA]
|
|
lea values, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
mov pc, t2
|
|
dispatch_current
|
|
end
|
|
|
|
# ============================================================================
|
|
# Simple data movement
|
|
# ============================================================================
|
|
|
|
handler Mov
|
|
load_operand t1, m_src
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
end
|
|
|
|
handler Mov2
|
|
load_operand t1, m_src1
|
|
store_operand m_dst1, t1
|
|
load_operand t2, m_src2
|
|
store_operand m_dst2, t2
|
|
dispatch_next
|
|
end
|
|
|
|
handler Mov3
|
|
load_operand t1, m_src1
|
|
store_operand m_dst1, t1
|
|
load_operand t2, m_src2
|
|
store_operand m_dst2, t2
|
|
load_operand t3, m_src3
|
|
store_operand m_dst3, t3
|
|
dispatch_next
|
|
end
|
|
|
|
# ============================================================================
|
|
# Arithmetic
|
|
# ============================================================================
|
|
|
|
# Arithmetic fast path: try int32, check overflow, fall back to double, then slow path.
|
|
# The coerce_to_doubles macro handles mixed int32+double coercion.
|
|
# On int32 overflow, we convert both operands to double and retry.
|
|
# box_double_or_int32 re-boxes double results as Int32 when possible,
|
|
# mirroring JS::Value(double), so downstream int32 fast paths can fire.
|
|
handler Add
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
coerce_to_doubles .both_int, .slow
|
|
# One or both doubles: ft0=lhs, ft1=rhs
|
|
fp_add ft0, ft1
|
|
box_double_or_int32 t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.both_int:
|
|
# t3=lhs (sign-extended), t4=rhs (sign-extended)
|
|
# 32-bit add with hardware overflow detection
|
|
add32_overflow t3, t4, .overflow
|
|
box_int32_clean t5, t3
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.overflow:
|
|
# Int32 overflow: convert both to double and redo the operation
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
int_to_double ft0, t3
|
|
int_to_double ft1, t4
|
|
fp_add ft0, ft1
|
|
fp_mov t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_add
|
|
end
|
|
|
|
# Same pattern as Add but with subtraction.
|
|
handler Sub
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
coerce_to_doubles .both_int, .slow
|
|
# One or both doubles: ft0=lhs, ft1=rhs
|
|
fp_sub ft0, ft1
|
|
box_double_or_int32 t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.both_int:
|
|
# t3=lhs (sign-extended), t4=rhs (sign-extended)
|
|
sub32_overflow t3, t4, .overflow
|
|
box_int32_clean t5, t3
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.overflow:
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
int_to_double ft0, t3
|
|
int_to_double ft1, t4
|
|
fp_sub ft0, ft1
|
|
fp_mov t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_sub
|
|
end
|
|
|
|
# Same pattern as Add but with multiplication.
|
|
# Extra complexity: 0 * negative = -0.0 (must produce negative zero double).
|
|
handler Mul
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
coerce_to_doubles .both_int, .slow
|
|
# One or both doubles: ft0=lhs, ft1=rhs
|
|
fp_mul ft0, ft1
|
|
box_double_or_int32 t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.both_int:
|
|
# t3=lhs (sign-extended), t4=rhs (sign-extended)
|
|
mul32_overflow t3, t4, .overflow
|
|
branch_nonzero t3, .store_int
|
|
# Result is 0: check if either operand was negative -> -0.0
|
|
unbox_int32 t5, t1
|
|
or t5, t4
|
|
branch_negative t5, .negative_zero
|
|
.store_int:
|
|
box_int32_clean t5, t3
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.negative_zero:
|
|
mov t5, NEGATIVE_ZERO
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.overflow:
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
int_to_double ft0, t3
|
|
int_to_double ft1, t4
|
|
fp_mul ft0, ft1
|
|
fp_mov t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_mul
|
|
end
|
|
|
|
# ============================================================================
|
|
# Control flow
|
|
# ============================================================================
|
|
|
|
handler Jump
|
|
load_label t0, m_target
|
|
goto_handler t0
|
|
end
|
|
|
|
# Conditional jumps: check boolean first (most common), then int32, then slow path.
|
|
# For JumpIf/JumpTrue/JumpFalse, a boolean's truth value is just bit 0.
|
|
# For int32, any nonzero low 32 bits means truthy.
|
|
handler JumpIf
|
|
load_operand t1, m_condition
|
|
extract_tag t2, t1
|
|
# Boolean fast path
|
|
branch_eq t2, BOOLEAN_TAG, .is_bool
|
|
# Int32 fast path
|
|
branch_eq t2, INT32_TAG, .is_int32
|
|
# Slow path: call helper to convert to boolean
|
|
call_helper asm_helper_to_boolean
|
|
branch_nonzero t0, .take_true
|
|
jmp .take_false
|
|
.is_bool:
|
|
branch_bits_set t1, 1, .take_true
|
|
jmp .take_false
|
|
.is_int32:
|
|
branch_nonzero32 t1, .take_true
|
|
jmp .take_false
|
|
.take_true:
|
|
load_label t0, m_true_target
|
|
goto_handler t0
|
|
.take_false:
|
|
load_label t0, m_false_target
|
|
goto_handler t0
|
|
end
|
|
|
|
handler JumpTrue
|
|
load_operand t1, m_condition
|
|
extract_tag t2, t1
|
|
branch_eq t2, BOOLEAN_TAG, .is_bool
|
|
branch_eq t2, INT32_TAG, .is_int32
|
|
call_helper asm_helper_to_boolean
|
|
branch_nonzero t0, .take
|
|
dispatch_next
|
|
.is_bool:
|
|
branch_bits_set t1, 1, .take
|
|
dispatch_next
|
|
.is_int32:
|
|
branch_nonzero32 t1, .take
|
|
dispatch_next
|
|
.take:
|
|
load_label t0, m_target
|
|
goto_handler t0
|
|
end
|
|
|
|
handler JumpFalse
|
|
load_operand t1, m_condition
|
|
extract_tag t2, t1
|
|
branch_eq t2, BOOLEAN_TAG, .is_bool
|
|
branch_eq t2, INT32_TAG, .is_int32
|
|
call_helper asm_helper_to_boolean
|
|
branch_zero t0, .take
|
|
dispatch_next
|
|
.is_bool:
|
|
branch_bits_clear t1, 1, .take
|
|
dispatch_next
|
|
.is_int32:
|
|
branch_zero32 t1, .take
|
|
dispatch_next
|
|
.take:
|
|
load_label t0, m_target
|
|
goto_handler t0
|
|
end
|
|
|
|
# Nullish check: undefined and null tags differ only in bit 0,
|
|
# so (tag & 0xFFFE) == UNDEFINED_TAG matches both.
|
|
handler JumpNullish
|
|
load_operand t1, m_condition
|
|
# Nullish: (tag & 0xFFFE) == 0x7FFE (matches undefined=0x7FFE and null=0x7FFF)
|
|
extract_tag t2, t1
|
|
and t2, 0xFFFE
|
|
branch_eq t2, UNDEFINED_TAG, .nullish
|
|
load_label t0, m_false_target
|
|
goto_handler t0
|
|
.nullish:
|
|
load_label t0, m_true_target
|
|
goto_handler t0
|
|
end
|
|
|
|
handler JumpUndefined
|
|
load_operand t1, m_condition
|
|
mov t0, UNDEFINED_SHIFTED
|
|
branch_eq t1, t0, .is_undefined
|
|
load_label t0, m_false_target
|
|
goto_handler t0
|
|
.is_undefined:
|
|
load_label t0, m_true_target
|
|
goto_handler t0
|
|
end
|
|
|
|
|
|
# Jump comparison handlers: use numeric_compare_coerce (handles mixed int32+double)
|
|
# combined with jump_binary_epilogue (provides .take_true, .take_false, .slow labels).
|
|
handler JumpLessThan
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare_coerce branch_lt_signed, branch_fp_less, .take_true, .take_false, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_less_than
|
|
end
|
|
|
|
handler JumpGreaterThan
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare_coerce branch_gt_signed, branch_fp_greater, .take_true, .take_false, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_greater_than
|
|
end
|
|
|
|
handler JumpLessThanEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare_coerce branch_le_signed, branch_fp_less_or_equal, .take_true, .take_false, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_less_than_equals
|
|
end
|
|
|
|
handler JumpGreaterThanEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare_coerce branch_ge_signed, branch_fp_greater_or_equal, .take_true, .take_false, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_greater_than_equals
|
|
end
|
|
|
|
handler JumpLooselyEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
loose_equality_core .take_true, .take_false, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_loosely_equals
|
|
end
|
|
|
|
handler JumpLooselyInequals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
loose_equality_core .take_false, .take_true, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_loosely_inequals
|
|
end
|
|
|
|
handler JumpStrictlyEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
strict_equality_core .take_true, .take_false, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_strictly_equals
|
|
end
|
|
|
|
handler JumpStrictlyInequals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
strict_equality_core .take_false, .take_true, .slow
|
|
jump_binary_epilogue asm_slow_path_jump_strictly_inequals
|
|
end
|
|
|
|
# Fast path for ++x: int32 + 1 with overflow check.
|
|
# On overflow, convert to double and add 1.0.
|
|
handler Increment
|
|
load_operand t1, m_dst
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .slow
|
|
unbox_int32 t3, t1
|
|
add32_overflow t3, 1, .overflow
|
|
box_int32_clean t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.overflow:
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
mov t0, DOUBLE_ONE
|
|
fp_mov ft1, t0
|
|
fp_add ft0, ft1
|
|
fp_mov t4, ft0
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_increment
|
|
end
|
|
|
|
# Fast path for --x: int32 - 1 with overflow check.
|
|
handler Decrement
|
|
load_operand t1, m_dst
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .slow
|
|
unbox_int32 t3, t1
|
|
sub32_overflow t3, 1, .overflow
|
|
box_int32_clean t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.overflow:
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
mov t0, DOUBLE_ONE
|
|
fp_mov ft1, t0
|
|
fp_sub ft0, ft1
|
|
fp_mov t4, ft0
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_decrement
|
|
end
|
|
|
|
handler Not
|
|
load_operand t1, m_src
|
|
extract_tag t2, t1
|
|
# Boolean fast path
|
|
branch_eq t2, BOOLEAN_TAG, .is_bool
|
|
# Int32 fast path
|
|
branch_eq t2, INT32_TAG, .is_int32
|
|
# Undefined/null -> !nullish = true
|
|
mov t3, t2
|
|
and t3, 0xFFFE
|
|
branch_eq t3, UNDEFINED_TAG, .store_true
|
|
# Slow path for remaining types (object, string, etc)
|
|
# NB: Objects go through slow path to handle [[IsHTMLDDA]]
|
|
call_helper asm_helper_to_boolean
|
|
branch_zero t0, .store_true
|
|
jmp .store_false
|
|
.is_bool:
|
|
branch_bits_clear t1, 1, .store_true
|
|
jmp .store_false
|
|
.is_int32:
|
|
branch_zero32 t1, .store_true
|
|
jmp .store_false
|
|
.store_true:
|
|
mov t0, BOOLEAN_TRUE
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.store_false:
|
|
mov t0, BOOLEAN_FALSE
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
end
|
|
|
|
# ============================================================================
|
|
# Return / function call
|
|
# ============================================================================
|
|
|
|
handler Return
|
|
# Empty is the internal "no explicit value" marker. Returning it from
|
|
# bytecode means "return undefined" at the JS level.
|
|
load_operand t0, m_value
|
|
mov t2, EMPTY_TAG_SHIFTED
|
|
branch_ne t0, t2, .value_ready
|
|
mov t0, UNDEFINED_SHIFTED
|
|
.value_ready:
|
|
# Inline JS-to-JS calls resume the caller directly from asm. Top-level
|
|
# returns instead exit back to the outer interpreter entry point.
|
|
load64 t1, [exec_ctx, EXECUTION_CONTEXT_CALLER_FRAME]
|
|
branch_zero t1, .top_level
|
|
pop_inline_frame_and_resume t1, t0
|
|
.top_level:
|
|
# Top-level return matches VM::run_executable(): write return_value,
|
|
# clear the exception slot, and leave the asm interpreter entirely.
|
|
# values[3] = return_value, values[1] = empty (clear exception)
|
|
store64 [values, 24], t0
|
|
store64 [values, 8], t2
|
|
exit
|
|
end
|
|
|
|
# Like Return, but does not clear the exception register (values[1]).
|
|
# Used at the end of a function body (after all user code).
|
|
handler End
|
|
# End shares the same inline-frame unwind logic as Return. The only
|
|
# top-level difference is that End preserves the current exception slot.
|
|
load_operand t0, m_value
|
|
mov t2, EMPTY_TAG_SHIFTED
|
|
branch_ne t0, t2, .value_ready
|
|
mov t0, UNDEFINED_SHIFTED
|
|
.value_ready:
|
|
# Inline frame: resume the caller immediately.
|
|
load64 t1, [exec_ctx, EXECUTION_CONTEXT_CALLER_FRAME]
|
|
branch_zero t1, .top_level
|
|
pop_inline_frame_and_resume t1, t0
|
|
.top_level:
|
|
# Top-level end: publish the return value and exit without touching
|
|
# values[1], since End does not model a user-visible `return` opcode.
|
|
store64 [values, 24], t0
|
|
exit
|
|
end
|
|
|
|
# Loads running_execution_context().lexical_environment into dst,
|
|
# NaN-boxed as a cell pointer.
|
|
handler GetLexicalEnvironment
|
|
load64 t0, [exec_ctx, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT]
|
|
mov t1, CELL_TAG_SHIFTED
|
|
or t0, t1
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
end
|
|
|
|
handler SetLexicalEnvironment
|
|
call_slow_path asm_slow_path_set_lexical_environment
|
|
end
|
|
|
|
# ============================================================================
|
|
# Environment / binding access
|
|
# ============================================================================
|
|
|
|
# Inline environment chain walk + binding value load with TDZ check.
|
|
handler GetBinding
|
|
walk_env_chain m_cache, .slow
|
|
# t3 = target environment, t2 = binding index
|
|
load64 t0, [t3, BINDINGS_DATA_PTR]
|
|
mul t2, t2, SIZEOF_BINDING
|
|
add t0, t2
|
|
# Check binding is initialized (TDZ)
|
|
load8 t1, [t0, BINDING_INITIALIZED]
|
|
branch_zero t1, .slow
|
|
load64 t1, [t0, BINDING_VALUE]
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_binding
|
|
end
|
|
|
|
# Inline environment chain walk + direct binding value load.
|
|
handler GetInitializedBinding
|
|
walk_env_chain m_cache, .slow
|
|
# t3 = target environment, t2 = binding index
|
|
load64 t0, [t3, BINDINGS_DATA_PTR]
|
|
mul t2, t2, SIZEOF_BINDING
|
|
add t0, t2
|
|
load64 t1, [t0, BINDING_VALUE]
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_initialized_binding
|
|
end
|
|
|
|
# Inline environment chain walk + initialize binding (set value + initialized=true).
|
|
handler InitializeLexicalBinding
|
|
walk_env_chain m_cache, .slow
|
|
# t3 = target environment, t2 = binding index
|
|
load64 t3, [t3, BINDINGS_DATA_PTR]
|
|
mul t2, t2, SIZEOF_BINDING
|
|
add t3, t2
|
|
# Store source value into binding
|
|
# NB: load_operand clobbers t0, so binding address must be in t3.
|
|
load_operand t1, m_src
|
|
store64 [t3, BINDING_VALUE], t1
|
|
# Set initialized = true
|
|
mov t1, 1
|
|
store8 [t3, BINDING_INITIALIZED], t1
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_initialize_lexical_binding
|
|
end
|
|
|
|
# Inline environment chain walk + set mutable binding.
|
|
handler SetLexicalBinding
|
|
walk_env_chain m_cache, .slow
|
|
# t3 = target environment, t2 = binding index
|
|
load64 t3, [t3, BINDINGS_DATA_PTR]
|
|
mul t2, t2, SIZEOF_BINDING
|
|
add t3, t2
|
|
# Check initialized (TDZ)
|
|
load8 t1, [t3, BINDING_INITIALIZED]
|
|
branch_zero t1, .slow
|
|
# Check mutable
|
|
load8 t1, [t3, BINDING_MUTABLE]
|
|
branch_zero t1, .slow
|
|
# Store source value into binding
|
|
# NB: load_operand clobbers t0, so binding address must be in t3.
|
|
load_operand t1, m_src
|
|
store64 [t3, BINDING_VALUE], t1
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_set_lexical_binding
|
|
end
|
|
|
|
# x++: save original to dst first, then increment src in-place.
|
|
handler PostfixIncrement
|
|
load_operand t1, m_src
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .slow
|
|
# Save original value to dst (the "postfix" part)
|
|
store_operand m_dst, t1
|
|
# Increment in-place: src = src + 1
|
|
unbox_int32 t3, t1
|
|
add32_overflow t3, 1, .overflow_after_store
|
|
box_int32_clean t4, t3
|
|
store_operand m_src, t4
|
|
dispatch_next
|
|
.overflow_after_store:
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
mov t0, DOUBLE_ONE
|
|
fp_mov ft1, t0
|
|
fp_add ft0, ft1
|
|
fp_mov t4, ft0
|
|
store_operand m_src, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_postfix_increment
|
|
end
|
|
|
|
# Division result is stored as int32 when representable (e.g. 6/3 = 2),
|
|
# matching the Value(double) constructor's behavior. We don't use
|
|
# coerce_to_doubles here because we never need the both-int32 branch --
|
|
# both operands go straight to FP regs.
|
|
handler Div
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
extract_tag t3, t1
|
|
branch_eq t3, INT32_TAG, .lhs_is_int32
|
|
# t3 already has lhs tag
|
|
check_tag_is_double t3, .slow
|
|
fp_mov ft0, t1
|
|
jmp .lhs_ok
|
|
.lhs_is_int32:
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
.lhs_ok:
|
|
# ft0 = lhs as double
|
|
extract_tag t3, t2
|
|
branch_eq t3, INT32_TAG, .rhs_is_int32
|
|
# t3 already has rhs tag
|
|
check_tag_is_double t3, .slow
|
|
fp_mov ft1, t2
|
|
jmp .do_div
|
|
.rhs_is_int32:
|
|
unbox_int32 t3, t2
|
|
int_to_double ft1, t3
|
|
.do_div:
|
|
# ft0 = lhs, ft1 = rhs
|
|
fp_div ft0, ft1
|
|
# Try to store result as int32 if it's an integer in i32 range.
|
|
# NB: We can't use js_to_int32 here because fjcvtzs applies modular
|
|
# reduction (e.g. 2^33 -> 0) which is wrong -- we need a strict
|
|
# round-trip check: truncate to int32, convert back, compare.
|
|
double_to_int32 t5, ft0, .store_double
|
|
# Exclude negative zero: -0.0 truncates to 0 but must stay double.
|
|
branch_nonzero t5, .store_int
|
|
fp_mov t5, ft0
|
|
branch_negative t5, .store_double
|
|
.store_int:
|
|
box_int32 t5, t5
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.store_double:
|
|
canonicalize_nan t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_div
|
|
end
|
|
|
|
# Numeric comparison handlers: use numeric_compare macro for both-int32 and
|
|
# both-double fast paths, fall back to slow path for mixed/non-numeric types.
|
|
# The boolean_result_epilogue macro provides .store_true, .store_false, .slow labels.
|
|
handler LessThan
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare branch_lt_signed, branch_fp_less, .store_true, .store_false, .slow
|
|
boolean_result_epilogue asm_slow_path_less_than
|
|
end
|
|
|
|
handler LessThanEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare branch_le_signed, branch_fp_less_or_equal, .store_true, .store_false, .slow
|
|
boolean_result_epilogue asm_slow_path_less_than_equals
|
|
end
|
|
|
|
handler GreaterThan
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare branch_gt_signed, branch_fp_greater, .store_true, .store_false, .slow
|
|
boolean_result_epilogue asm_slow_path_greater_than
|
|
end
|
|
|
|
handler GreaterThanEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
numeric_compare branch_ge_signed, branch_fp_greater_or_equal, .store_true, .store_false, .slow
|
|
boolean_result_epilogue asm_slow_path_greater_than_equals
|
|
end
|
|
|
|
# Bitwise ops accept int32 and boolean, fall back to slow path for anything else.
|
|
handler BitwiseXor
|
|
bitwise_op xor, asm_slow_path_bitwise_xor
|
|
end
|
|
|
|
# ============================================================================
|
|
# Unary operators
|
|
# ============================================================================
|
|
|
|
# Fast path for numeric values: +x is a no-op for int32 and double.
|
|
handler UnaryPlus
|
|
load_operand t1, m_src
|
|
# Check if int32
|
|
extract_tag t0, t1
|
|
branch_eq t0, INT32_TAG, .done
|
|
# t0 already has tag; check if double
|
|
check_tag_is_double t0, .slow
|
|
.done:
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_unary_plus
|
|
end
|
|
|
|
# Check if value is TDZ (empty). If not, just continue.
|
|
handler ThrowIfTDZ
|
|
load_operand t1, m_src
|
|
mov t0, EMPTY_VALUE
|
|
branch_eq t1, t0, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_throw_if_tdz
|
|
end
|
|
|
|
# Check if value is an object. Only throws on non-object (rare).
|
|
handler ThrowIfNotObject
|
|
load_operand t1, m_src
|
|
extract_tag t0, t1
|
|
branch_ne t0, OBJECT_TAG, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_throw_if_not_object
|
|
end
|
|
|
|
# Check if value is nullish (undefined or null). Only throws on nullish (rare).
|
|
handler ThrowIfNullish
|
|
load_operand t1, m_src
|
|
extract_tag t0, t1
|
|
and t0, 0xFFFE
|
|
branch_eq t0, UNDEFINED_TAG, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_throw_if_nullish
|
|
end
|
|
|
|
# Fast path for int32: ~value
|
|
handler BitwiseNot
|
|
load_operand t1, m_src
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .slow
|
|
# NOT the low 32 bits (not32 zeros upper 32), then re-box
|
|
mov t3, t1
|
|
not32 t3
|
|
box_int32_clean t3, t3
|
|
store_operand m_dst, t3
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_bitwise_not
|
|
end
|
|
|
|
handler BitwiseAnd
|
|
bitwise_op and, asm_slow_path_bitwise_and
|
|
end
|
|
|
|
handler BitwiseOr
|
|
bitwise_op or, asm_slow_path_bitwise_or
|
|
end
|
|
|
|
# Shift ops: int32-only fast path, shift count masked to 0-31 per spec.
|
|
handler LeftShift
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .slow
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
and t4, 31
|
|
shl t3, t4
|
|
box_int32 t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_left_shift
|
|
end
|
|
|
|
handler RightShift
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .slow
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
and t4, 31
|
|
sar t3, t4
|
|
box_int32 t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_right_shift
|
|
end
|
|
|
|
# Unsigned right shift: result is always unsigned, so values > INT32_MAX
|
|
# must be stored as double (can't fit in a signed int32 NaN-box).
|
|
handler UnsignedRightShift
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .slow
|
|
# u32 result = (u32)lhs >> (rhs % 32)
|
|
mov t3, t1
|
|
and t3, 0xFFFFFFFF
|
|
unbox_int32 t4, t2
|
|
and t4, 31
|
|
shr t3, t4
|
|
# If result > INT32_MAX, store as double
|
|
branch_bit_set t3, 31, .as_double
|
|
box_int32_clean t3, t3
|
|
store_operand m_dst, t3
|
|
dispatch_next
|
|
.as_double:
|
|
int_to_double ft0, t3
|
|
fp_mov t3, ft0
|
|
store_operand m_dst, t3
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_unsigned_right_shift
|
|
end
|
|
|
|
# Modulo: int32-only fast path for non-negative dividend.
|
|
# Negative dividend falls to slow path to handle -0 and INT_MIN correctly.
|
|
handler Mod
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .slow
|
|
unbox_int32 t3, t1
|
|
unbox_int32 t4, t2
|
|
# Check d == 0
|
|
branch_zero t4, .slow
|
|
# Check n >= 0 (positive fast path avoids INT_MIN/-1 and negative zero)
|
|
branch_negative t3, .slow
|
|
# divmod: quotient in t0, remainder in t2
|
|
divmod t0, t2, t3, t4
|
|
box_int32 t2, t2
|
|
store_operand m_dst, t2
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_mod
|
|
end
|
|
|
|
# ============================================================================
|
|
# Equality and comparison
|
|
# ============================================================================
|
|
|
|
# Equality handlers use the strict/loose_equality_core macros which handle
|
|
# type-specific comparisons (int32, boolean, undefined/null bitwise compare,
|
|
# double with NaN awareness, string pointer shortcut, bigint -> slow path).
|
|
handler StrictlyEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
strict_equality_core .store_true, .store_false, .slow
|
|
boolean_result_epilogue asm_slow_path_strictly_equals
|
|
end
|
|
|
|
handler StrictlyInequals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
strict_equality_core .store_false, .store_true, .slow
|
|
boolean_result_epilogue asm_slow_path_strictly_inequals
|
|
end
|
|
|
|
# Inline environment chain walk + get callee and this.
|
|
handler GetCalleeAndThisFromEnvironment
|
|
walk_env_chain m_cache, .slow
|
|
# t3 = target environment, t2 = binding index
|
|
load64 t0, [t3, BINDINGS_DATA_PTR]
|
|
mul t2, t2, SIZEOF_BINDING
|
|
add t0, t2
|
|
load64 t1, [t0, BINDING_VALUE]
|
|
# Check value is not empty (TDZ)
|
|
mov t4, EMPTY_TAG_SHIFTED
|
|
branch_eq t1, t4, .slow
|
|
store_operand m_callee, t1
|
|
# this = undefined (DeclarativeEnvironment.with_base_object() always returns nullptr)
|
|
mov t0, UNDEFINED_SHIFTED
|
|
store_operand m_this_value, t0
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_callee_and_this
|
|
end
|
|
|
|
handler LooselyEquals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
loose_equality_core .store_true, .store_false, .slow
|
|
boolean_result_epilogue asm_slow_path_loosely_equals
|
|
end
|
|
|
|
handler LooselyInequals
|
|
load_operand t1, m_lhs
|
|
load_operand t2, m_rhs
|
|
loose_equality_core .store_false, .store_true, .slow
|
|
boolean_result_epilogue asm_slow_path_loosely_inequals
|
|
end
|
|
|
|
handler UnaryMinus
|
|
load_operand t1, m_src
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .try_double
|
|
unbox_int32 t3, t1
|
|
# -0 check: if value is 0, result is -0.0 (double)
|
|
branch_zero t3, .negative_zero
|
|
# 32-bit negate with overflow detection (INT32_MIN)
|
|
neg32_overflow t3, .overflow
|
|
box_int32_clean t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.negative_zero:
|
|
mov t0, NEGATIVE_ZERO
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.overflow:
|
|
# INT32_MIN: -(-2147483648) = 2147483648.0
|
|
int_to_double ft0, t3
|
|
fp_mov t4, ft0
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.try_double:
|
|
# t2 already has tag
|
|
check_tag_is_double t2, .slow
|
|
# Negate double: flip sign bit (bit 63)
|
|
toggle_bit t1, 63
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_unary_minus
|
|
end
|
|
|
|
# x--: save original to dst first, then decrement src in-place.
|
|
handler PostfixDecrement
|
|
load_operand t1, m_src
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .slow
|
|
# Save original value to dst (the "postfix" part)
|
|
store_operand m_dst, t1
|
|
# Decrement in-place: src = src - 1
|
|
unbox_int32 t3, t1
|
|
sub32_overflow t3, 1, .overflow_after_store
|
|
box_int32_clean t4, t3
|
|
store_operand m_src, t4
|
|
dispatch_next
|
|
.overflow_after_store:
|
|
unbox_int32 t3, t1
|
|
int_to_double ft0, t3
|
|
mov t0, DOUBLE_ONE
|
|
fp_mov ft1, t0
|
|
fp_sub ft0, ft1
|
|
fp_mov t4, ft0
|
|
store_operand m_src, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_postfix_decrement
|
|
end
|
|
|
|
handler ToInt32
|
|
load_operand t1, m_value
|
|
extract_tag t2, t1
|
|
branch_ne t2, INT32_TAG, .try_double
|
|
# Already int32, just copy
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.try_double:
|
|
# t2 already has tag; check if double (copy first, t2 needed at .try_boolean)
|
|
mov t3, t2
|
|
check_tag_is_double t3, .try_boolean
|
|
# Convert double to int32 using JS ToInt32 semantics.
|
|
# With FEAT_JSCVT: fjcvtzs handles everything in one instruction.
|
|
# Without: truncate + round-trip check, slow path on mismatch.
|
|
fp_mov ft0, t1
|
|
js_to_int32 t2, ft0, .slow
|
|
box_int32_clean t2, t2
|
|
store_operand m_dst, t2
|
|
dispatch_next
|
|
.try_boolean:
|
|
branch_ne t2, BOOLEAN_TAG, .slow
|
|
# Convert boolean to int32: false -> 0, true -> 1
|
|
and t1, 1
|
|
box_int32_clean t1, t1
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.slow:
|
|
# Slow path handles other types (string, object, nullish, etc) and uncommon cases.
|
|
call_slow_path asm_slow_path_to_int32
|
|
end
|
|
|
|
# ============================================================================
|
|
# Property access (indexed + named + inline caches)
|
|
# ============================================================================
|
|
|
|
# Fast path for array[int32_index] = value with Packed/Holey indexed storage.
|
|
handler PutByValue
|
|
# Only fast-path Normal puts (not Getter/Setter/Own)
|
|
load8 t0, [pb, pc, m_kind]
|
|
branch_ne t0, PUT_KIND_NORMAL, .slow
|
|
load_operand t1, m_base
|
|
load_operand t2, m_property
|
|
# Check base is an object
|
|
extract_tag t3, t1
|
|
branch_ne t3, OBJECT_TAG, .slow
|
|
# Check property is non-negative int32
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .slow
|
|
mov t4, t2
|
|
and t4, 0xFFFFFFFF
|
|
# Check high bit (negative int32)
|
|
branch_bit_set t4, 31, .slow
|
|
# Extract Object*
|
|
unbox_object t3, t1
|
|
# Check IsTypedArray flag -- branch to C++ helper early
|
|
load8 t0, [t3, OBJECT_FLAGS]
|
|
branch_bits_set t0, OBJECT_FLAG_IS_TYPED_ARRAY, .try_typed_array
|
|
# Check !may_interfere_with_indexed_property_access
|
|
branch_bits_set t0, OBJECT_FLAG_MAY_INTERFERE, .slow
|
|
# Packed is the hot path: existing elements can be overwritten directly.
|
|
load8 t0, [t3, OBJECT_INDEXED_STORAGE_KIND]
|
|
branch_ne t0, INDEXED_STORAGE_KIND_PACKED, .not_packed
|
|
# Check index vs array_like_size
|
|
load32 t5, [t3, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
|
|
branch_ge_unsigned t4, t5, .slow
|
|
load64 t5, [t3, OBJECT_INDEXED_ELEMENTS]
|
|
load_operand t1, m_src
|
|
store64 [t5, t4, 8], t1
|
|
dispatch_next
|
|
.not_packed:
|
|
branch_ne t0, INDEXED_STORAGE_KIND_HOLEY, .slow
|
|
# Holey arrays need a slot load to distinguish existing elements from holes.
|
|
load32 t5, [t3, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
|
|
branch_ge_unsigned t4, t5, .slow
|
|
load64 t5, [t3, OBJECT_INDEXED_ELEMENTS]
|
|
branch_zero t5, .try_holey_array_slow
|
|
mov t0, t5
|
|
sub t0, 8
|
|
load32 t0, [t0, 0]
|
|
branch_ge_unsigned t4, t0, .try_holey_array_slow
|
|
load64 t1, [t5, t4, 8]
|
|
mov t0, EMPTY_TAG_SHIFTED
|
|
branch_eq t1, t0, .try_holey_array_slow
|
|
load_operand t1, m_src
|
|
store64 [t5, t4, 8], t1
|
|
dispatch_next
|
|
.try_holey_array_slow:
|
|
call_interp asm_try_put_by_value_holey_array
|
|
branch_nonzero t0, .slow
|
|
dispatch_next
|
|
.try_typed_array:
|
|
# t3 = Object*, t4 = index (u32, non-negative)
|
|
# Load cached data pointer (pre-computed: buffer.data() + byte_offset)
|
|
# nullptr means uncached -> C++ helper will resolve the access.
|
|
load64 t5, [t3, TYPED_ARRAY_CACHED_DATA_PTR]
|
|
branch_zero t5, .try_typed_array_slow
|
|
# Cached pointers only exist for fixed-length typed arrays, so array_length
|
|
# is known to hold a concrete u32 value here.
|
|
load32 t0, [t3, TYPED_ARRAY_ARRAY_LENGTH_VALUE]
|
|
branch_ge_unsigned t4, t0, .slow
|
|
# t5 = data base pointer, t4 = index
|
|
# Load kind into t2 before load_operand clobbers t0
|
|
load8 t2, [t3, TYPED_ARRAY_KIND]
|
|
# Load source value into t1 (clobbers t0)
|
|
load_operand t1, m_src
|
|
# Check if source is int32
|
|
extract_tag t0, t1
|
|
branch_eq t0, INT32_TAG, .ta_store_int32
|
|
# Non-int32 value: only handle float typed arrays with double sources
|
|
branch_eq t2, TYPED_ARRAY_KIND_FLOAT32, .ta_store_float32
|
|
branch_ne t2, TYPED_ARRAY_KIND_FLOAT64, .try_typed_array_slow
|
|
# Compute store address before check_is_double clobbers t4
|
|
mov t0, t4
|
|
shl t0, 3
|
|
add t0, t5
|
|
# Verify value is actually a double
|
|
check_is_double t1, .try_typed_array_slow
|
|
# Float64Array: store raw double bits
|
|
store64 [t0, 0], t1
|
|
dispatch_next
|
|
.ta_store_float32:
|
|
mov t0, t4
|
|
shl t0, 2
|
|
add t0, t5
|
|
check_is_double t1, .try_typed_array_slow
|
|
fp_mov ft0, t1
|
|
double_to_float ft0, ft0
|
|
storef32 [t0, 0], ft0
|
|
dispatch_next
|
|
.ta_store_int32:
|
|
# t1 = NaN-boxed int32, sign-extend it into t0
|
|
unbox_int32 t0, t1
|
|
# Dispatch on kind (in t2)
|
|
branch_any_eq t2, TYPED_ARRAY_KIND_INT32, TYPED_ARRAY_KIND_UINT32, .ta_put_int32
|
|
branch_eq t2, TYPED_ARRAY_KIND_FLOAT32, .ta_put_float32
|
|
branch_eq t2, TYPED_ARRAY_KIND_UINT8_CLAMPED, .ta_put_uint8_clamped
|
|
branch_any_eq t2, TYPED_ARRAY_KIND_UINT8, TYPED_ARRAY_KIND_INT8, .ta_put_uint8
|
|
branch_any_eq t2, TYPED_ARRAY_KIND_UINT16, TYPED_ARRAY_KIND_INT16, .ta_put_uint16
|
|
jmp .try_typed_array_slow
|
|
.ta_put_int32:
|
|
store32 [t5, t4, 4], t0
|
|
dispatch_next
|
|
.ta_put_float32:
|
|
int_to_double ft0, t0
|
|
double_to_float ft0, ft0
|
|
mov t3, t4
|
|
shl t3, 2
|
|
add t3, t5
|
|
storef32 [t3, 0], ft0
|
|
dispatch_next
|
|
.ta_put_uint8_clamped:
|
|
branch_negative t0, .ta_put_uint8_clamped_zero
|
|
mov t3, 255
|
|
branch_ge_unsigned t0, t3, .ta_put_uint8_clamped_max
|
|
store8 [t5, t4], t0
|
|
dispatch_next
|
|
.ta_put_uint8_clamped_zero:
|
|
mov t0, 0
|
|
store8 [t5, t4], t0
|
|
dispatch_next
|
|
.ta_put_uint8_clamped_max:
|
|
mov t0, 255
|
|
store8 [t5, t4], t0
|
|
dispatch_next
|
|
.ta_put_uint8:
|
|
store8 [t5, t4], t0
|
|
dispatch_next
|
|
.ta_put_uint16:
|
|
mov t3, t4
|
|
add t3, t4
|
|
add t3, t5
|
|
store16 [t3, 0], t0
|
|
dispatch_next
|
|
.try_typed_array_slow:
|
|
call_interp asm_try_put_by_value_typed_array
|
|
branch_nonzero t0, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_put_by_value
|
|
end
|
|
|
|
# Inline cache fast path for property access (own + prototype chain).
|
|
handler GetById
|
|
load_operand t1, m_base
|
|
# Check base is an object
|
|
extract_tag t2, t1
|
|
branch_ne t2, OBJECT_TAG, .try_cache
|
|
# Extract Object* from NaN-boxed value (sign-extend lower 48 bits)
|
|
unbox_object t3, t1
|
|
# Load Object.m_shape
|
|
load64 t4, [t3, OBJECT_SHAPE]
|
|
# Get PropertyLookupCache* (direct pointer from instruction stream)
|
|
load64 t5, [pb, pc, m_cache]
|
|
# Check entry[0].shape and entry[0].prototype.
|
|
load_pair64 t1, t0, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE]
|
|
branch_ne t1, t4, .try_cache
|
|
branch_nonzero t0, .proto
|
|
# Check dictionary generation matches
|
|
load_pair32 t1, t0, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
|
|
load32 t2, [t4, SHAPE_DICTIONARY_GENERATION]
|
|
branch_ne t0, t2, .try_cache
|
|
# IC hit! Load property value via get_direct (own property)
|
|
load64 t5, [t3, OBJECT_NAMED_PROPERTIES]
|
|
load64 t0, [t5, t1, 8]
|
|
# Check value is not an accessor
|
|
extract_tag t2, t0
|
|
branch_eq t2, ACCESSOR_TAG, .try_cache
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.proto:
|
|
# t0 = prototype Object*, t4 = object's shape, t5 = PLC base
|
|
# Check prototype chain validity (direct pointer, null = invalid)
|
|
load64 t1, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE_CHAIN_VALIDITY]
|
|
branch_zero t1, .try_cache
|
|
load8 t2, [t1, PROTOTYPE_CHAIN_VALIDITY_VALID]
|
|
branch_zero t2, .try_cache
|
|
# Check dictionary generation matches
|
|
load_pair32 t2, t1, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
|
|
load32 t4, [t4, SHAPE_DICTIONARY_GENERATION]
|
|
branch_ne t1, t4, .try_cache
|
|
# IC hit! Load property value via get_direct (from prototype)
|
|
load64 t1, [t0, OBJECT_NAMED_PROPERTIES]
|
|
load64 t0, [t1, t2, 8]
|
|
# Check value is not an accessor
|
|
extract_tag t1, t0
|
|
branch_eq t1, ACCESSOR_TAG, .try_cache
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.try_cache:
|
|
# Try all cache entries via C++ helper
|
|
call_interp asm_try_get_by_id_cache
|
|
branch_zero t0, .done
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_by_id
|
|
.done:
|
|
dispatch_next
|
|
end
|
|
|
|
# Inline cache fast path for own-property store (ChangeOwnProperty).
|
|
handler PutById
|
|
load_operand t1, m_base
|
|
# Check base is an object
|
|
extract_tag t2, t1
|
|
branch_ne t2, OBJECT_TAG, .try_cache
|
|
# Extract Object* from NaN-boxed value (sign-extend lower 48 bits)
|
|
unbox_object t3, t1
|
|
# Load Object.m_shape
|
|
load64 t4, [t3, OBJECT_SHAPE]
|
|
# Get PropertyLookupCache* (direct pointer from instruction stream)
|
|
load64 t5, [pb, pc, m_cache]
|
|
# Check entry[0].shape and entry[0].prototype.
|
|
load_pair64 t1, t0, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE]
|
|
branch_ne t1, t4, .try_cache
|
|
branch_nonzero t0, .try_cache
|
|
# Check dictionary generation matches
|
|
load_pair32 t1, t0, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
|
|
load32 t2, [t4, SHAPE_DICTIONARY_GENERATION]
|
|
branch_ne t0, t2, .try_cache
|
|
# Check current value at property_offset is not an accessor
|
|
load64 t5, [t3, OBJECT_NAMED_PROPERTIES]
|
|
load64 t2, [t5, t1, 8]
|
|
extract_tag t4, t2
|
|
branch_eq t4, ACCESSOR_TAG, .try_cache
|
|
# IC hit! Store new value via put_direct
|
|
# Save property offset in t4 before load_operand clobbers t0 (rax)
|
|
mov t4, t1
|
|
load_operand t1, m_src
|
|
store64 [t5, t4, 8], t1
|
|
dispatch_next
|
|
.try_cache:
|
|
# Try all cache entries via C++ helper (handles AddOwnProperty)
|
|
call_interp asm_try_put_by_id_cache
|
|
branch_zero t0, .done
|
|
.slow:
|
|
call_slow_path asm_slow_path_put_by_id
|
|
.done:
|
|
dispatch_next
|
|
end
|
|
|
|
# Fast path for array[int32_index] with Packed/Holey indexed storage.
|
|
handler GetByValue
|
|
load_operand t1, m_base
|
|
load_operand t2, m_property
|
|
# Check base is an object
|
|
extract_tag t3, t1
|
|
branch_ne t3, OBJECT_TAG, .slow
|
|
# Check property is non-negative int32
|
|
extract_tag t4, t2
|
|
branch_ne t4, INT32_TAG, .slow
|
|
mov t4, t2
|
|
and t4, 0xFFFFFFFF
|
|
# t4 = index (zero-extended u32)
|
|
# Check high bit (negative int32)
|
|
branch_bit_set t4, 31, .slow
|
|
# Extract Object*
|
|
unbox_object t3, t1
|
|
# Check IsTypedArray flag -- branch to C++ helper early
|
|
load8 t0, [t3, OBJECT_FLAGS]
|
|
branch_bits_set t0, OBJECT_FLAG_IS_TYPED_ARRAY, .try_typed_array
|
|
# Check !may_interfere_with_indexed_property_access
|
|
branch_bits_set t0, OBJECT_FLAG_MAY_INTERFERE, .slow
|
|
# Packed is the hot path: in-bounds elements are always present.
|
|
load8 t0, [t3, OBJECT_INDEXED_STORAGE_KIND]
|
|
branch_ne t0, INDEXED_STORAGE_KIND_PACKED, .not_packed
|
|
# Check index < array_like_size
|
|
load32 t5, [t3, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
|
|
branch_ge_unsigned t4, t5, .slow
|
|
load64 t5, [t3, OBJECT_INDEXED_ELEMENTS]
|
|
load64 t0, [t5, t4, 8]
|
|
# NB: No accessor check needed -- Packed/Holey storage
|
|
# can only hold default-attributed data properties.
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.not_packed:
|
|
branch_ne t0, INDEXED_STORAGE_KIND_HOLEY, .slow
|
|
# Holey arrays need a slot load to distinguish present elements from holes.
|
|
load32 t5, [t3, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
|
|
branch_ge_unsigned t4, t5, .slow
|
|
load64 t5, [t3, OBJECT_INDEXED_ELEMENTS]
|
|
branch_zero t5, .slow
|
|
mov t0, t5
|
|
sub t0, 8
|
|
load32 t0, [t0, 0]
|
|
branch_ge_unsigned t4, t0, .slow
|
|
load64 t0, [t5, t4, 8]
|
|
mov t5, EMPTY_TAG_SHIFTED
|
|
branch_eq t0, t5, .slow
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.try_typed_array:
|
|
# t3 = Object*, t4 = index (u32, non-negative)
|
|
# Load cached data pointer (pre-computed: buffer.data() + byte_offset)
|
|
# nullptr means uncached -> C++ helper will resolve the access.
|
|
load64 t5, [t3, TYPED_ARRAY_CACHED_DATA_PTR]
|
|
branch_zero t5, .try_typed_array_slow
|
|
# Cached pointers only exist for fixed-length typed arrays, so array_length
|
|
# is known to hold a concrete u32 value here.
|
|
load32 t0, [t3, TYPED_ARRAY_ARRAY_LENGTH_VALUE]
|
|
branch_ge_unsigned t4, t0, .try_typed_array_slow
|
|
# t5 = data base pointer, t4 = index
|
|
# Dispatch on kind
|
|
load8 t0, [t3, TYPED_ARRAY_KIND]
|
|
branch_eq t0, TYPED_ARRAY_KIND_INT32, .ta_int32
|
|
branch_any_eq t0, TYPED_ARRAY_KIND_UINT8, TYPED_ARRAY_KIND_UINT8_CLAMPED, .ta_uint8
|
|
branch_eq t0, TYPED_ARRAY_KIND_UINT16, .ta_uint16
|
|
branch_eq t0, TYPED_ARRAY_KIND_INT8, .ta_int8
|
|
branch_eq t0, TYPED_ARRAY_KIND_INT16, .ta_int16
|
|
branch_eq t0, TYPED_ARRAY_KIND_UINT32, .ta_uint32
|
|
branch_eq t0, TYPED_ARRAY_KIND_FLOAT32, .ta_float32
|
|
branch_eq t0, TYPED_ARRAY_KIND_FLOAT64, .ta_float64
|
|
jmp .try_typed_array_slow
|
|
.ta_int32:
|
|
load32 t0, [t5, t4, 4]
|
|
jmp .ta_box_int32
|
|
.ta_uint8:
|
|
load8 t0, [t5, t4]
|
|
jmp .ta_box_int32
|
|
.ta_uint16:
|
|
mov t0, t4
|
|
add t0, t4
|
|
load16 t0, [t5, t0]
|
|
jmp .ta_box_int32
|
|
.ta_int8:
|
|
load8s t0, [t5, t4]
|
|
jmp .ta_box_int32
|
|
.ta_int16:
|
|
mov t0, t4
|
|
add t0, t4
|
|
load16s t0, [t5, t0]
|
|
jmp .ta_box_int32
|
|
.ta_float32:
|
|
mov t0, t4
|
|
shl t0, 2
|
|
add t0, t5
|
|
loadf32 ft0, [t0, 0]
|
|
float_to_double ft0, ft0
|
|
fp_mov t1, ft0
|
|
mov t3, NEGATIVE_ZERO
|
|
branch_eq t1, t3, .ta_f64_as_double
|
|
double_to_int32 t0, ft0, .ta_f64_as_double
|
|
branch_nonzero t0, .ta_f64_as_int
|
|
jmp .ta_f64_as_int
|
|
.ta_float64:
|
|
# index * 8 for f64 elements
|
|
mov t0, t4
|
|
shl t0, 3
|
|
add t0, t5
|
|
load64 t1, [t0, 0]
|
|
fp_mov ft0, t1
|
|
# Exclude negative zero early (t1 gets clobbered by double_to_int32)
|
|
mov t3, NEGATIVE_ZERO
|
|
branch_eq t1, t3, .ta_f64_as_double
|
|
# Try to store as int32 if the value is an integer in i32 range.
|
|
double_to_int32 t0, ft0, .ta_f64_as_double
|
|
branch_nonzero t0, .ta_f64_as_int
|
|
# double_to_int32 succeeded with 0 -- this is +0.0, box as int
|
|
.ta_f64_as_int:
|
|
box_int32 t3, t0
|
|
store_operand m_dst, t3
|
|
dispatch_next
|
|
.ta_f64_as_double:
|
|
canonicalize_nan t0, ft0
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.ta_uint32:
|
|
load32 t0, [t5, t4, 4]
|
|
branch_bit_set t0, 31, .ta_uint32_to_double
|
|
jmp .ta_box_int32
|
|
.ta_uint32_to_double:
|
|
# Value > INT32_MAX, convert to double
|
|
int_to_double ft0, t0
|
|
fp_mov t0, ft0
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.ta_box_int32:
|
|
box_int32_clean t3, t0
|
|
store_operand m_dst, t3
|
|
dispatch_next
|
|
.try_typed_array_slow:
|
|
call_interp asm_try_get_by_value_typed_array
|
|
branch_nonzero t0, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_by_value
|
|
end
|
|
|
|
# Fast path for Array.length (magical length property).
|
|
# Also includes IC fast path for non-array objects (same as GetById).
|
|
handler GetLength
|
|
load_operand t1, m_base
|
|
# Check base is an object
|
|
extract_tag t2, t1
|
|
branch_ne t2, OBJECT_TAG, .slow
|
|
# Extract Object*
|
|
unbox_object t3, t1
|
|
# Check has_magical_length_property flag
|
|
load8 t0, [t3, OBJECT_FLAGS]
|
|
branch_bits_set t0, OBJECT_FLAG_HAS_MAGICAL_LENGTH, .magical_length
|
|
# Non-magical length: IC fast path (same as GetById)
|
|
load64 t4, [t3, OBJECT_SHAPE]
|
|
load64 t5, [pb, pc, m_cache]
|
|
# Check entry[0].shape and entry[0].prototype.
|
|
load_pair64 t1, t0, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE]
|
|
branch_ne t1, t4, .slow
|
|
branch_nonzero t0, .slow
|
|
# Check dictionary generation
|
|
load_pair32 t1, t0, [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [t5, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
|
|
load32 t2, [t4, SHAPE_DICTIONARY_GENERATION]
|
|
branch_ne t0, t2, .slow
|
|
# IC hit
|
|
load64 t5, [t3, OBJECT_NAMED_PROPERTIES]
|
|
load64 t0, [t5, t1, 8]
|
|
extract_tag t2, t0
|
|
branch_eq t2, ACCESSOR_TAG, .slow
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.magical_length:
|
|
# Object.m_indexed_array_like_size (u32)
|
|
load32 t0, [t3, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
|
|
# Box as int32 if fits (u32 always fits since bit 31 check is for sign)
|
|
mov t2, t0
|
|
shr t2, 31
|
|
branch_nonzero t2, .length_double
|
|
# Tag as int32
|
|
box_int32 t3, t0
|
|
store_operand m_dst, t3
|
|
dispatch_next
|
|
.length_double:
|
|
int_to_double ft0, t0
|
|
fp_mov t0, ft0
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_length
|
|
end
|
|
|
|
# Inline cache fast path for global variable access via the global object.
|
|
handler GetGlobal
|
|
# Load global_declarative_environment and global_object via realm
|
|
load64 t0, [exec_ctx, EXECUTION_CONTEXT_REALM]
|
|
load_pair64 t2, t1, [t0, REALM_GLOBAL_OBJECT], [t0, REALM_GLOBAL_DECLARATIVE_ENVIRONMENT]
|
|
# Get GlobalVariableCache* (direct pointer from instruction stream)
|
|
load64 t3, [pb, pc, m_cache]
|
|
# Check environment_serial_number matches
|
|
load64 t0, [t3, GLOBAL_VARIABLE_CACHE_ENVIRONMENT_SERIAL]
|
|
load64 t4, [t1, DECLARATIVE_ENVIRONMENT_SERIAL]
|
|
branch_ne t0, t4, .slow
|
|
# Shape-based fast path: check entries[0].shape matches global_object.shape
|
|
# (falls through to env binding path on shape mismatch)
|
|
load64 t4, [t2, OBJECT_SHAPE]
|
|
load64 t0, [t3, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE]
|
|
branch_ne t0, t4, .try_env_binding
|
|
# Check dictionary generation
|
|
load32 t5, [t4, SHAPE_DICTIONARY_GENERATION]
|
|
load_pair32 t4, t0, [t3, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [t3, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
|
|
branch_ne t0, t5, .try_env_binding
|
|
# IC hit! Load property value via get_direct
|
|
load64 t5, [t2, OBJECT_NAMED_PROPERTIES]
|
|
load64 t0, [t5, t4, 8]
|
|
# Check not accessor
|
|
extract_tag t5, t0
|
|
branch_eq t5, ACCESSOR_TAG, .slow
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.try_env_binding:
|
|
# Check if cache has an environment binding index (global let/const)
|
|
load8 t0, [t3, GLOBAL_VARIABLE_CACHE_HAS_ENVIRONMENT_BINDING]
|
|
branch_zero t0, .slow
|
|
# Bail to C++ for module environments (rare)
|
|
load8 t0, [t3, GLOBAL_VARIABLE_CACHE_IN_MODULE_ENVIRONMENT]
|
|
branch_nonzero t0, .slow_env
|
|
# Inline env binding: index into global_declarative_environment bindings
|
|
# t1 = global_declarative_environment (loaded at handler entry)
|
|
load32 t0, [t3, GLOBAL_VARIABLE_CACHE_ENVIRONMENT_BINDING_INDEX]
|
|
load64 t4, [t1, BINDINGS_DATA_PTR]
|
|
mul t0, t0, SIZEOF_BINDING
|
|
add t4, t0
|
|
# Check binding is initialized (TDZ)
|
|
load8 t0, [t4, BINDING_INITIALIZED]
|
|
branch_zero t0, .slow
|
|
# Load binding value
|
|
load64 t0, [t4, BINDING_VALUE]
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.slow_env:
|
|
call_interp asm_try_get_global_env_binding
|
|
branch_nonzero t0, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_get_global
|
|
end
|
|
|
|
# Inline cache fast path for global variable store via the global object.
|
|
handler SetGlobal
|
|
# Load global_declarative_environment and global_object via realm
|
|
load64 t0, [exec_ctx, EXECUTION_CONTEXT_REALM]
|
|
load_pair64 t2, t1, [t0, REALM_GLOBAL_OBJECT], [t0, REALM_GLOBAL_DECLARATIVE_ENVIRONMENT]
|
|
# Get GlobalVariableCache* (direct pointer from instruction stream)
|
|
load64 t3, [pb, pc, m_cache]
|
|
# Check environment_serial_number matches
|
|
load64 t0, [t3, GLOBAL_VARIABLE_CACHE_ENVIRONMENT_SERIAL]
|
|
load64 t4, [t1, DECLARATIVE_ENVIRONMENT_SERIAL]
|
|
branch_ne t0, t4, .slow
|
|
# Shape-based fast path: check entries[0].shape matches global_object.shape
|
|
# (falls through to env binding path on shape mismatch)
|
|
load64 t4, [t2, OBJECT_SHAPE]
|
|
load64 t0, [t3, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE]
|
|
branch_ne t0, t4, .try_env_binding
|
|
# Check dictionary generation
|
|
load32 t5, [t4, SHAPE_DICTIONARY_GENERATION]
|
|
load_pair32 t4, t0, [t3, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [t3, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
|
|
branch_ne t0, t5, .try_env_binding
|
|
# IC hit! Load current value to check it's not an accessor
|
|
load64 t5, [t2, OBJECT_NAMED_PROPERTIES]
|
|
load64 t0, [t5, t4, 8]
|
|
extract_tag t0, t0
|
|
branch_eq t0, ACCESSOR_TAG, .slow
|
|
# Store new value via put_direct
|
|
# NB: load_operand clobbers t0, so property offset stays in t4.
|
|
load_operand t0, m_src
|
|
store64 [t5, t4, 8], t0
|
|
dispatch_next
|
|
.try_env_binding:
|
|
# Check if cache has an environment binding index (global let/const)
|
|
load8 t0, [t3, GLOBAL_VARIABLE_CACHE_HAS_ENVIRONMENT_BINDING]
|
|
branch_zero t0, .slow
|
|
# Bail to C++ for module environments (rare)
|
|
load8 t0, [t3, GLOBAL_VARIABLE_CACHE_IN_MODULE_ENVIRONMENT]
|
|
branch_nonzero t0, .slow_env
|
|
# Inline env binding: index into global_declarative_environment bindings
|
|
# t1 = global_declarative_environment (loaded at handler entry)
|
|
load32 t0, [t3, GLOBAL_VARIABLE_CACHE_ENVIRONMENT_BINDING_INDEX]
|
|
load64 t4, [t1, BINDINGS_DATA_PTR]
|
|
mul t0, t0, SIZEOF_BINDING
|
|
add t4, t0
|
|
# Check binding is initialized (TDZ) and mutable
|
|
load8 t0, [t4, BINDING_INITIALIZED]
|
|
branch_zero t0, .slow
|
|
load8 t0, [t4, BINDING_MUTABLE]
|
|
branch_zero t0, .slow
|
|
# Store value into binding
|
|
load_operand t0, m_src
|
|
store64 [t4, BINDING_VALUE], t0
|
|
dispatch_next
|
|
.slow_env:
|
|
call_interp asm_try_set_global_env_binding
|
|
branch_nonzero t0, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_set_global
|
|
end
|
|
|
|
handler Call
|
|
# Inline Call in asm for the two callee kinds that can stay in the
|
|
# dispatch loop without taking the full Call slow path:
|
|
#
|
|
# - ECMAScriptFunctionObject with inline-ready bytecode: build the
|
|
# callee frame here and dispatch at pc = 0 of the callee bytecode.
|
|
# Cases that need function-environment allocation or sloppy primitive
|
|
# this-boxing can't stay in pure asm but also don't want the full
|
|
# slow path (which would insert a run_executable() boundary and an
|
|
# observable microtask drain), so they detour through the
|
|
# asm_try_inline_call helper at .call_interp_inline.
|
|
#
|
|
# - RawNativeFunction: build a callee ExecutionContext here, call the
|
|
# stored C++ function pointer directly via call_raw_native, and then
|
|
# tear the frame down on return. Exceptions go through a dedicated
|
|
# helper that unwinds the callee frame before dispatching to a JS
|
|
# handler (see .call_raw_native_exception).
|
|
#
|
|
# Everything else — non-functions, NativeJavaScriptBackedFunction,
|
|
# ECMAScript functions that can't inline, Proxies, ... — falls through
|
|
# to .call_slow, i.e. asm_slow_path_call.
|
|
#
|
|
# High-level flow of the ECMAScript fast path:
|
|
# 1. Validate the callee and load its shared function metadata.
|
|
# 2. Bind `this` inline when we can do so without allocations.
|
|
# 3. Reserve an InterpreterStack frame and populate ExecutionContext.
|
|
# 4. Materialize [registers | locals | constants | arguments].
|
|
# 5. Swap VM state over to the callee frame and dispatch at pc = 0.
|
|
#
|
|
# Register usage within this handler:
|
|
# t3 = callee ECMAScriptFunctionObject*
|
|
# t2 = asm-call metadata / later callee ExecutionContext*
|
|
# t7 = callee Executable* carried across `this` binding
|
|
# t8 = boxed `this` value carried into the callee
|
|
load_operand t0, m_callee
|
|
extract_tag t1, t0
|
|
branch_ne t1, OBJECT_TAG, .call_slow
|
|
unbox_object t0, t0
|
|
mov t3, t0
|
|
|
|
# Non-functions still go through the normal Call slow path for proper error
|
|
# reporting. Non-ECMAScript function objects get a RawNativeFunction fast
|
|
# path attempt before we fully give up.
|
|
load8 t1, [t3, OBJECT_FLAGS]
|
|
branch_bits_clear t1, OBJECT_FLAG_IS_FUNCTION, .call_slow
|
|
branch_bits_clear t1, OBJECT_FLAG_IS_ECMASCRIPT_FUNCTION_OBJECT, .call_try_native
|
|
|
|
load64 t2, [t3, ECMASCRIPT_FUNCTION_OBJECT_SHARED_DATA]
|
|
load_pair64 t7, t2, [t2, SHARED_FUNCTION_INSTANCE_DATA_EXECUTABLE], [t2, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA]
|
|
branch_bits_clear t2, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_CAN_INLINE_CALL, .call_slow
|
|
# NewFunctionEnvironment() allocates and has to stay out of the pure asm
|
|
# path, but we still preserve inline-call semantics via .call_interp_inline.
|
|
branch_bits_set t2, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_FUNCTION_ENVIRONMENT_NEEDED, .call_interp_inline
|
|
|
|
# Bind this without allocations. Sloppy primitive this-values still need
|
|
# ToObject(), so they use the C++ inline-frame helper.
|
|
#
|
|
# t8 starts as "empty" to match the normal interpreter behavior for
|
|
# callees that never observe `this`.
|
|
mov t8, EMPTY_TAG_SHIFTED
|
|
branch_bits_clear t2, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_USES_THIS, .this_ready
|
|
load_operand t8, m_this_value
|
|
branch_bits_set t2, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_STRICT, .this_ready
|
|
|
|
# Sloppy null/undefined binds the callee realm's global object.
|
|
# Sloppy primitive receivers need ToObject(), which may allocate wrappers,
|
|
# so they go through the helper instead of the full Call slow path.
|
|
extract_tag t1, t8
|
|
mov t0, t1
|
|
and t0, 0xFFFE
|
|
branch_eq t0, UNDEFINED_TAG, .sloppy_global_this
|
|
branch_eq t1, OBJECT_TAG, .this_ready
|
|
jmp .call_interp_inline
|
|
|
|
.sloppy_global_this:
|
|
load64 t1, [t3, OBJECT_SHAPE]
|
|
load64 t1, [t1, SHAPE_REALM]
|
|
load64 t1, [t1, REALM_GLOBAL_ENVIRONMENT]
|
|
load64 t1, [t1, GLOBAL_ENVIRONMENT_GLOBAL_THIS_VALUE]
|
|
# Match Value(Object*): keep only the low 48 pointer bits before boxing.
|
|
shl t1, 16
|
|
shr t1, 16
|
|
mov t8, OBJECT_TAG_SHIFTED
|
|
or t8, t1
|
|
|
|
.this_ready:
|
|
# The low 32 bits of the packed metadata word hold the formal parameter count.
|
|
and t2, 0xFFFFFFFF
|
|
|
|
load32 t6, [pb, pc, m_argument_count]
|
|
mov t4, t2
|
|
branch_ge_unsigned t4, t6, .arg_count_ready
|
|
mov t4, t6
|
|
.arg_count_ready:
|
|
load_pair32 t5, t1, [t7, EXECUTABLE_REGISTERS_AND_LOCALS_COUNT], [t7, EXECUTABLE_REGISTERS_AND_LOCALS_AND_CONSTANTS_COUNT]
|
|
|
|
# Inline InterpreterStack::allocate().
|
|
# t1 = total Value slots, t2 = new stack top, t6 = current frame base.
|
|
add t1, t4
|
|
mov t2, t1
|
|
shl t2, 3
|
|
add t2, SIZEOF_EXECUTION_CONTEXT
|
|
|
|
load_vm t0
|
|
lea t0, [t0, VM_INTERPRETER_STACK]
|
|
load_pair64 t6, t0, [t0, INTERPRETER_STACK_TOP], [t0, INTERPRETER_STACK_LIMIT]
|
|
add t2, t6
|
|
branch_ge_unsigned t0, t2, .stack_ok
|
|
jmp .call_slow
|
|
|
|
.stack_ok:
|
|
load_vm t0
|
|
store64 [t0, VM_INTERPRETER_STACK_TOP], t2
|
|
|
|
# Set up the callee ExecutionContext header exactly the way
|
|
# VM::push_inline_frame() / run_executable() would see it.
|
|
mov t2, t6
|
|
lea t6, [t6, SIZEOF_EXECUTION_CONTEXT]
|
|
store_pair32 [t2, EXECUTION_CONTEXT_REGISTERS_AND_CONSTANTS_AND_LOCALS_AND_ARGUMENTS_COUNT], [t2, EXECUTION_CONTEXT_ARGUMENT_COUNT], t1, t4
|
|
load32 t0, [pb, pc, m_argument_count]
|
|
store32 [t2, EXECUTION_CONTEXT_PASSED_ARGUMENT_COUNT], t0
|
|
|
|
load64 t0, [t3, OBJECT_SHAPE]
|
|
load64 t0, [t0, SHAPE_REALM]
|
|
store_pair64 [t2, EXECUTION_CONTEXT_FUNCTION], [t2, EXECUTION_CONTEXT_REALM], t3, t0
|
|
|
|
load_pair64 t0, t1, [t3, ECMASCRIPT_FUNCTION_OBJECT_ENVIRONMENT], [t3, ECMASCRIPT_FUNCTION_OBJECT_PRIVATE_ENVIRONMENT]
|
|
store_pair64 [t2, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT], [t2, EXECUTION_CONTEXT_VARIABLE_ENVIRONMENT], t0, t0
|
|
store64 [t2, EXECUTION_CONTEXT_PRIVATE_ENVIRONMENT], t1
|
|
store_pair64 [t2, EXECUTION_CONTEXT_THIS_VALUE], [t2, EXECUTION_CONTEXT_EXECUTABLE], t8, t7
|
|
|
|
mov t1, EMPTY_TAG_SHIFTED
|
|
store_pair64 [t6, ACCUMULATOR_REG_OFFSET], [t6, EXCEPTION_REG_OFFSET], t1, t1
|
|
store64 [t6, THIS_VALUE_REG_OFFSET], t8
|
|
store_pair64 [t6, RETURN_VALUE_REG_OFFSET], [t6, SAVED_LEXICAL_ENVIRONMENT_REG_OFFSET], t1, t1
|
|
|
|
# ScriptOrModule is a two-word Variant in ExecutionContext, so copy both
|
|
# machine words explicitly.
|
|
lea t0, [t2, EXECUTION_CONTEXT_SCRIPT_OR_MODULE]
|
|
lea t7, [t3, ECMASCRIPT_FUNCTION_OBJECT_SCRIPT_OR_MODULE]
|
|
load_pair64 t3, t8, [t7, 0], [t7, 8]
|
|
store64 [t0, 0], t3
|
|
store64 [t0, 8], t8
|
|
|
|
store32 [t2, EXECUTION_CONTEXT_PROGRAM_COUNTER], 0
|
|
store32 [t2, EXECUTION_CONTEXT_SKIP_WHEN_DETERMINING_INCUMBENT_COUNTER], 0
|
|
mov t0, EXECUTION_CONTEXT_NO_YIELD_CONTINUATION
|
|
store32 [t2, EXECUTION_CONTEXT_YIELD_CONTINUATION], t0
|
|
store8 [t2, EXECUTION_CONTEXT_YIELD_IS_AWAIT], 0
|
|
store8 [t2, EXECUTION_CONTEXT_CALLER_IS_CONSTRUCT], 0
|
|
store64 [t2, EXECUTION_CONTEXT_CALLER_FRAME], exec_ctx
|
|
load_pair32 t0, t1, [pb, pc, m_length], [pb, pc, m_dst]
|
|
lea t3, [pb, pc]
|
|
sub t3, pb
|
|
add t0, t3
|
|
store_pair32 [t2, EXECUTION_CONTEXT_CALLER_RETURN_PC], [t2, EXECUTION_CONTEXT_CALLER_DST_RAW], t0, t1
|
|
|
|
# values = [registers | locals | constants | arguments]
|
|
# Keep t2 at the ExecutionContext base while t6 walks the Value tail.
|
|
mov t0, t5
|
|
shl t0, 3
|
|
mov t3, RESERVED_REGISTERS_SIZE
|
|
.clear_registers_and_locals:
|
|
mov t8, t3
|
|
add t8, 8
|
|
branch_ge_unsigned t8, t0, .clear_registers_and_locals_tail
|
|
store_pair64 [t6, t3, 0], [t6, t3, 8], t1, t1
|
|
add t3, 16
|
|
jmp .clear_registers_and_locals
|
|
|
|
.clear_registers_and_locals_tail:
|
|
branch_ge_unsigned t3, t0, .copy_constants
|
|
store64 [t6, t3], t1
|
|
|
|
.copy_constants:
|
|
load64 t0, [t2, EXECUTION_CONTEXT_EXECUTABLE]
|
|
load_pair64 t3, t0, [t0, EXECUTABLE_ASM_CONSTANTS_SIZE], [t0, EXECUTABLE_ASM_CONSTANTS_DATA]
|
|
mov t1, t5
|
|
xor t8, t8
|
|
.copy_constants_loop:
|
|
branch_ge_unsigned t8, t3, .copy_arguments
|
|
load64 t7, [t0, t8, 8]
|
|
store64 [t6, t1, 8], t7
|
|
add t8, 1
|
|
add t1, 1
|
|
jmp .copy_constants_loop
|
|
|
|
.copy_arguments:
|
|
load32 t7, [pb, pc, m_argument_count]
|
|
mov t1, t5
|
|
add t1, t3
|
|
lea t0, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
lea t8, [pb, pc]
|
|
add t8, m_expression_string
|
|
add t8, 4
|
|
xor t3, t3
|
|
.copy_arguments_loop:
|
|
# The operand array in the bytecode stores caller register indices.
|
|
branch_ge_unsigned t3, t7, .fill_missing_arguments
|
|
load32 t5, [t8, t3, 4]
|
|
load64 t5, [t0, t5, 8]
|
|
store64 [t6, t1, 8], t5
|
|
add t3, 1
|
|
add t1, 1
|
|
jmp .copy_arguments_loop
|
|
|
|
.fill_missing_arguments:
|
|
mov t3, t1
|
|
add t3, t4
|
|
sub t3, t7
|
|
mov t0, UNDEFINED_SHIFTED
|
|
.fill_missing_arguments_loop:
|
|
branch_ge_unsigned t1, t3, .enter_callee
|
|
store64 [t6, t1, 8], t0
|
|
add t1, 1
|
|
jmp .fill_missing_arguments_loop
|
|
|
|
.enter_callee:
|
|
load64 pb, [t2, EXECUTION_CONTEXT_EXECUTABLE]
|
|
load64 pb, [pb, EXECUTABLE_BYTECODE_DATA]
|
|
load_vm t0
|
|
store64 [t0, VM_RUNNING_EXECUTION_CONTEXT], t2
|
|
mov exec_ctx, t2
|
|
lea values, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
xor pc, pc
|
|
goto_handler pc
|
|
.call_interp_inline:
|
|
# Shared escape hatch for the cases that need C++ help to build the inline
|
|
# frame correctly but must not take the full Call slow path, since that
|
|
# would insert a run_executable() boundary and observable microtask drain.
|
|
call_interp asm_try_inline_call
|
|
branch_nonzero t0, .call_slow
|
|
load_vm t0
|
|
load64 exec_ctx, [t0, VM_RUNNING_EXECUTION_CONTEXT]
|
|
lea values, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
load64 t0, [exec_ctx, EXECUTION_CONTEXT_EXECUTABLE]
|
|
load64 pb, [t0, EXECUTABLE_BYTECODE_DATA]
|
|
xor pc, pc
|
|
goto_handler pc
|
|
.call_try_native:
|
|
# Fast path for RawNativeFunction: the callee is a plain C++ function
|
|
# pointer with no JS-visible prologue, so we can build the callee frame
|
|
# ourselves and jump straight at the entry point. NativeFunction objects
|
|
# that still carry a callback (NativeJavaScriptBackedFunction) do not have
|
|
# this flag set and fall through to .call_slow.
|
|
load8 t0, [t3, OBJECT_FLAGS]
|
|
branch_bits_clear t0, OBJECT_FLAG_IS_RAW_NATIVE_FUNCTION, .call_slow
|
|
|
|
# Unlike the ECMAScript path we don't pad to the formal parameter count:
|
|
# native functions read their arguments via the passed-count API, so we
|
|
# only need space for the call-site arguments plus the ExecutionContext
|
|
# header. t4 = argument count, t5 = total bytes needed for this frame.
|
|
load32 t4, [pb, pc, m_argument_count]
|
|
mov t5, t4
|
|
shl t5, 3
|
|
add t5, SIZEOF_EXECUTION_CONTEXT
|
|
|
|
# Inline InterpreterStack::allocate(): bail to C++ if the interpreter
|
|
# stack doesn't have room for the new frame. t6 = new frame base (old
|
|
# top), t5 becomes the new top after the add below.
|
|
load_vm t0
|
|
lea t0, [t0, VM_INTERPRETER_STACK]
|
|
load_pair64 t6, t7, [t0, INTERPRETER_STACK_TOP], [t0, INTERPRETER_STACK_LIMIT]
|
|
add t5, t6
|
|
branch_ge_unsigned t7, t5, .native_interpreter_stack_ok
|
|
jmp .call_slow
|
|
|
|
.native_interpreter_stack_ok:
|
|
# RawNativeFunctions run real C++ code on the host stack, so we also have
|
|
# to check that we're not about to blow past the VM's reserved stack
|
|
# limit. The ECMAScript path can skip this because it never leaves asm.
|
|
load_vm t0
|
|
lea t0, [t0, VM_STACK_INFO]
|
|
load64 t7, [t0, STACK_INFO_BASE]
|
|
add t7, VM_STACK_SPACE_LIMIT
|
|
branch_ge_unsigned fp, t7, .native_stack_space_ok
|
|
jmp .call_slow
|
|
|
|
.native_stack_space_ok:
|
|
# Commit the new interpreter stack top. From here on we own [t6, t5).
|
|
load_vm t0
|
|
store64 [t0, VM_INTERPRETER_STACK_TOP], t5
|
|
|
|
# Populate the callee ExecutionContext to match what VM::push_execution_context
|
|
# plus NativeFunction::internal_call would produce. t2 tracks the EC
|
|
# header, t6 advances to the argument Value array that follows it.
|
|
mov t2, t6
|
|
lea t6, [t6, SIZEOF_EXECUTION_CONTEXT]
|
|
# For natives, argument_count and "registers+..." total are both just the
|
|
# call-site argument count: there are no registers, locals, or constants.
|
|
store_pair32 [t2, EXECUTION_CONTEXT_REGISTERS_AND_CONSTANTS_AND_LOCALS_AND_ARGUMENTS_COUNT], [t2, EXECUTION_CONTEXT_ARGUMENT_COUNT], t4, t4
|
|
store32 [t2, EXECUTION_CONTEXT_PASSED_ARGUMENT_COUNT], t4
|
|
|
|
# Shape stores a Realm pointer; use it as the callee EC realm.
|
|
load64 t0, [t3, OBJECT_SHAPE]
|
|
load64 t0, [t0, SHAPE_REALM]
|
|
store_pair64 [t2, EXECUTION_CONTEXT_FUNCTION], [t2, EXECUTION_CONTEXT_REALM], t3, t0
|
|
|
|
# Mirror NativeFunction::internal_call: a raw native has no environment of
|
|
# its own, so lexical/variable/private environments are copied straight
|
|
# from the caller frame.
|
|
load_pair64 t0, t7, [exec_ctx, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT], [exec_ctx, EXECUTION_CONTEXT_VARIABLE_ENVIRONMENT]
|
|
store_pair64 [t2, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT], [t2, EXECUTION_CONTEXT_VARIABLE_ENVIRONMENT], t0, t7
|
|
load64 t0, [exec_ctx, EXECUTION_CONTEXT_PRIVATE_ENVIRONMENT]
|
|
store64 [t2, EXECUTION_CONTEXT_PRIVATE_ENVIRONMENT], t0
|
|
# |this| is forwarded unchanged. Native builtins do their own type checks
|
|
# on the receiver where they need to.
|
|
load_operand t0, m_this_value
|
|
store64 [t2, EXECUTION_CONTEXT_THIS_VALUE], t0
|
|
|
|
# Zero out the ScriptOrModule variant (two words) and Executable pointer.
|
|
# Native frames don't belong to any script/module and have no bytecode.
|
|
xor t0, t0
|
|
lea t7, [t2, EXECUTION_CONTEXT_SCRIPT_OR_MODULE]
|
|
store_pair64 [t7, 0], [t7, 8], t0, t0
|
|
store64 [t2, EXECUTION_CONTEXT_EXECUTABLE], t0
|
|
store32 [t2, EXECUTION_CONTEXT_PROGRAM_COUNTER], 0
|
|
store32 [t2, EXECUTION_CONTEXT_SKIP_WHEN_DETERMINING_INCUMBENT_COUNTER], 0
|
|
mov t0, EXECUTION_CONTEXT_NO_YIELD_CONTINUATION
|
|
store32 [t2, EXECUTION_CONTEXT_YIELD_CONTINUATION], t0
|
|
store8 [t2, EXECUTION_CONTEXT_YIELD_IS_AWAIT], 0
|
|
store8 [t2, EXECUTION_CONTEXT_CALLER_IS_CONSTRUCT], 0
|
|
|
|
# While asm runs, the authoritative program counter lives in the `pc`
|
|
# register and the caller EC's stored program_counter is stale. Before we
|
|
# leave asm to run native C++ that may throw, sync `pc` into the caller
|
|
# EC as a bytecode offset (pc - pb). asm_helper_handle_raw_native_exception
|
|
# and VM::handle_exception both read from the caller EC after unwind.
|
|
lea t7, [pb, pc]
|
|
sub t7, pb
|
|
store32 [exec_ctx, EXECUTION_CONTEXT_PROGRAM_COUNTER], t7
|
|
store64 [t2, EXECUTION_CONTEXT_CALLER_FRAME], exec_ctx
|
|
# CALLER_RETURN_PC is the bytecode offset of the instruction after the
|
|
# Call (Call offset + Call length). CALLER_DST_RAW records where the
|
|
# return value should be written in the caller's value array.
|
|
load32 t0, [pb, pc, m_length]
|
|
add t0, t7
|
|
store32 [t2, EXECUTION_CONTEXT_CALLER_RETURN_PC], t0
|
|
load32 t0, [pb, pc, m_dst]
|
|
store32 [t2, EXECUTION_CONTEXT_CALLER_DST_RAW], t0
|
|
|
|
# Copy the call-site arguments from the caller's value array into the
|
|
# callee frame's argument tail. t0 points at the caller's value array,
|
|
# t8 at the Operand[] that trails the fixed Call instruction fields.
|
|
# The Call layout ends with `m_expression_string: Optional<StringTableIndex>`
|
|
# (4 bytes via the sentinel specialization) followed by `m_arguments`, so
|
|
# base + offsetof(m_expression_string) + 4 is the operand array.
|
|
lea t0, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
lea t8, [pb, pc]
|
|
add t8, m_expression_string
|
|
add t8, 4
|
|
xor t7, t7
|
|
.copy_native_arguments_loop:
|
|
branch_ge_unsigned t7, t4, .enter_raw_native
|
|
load32 t5, [t8, t7, 4]
|
|
load64 t5, [t0, t5, 8]
|
|
store64 [t6, t7, 8], t5
|
|
add t7, 1
|
|
jmp .copy_native_arguments_loop
|
|
|
|
.enter_raw_native:
|
|
# Swap the running ExecutionContext over to the callee and point the
|
|
# asm `values` register at its argument array. After this, we look like
|
|
# a normal inline frame from the VM's perspective.
|
|
load_vm t0
|
|
store64 [t0, VM_RUNNING_EXECUTION_CONTEXT], t2
|
|
mov exec_ctx, t2
|
|
lea values, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
|
|
# Invoke the raw C++ function pointer. call_raw_native lowers to a native
|
|
# call through the platform ABI and surfaces the returned
|
|
# ThrowCompletionOr<Value> via (t0, t1): t0 is the Value payload and t1
|
|
# holds the Variant discriminator in its low byte (0 = Value, 1 =
|
|
# ErrorValue). Anything non-zero in that byte means the native threw and
|
|
# t0 is the thrown Value, not a return value.
|
|
load64 t3, [t3, RAW_NATIVE_FUNCTION_NATIVE_FUNCTION]
|
|
call_raw_native t3
|
|
and t1, 0xFF
|
|
branch_nonzero t1, .call_raw_native_exception
|
|
|
|
# Normal return path: tear the callee frame off the interpreter stack,
|
|
# restore the caller as the running ExecutionContext, write the return
|
|
# value into the caller's m_dst operand, and dispatch the next insn.
|
|
load64 t2, [exec_ctx, EXECUTION_CONTEXT_CALLER_FRAME]
|
|
load_vm t3
|
|
store64 [t3, VM_RUNNING_EXECUTION_CONTEXT], t2
|
|
store64 [t3, VM_INTERPRETER_STACK_TOP], exec_ctx
|
|
mov exec_ctx, t2
|
|
lea values, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
store_operand m_dst, t0
|
|
load32 t0, [pb, pc, m_length]
|
|
dispatch_variable t0
|
|
|
|
.call_raw_native_exception:
|
|
# The native threw. Hand the thrown Value off to a C++ helper, which
|
|
# unwinds the callee frame off the interpreter stack and calls through
|
|
# to VM::handle_exception. Return value (t0) follows the standard asm
|
|
# slow-path convention (see AsmInterpreter.cpp:127):
|
|
# >= 0 : an enclosing handler was found; t0 is the new program counter
|
|
# to resume at inside the (post-unwind) running execution context.
|
|
# < 0 : no handler; bail out of the asm dispatch loop entirely.
|
|
mov t1, t0
|
|
call_helper asm_helper_handle_raw_native_exception
|
|
branch_negative t0, .call_exit_asm
|
|
jmp .call_exception_handled
|
|
.call_exception_handled:
|
|
# Reload exec_ctx/values/pb/pc from the caller frame the helper left us
|
|
# on, and resume dispatching at its program_counter (which the helper
|
|
# already updated to the handler entry).
|
|
load_vm t0
|
|
load64 exec_ctx, [t0, VM_RUNNING_EXECUTION_CONTEXT]
|
|
lea values, [exec_ctx, SIZEOF_EXECUTION_CONTEXT]
|
|
load64 t0, [exec_ctx, EXECUTION_CONTEXT_EXECUTABLE]
|
|
load64 pb, [t0, EXECUTABLE_BYTECODE_DATA]
|
|
load32 t2, [exec_ctx, EXECUTION_CONTEXT_PROGRAM_COUNTER]
|
|
mov pc, t2
|
|
goto_handler pc
|
|
.call_exit_asm:
|
|
# No JS handler caught the native exception; bail out of the asm
|
|
# dispatch loop and let the C++ caller of run_asm() see the throw.
|
|
exit
|
|
.call_slow:
|
|
call_slow_path asm_slow_path_call
|
|
end
|
|
|
|
# Fast paths for common Math builtins with a single double argument.
|
|
# Before using the fast path, we validate that the callee is still the
|
|
# original builtin function (user code may have reassigned e.g. Math.abs).
|
|
handler CallBuiltinMathAbs
|
|
validate_callee_builtin BUILTIN_MATH_ABS, .slow
|
|
load_operand t1, m_argument
|
|
check_is_double t1, .try_abs_int32
|
|
# abs(double) = clear sign bit (bit 63)
|
|
clear_bit t1, 63
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
.try_abs_int32:
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
# abs(int32): negate if negative
|
|
unbox_int32 t3, t1
|
|
branch_not_negative t3, .abs_positive
|
|
neg32_overflow t3, .abs_overflow
|
|
.abs_positive:
|
|
box_int32_clean t4, t3
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.abs_overflow:
|
|
# INT32_MIN: abs(-2147483648) = 2147483648.0
|
|
unbox_int32 t3, t1
|
|
neg t3
|
|
int_to_double ft0, t3
|
|
fp_mov t4, ft0
|
|
store_operand m_dst, t4
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_math_abs
|
|
end
|
|
|
|
handler CallBuiltinMathFloor
|
|
validate_callee_builtin BUILTIN_MATH_FLOOR, .slow
|
|
load_operand t1, m_argument
|
|
check_is_double t1, .slow
|
|
fp_mov ft0, t1
|
|
fp_floor ft0, ft0
|
|
box_double_or_int32 t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_math_floor
|
|
end
|
|
|
|
handler CallBuiltinMathCeil
|
|
validate_callee_builtin BUILTIN_MATH_CEIL, .slow
|
|
load_operand t1, m_argument
|
|
check_is_double t1, .slow
|
|
fp_mov ft0, t1
|
|
fp_ceil ft0, ft0
|
|
box_double_or_int32 t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_math_ceil
|
|
end
|
|
|
|
handler CallBuiltinMathSqrt
|
|
validate_callee_builtin BUILTIN_MATH_SQRT, .slow
|
|
load_operand t1, m_argument
|
|
check_is_double t1, .slow
|
|
fp_mov ft0, t1
|
|
fp_sqrt ft0, ft0
|
|
box_double_or_int32 t5, ft0
|
|
store_operand m_dst, t5
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_math_sqrt
|
|
end
|
|
|
|
handler CallBuiltinMathExp
|
|
validate_callee_builtin BUILTIN_MATH_EXP, .slow
|
|
load_operand t1, m_argument
|
|
check_is_double t1, .slow
|
|
fp_mov ft0, t1
|
|
call_helper asm_helper_math_exp
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_math_exp
|
|
end
|
|
|
|
handler CallBuiltinMathLog
|
|
call_slow_path asm_slow_path_call_builtin_math_log
|
|
end
|
|
|
|
handler CallBuiltinMathPow
|
|
call_slow_path asm_slow_path_call_builtin_math_pow
|
|
end
|
|
|
|
handler CallBuiltinMathImul
|
|
call_slow_path asm_slow_path_call_builtin_math_imul
|
|
end
|
|
|
|
handler CallBuiltinMathRandom
|
|
call_slow_path asm_slow_path_call_builtin_math_random
|
|
end
|
|
|
|
handler CallBuiltinMathRound
|
|
call_slow_path asm_slow_path_call_builtin_math_round
|
|
end
|
|
|
|
handler CallBuiltinMathSin
|
|
call_slow_path asm_slow_path_call_builtin_math_sin
|
|
end
|
|
|
|
handler CallBuiltinMathCos
|
|
call_slow_path asm_slow_path_call_builtin_math_cos
|
|
end
|
|
|
|
handler CallBuiltinMathTan
|
|
call_slow_path asm_slow_path_call_builtin_math_tan
|
|
end
|
|
|
|
handler CallBuiltinRegExpPrototypeExec
|
|
call_slow_path asm_slow_path_call_builtin_regexp_prototype_exec
|
|
end
|
|
|
|
handler CallBuiltinRegExpPrototypeReplace
|
|
call_slow_path asm_slow_path_call_builtin_regexp_prototype_replace
|
|
end
|
|
|
|
handler CallBuiltinRegExpPrototypeSplit
|
|
call_slow_path asm_slow_path_call_builtin_regexp_prototype_split
|
|
end
|
|
|
|
handler CallBuiltinOrdinaryHasInstance
|
|
call_slow_path asm_slow_path_call_builtin_ordinary_has_instance
|
|
end
|
|
|
|
handler CallBuiltinArrayIteratorPrototypeNext
|
|
call_slow_path asm_slow_path_call_builtin_array_iterator_prototype_next
|
|
end
|
|
|
|
handler CallBuiltinMapIteratorPrototypeNext
|
|
call_slow_path asm_slow_path_call_builtin_map_iterator_prototype_next
|
|
end
|
|
|
|
handler CallBuiltinSetIteratorPrototypeNext
|
|
call_slow_path asm_slow_path_call_builtin_set_iterator_prototype_next
|
|
end
|
|
|
|
handler CallBuiltinStringIteratorPrototypeNext
|
|
call_slow_path asm_slow_path_call_builtin_string_iterator_prototype_next
|
|
end
|
|
|
|
handler CallBuiltinStringFromCharCode
|
|
validate_callee_builtin BUILTIN_STRING_FROM_CHAR_CODE, .slow
|
|
|
|
load_operand t1, m_argument
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
unbox_int32 t0, t1
|
|
and t0, 0xffff
|
|
branch_ge_unsigned t0, 0x80, .single_code_unit
|
|
|
|
mov t1, t0
|
|
call_helper asm_helper_single_ascii_character_string
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
|
|
.single_code_unit:
|
|
mov t1, t0
|
|
call_helper asm_helper_single_utf16_code_unit_string
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_string_from_char_code
|
|
end
|
|
|
|
handler CallBuiltinStringPrototypeCharCodeAt
|
|
validate_callee_builtin BUILTIN_STRING_PROTOTYPE_CHAR_CODE_AT, .slow
|
|
|
|
load_operand t1, m_this_value
|
|
extract_tag t3, t1
|
|
branch_ne t3, STRING_TAG, .slow
|
|
unbox_object t2, t1
|
|
|
|
load_operand t1, m_argument
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
unbox_int32 t4, t1
|
|
branch_negative t4, .out_of_bounds
|
|
|
|
load_primitive_string_utf16_code_unit .out_of_bounds, .slow
|
|
box_int32_clean t1, t0
|
|
store_operand m_dst, t1
|
|
dispatch_next
|
|
|
|
.out_of_bounds:
|
|
mov t0, CANON_NAN_BITS
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_string_prototype_char_code_at
|
|
end
|
|
|
|
handler CallBuiltinStringPrototypeCharAt
|
|
validate_callee_builtin BUILTIN_STRING_PROTOTYPE_CHAR_AT, .slow
|
|
|
|
load_operand t1, m_this_value
|
|
extract_tag t3, t1
|
|
branch_ne t3, STRING_TAG, .slow
|
|
unbox_object t2, t1
|
|
|
|
load_operand t1, m_argument
|
|
extract_tag t3, t1
|
|
branch_ne t3, INT32_TAG, .slow
|
|
unbox_int32 t4, t1
|
|
branch_negative t4, .empty
|
|
|
|
load_primitive_string_utf16_code_unit .empty, .slow
|
|
branch_ge_unsigned t0, 0x80, .slow
|
|
|
|
mov t1, t0
|
|
call_helper asm_helper_single_ascii_character_string
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
|
|
.empty:
|
|
mov t1, 0
|
|
call_helper asm_helper_empty_string
|
|
store_operand m_dst, t0
|
|
dispatch_next
|
|
|
|
.slow:
|
|
call_slow_path asm_slow_path_call_builtin_string_prototype_char_at
|
|
end
|
|
|
|
# ============================================================================
|
|
# Slow-path-only handlers
|
|
# ============================================================================
|
|
|
|
# Handlers below are pure slow-path delegations: no fast path is worthwhile
|
|
# because the operation is inherently complex (object allocation, prototype
|
|
# chain walks, etc). Having them here avoids the generic fallback handler's
|
|
# overhead of saving/restoring all temporaries.
|
|
|
|
handler GetObjectPropertyIterator
|
|
call_slow_path asm_slow_path_get_object_property_iterator
|
|
end
|
|
|
|
handler ObjectPropertyIteratorNext
|
|
load_operand t1, m_iterator_object
|
|
extract_tag t2, t1
|
|
branch_ne t2, OBJECT_TAG, .slow
|
|
unbox_object t3, t1
|
|
|
|
load8 t4, [t3, PROPERTY_NAME_ITERATOR_FAST_PATH]
|
|
mov t0, OBJECT_PROPERTY_ITERATOR_FAST_PATH_NONE
|
|
branch_eq t4, t0, .slow
|
|
|
|
# These guards mirror PropertyNameIterator::fast_path_still_valid(). If the
|
|
# receiver or prototype chain no longer matches the cached snapshot, we drop
|
|
# to C++ and continue in deoptimized mode for the rest of the enumeration.
|
|
load_pair64 t5, t7, [t3, PROPERTY_NAME_ITERATOR_PROPERTY_CACHE], [t3, PROPERTY_NAME_ITERATOR_SHAPE]
|
|
load64 t6, [t3, PROPERTY_NAME_ITERATOR_OBJECT]
|
|
load64 t8, [t6, OBJECT_SHAPE]
|
|
branch_ne t8, t7, .slow
|
|
|
|
load8 t2, [t3, PROPERTY_NAME_ITERATOR_SHAPE_IS_DICTIONARY]
|
|
branch_zero t2, .check_receiver
|
|
load32 t0, [t8, SHAPE_DICTIONARY_GENERATION]
|
|
load32 t2, [t3, PROPERTY_NAME_ITERATOR_SHAPE_DICTIONARY_GENERATION]
|
|
branch_ne t0, t2, .slow
|
|
|
|
.check_receiver:
|
|
mov t0, OBJECT_PROPERTY_ITERATOR_FAST_PATH_PACKED_INDEXED
|
|
branch_ne t4, t0, .check_proto
|
|
load8 t0, [t6, OBJECT_INDEXED_STORAGE_KIND]
|
|
mov t2, INDEXED_STORAGE_KIND_PACKED
|
|
branch_ne t0, t2, .slow
|
|
load32 t0, [t6, OBJECT_INDEXED_ARRAY_LIKE_SIZE]
|
|
load32 t2, [t3, PROPERTY_NAME_ITERATOR_INDEXED_PROPERTY_COUNT]
|
|
branch_ne t0, t2, .slow
|
|
|
|
.check_proto:
|
|
load64 t0, [t3, PROPERTY_NAME_ITERATOR_PROTOTYPE_CHAIN_VALIDITY]
|
|
branch_zero t0, .next_key
|
|
load8 t2, [t0, PROTOTYPE_CHAIN_VALIDITY_VALID]
|
|
branch_zero t2, .slow
|
|
|
|
.next_key:
|
|
# property_values is laid out as:
|
|
# [receiver packed index keys..., flattened named keys...]
|
|
load_pair32 t2, t0, [t3, PROPERTY_NAME_ITERATOR_INDEXED_PROPERTY_COUNT], [t3, PROPERTY_NAME_ITERATOR_NEXT_INDEXED_PROPERTY]
|
|
branch_ge_unsigned t0, t2, .named
|
|
load64 t8, [t5, OBJECT_PROPERTY_ITERATOR_CACHE_DATA_PROPERTY_VALUES_DATA]
|
|
load64 t8, [t8, t0, 8]
|
|
add t0, 1
|
|
store32 [t3, PROPERTY_NAME_ITERATOR_NEXT_INDEXED_PROPERTY], t0
|
|
store_operand m_dst_value, t8
|
|
mov t0, BOOLEAN_FALSE
|
|
store_operand m_dst_done, t0
|
|
dispatch_next
|
|
|
|
.named:
|
|
load64 t0, [t3, PROPERTY_NAME_ITERATOR_NEXT_PROPERTY]
|
|
load64 t8, [t5, OBJECT_PROPERTY_ITERATOR_CACHE_DATA_PROPERTY_VALUES_SIZE]
|
|
sub t8, t2
|
|
branch_ge_unsigned t0, t8, .done
|
|
mov t8, t0
|
|
add t8, t2
|
|
load64 t5, [t5, OBJECT_PROPERTY_ITERATOR_CACHE_DATA_PROPERTY_VALUES_DATA]
|
|
load64 t8, [t5, t8, 8]
|
|
add t0, 1
|
|
store64 [t3, PROPERTY_NAME_ITERATOR_NEXT_PROPERTY], t0
|
|
store_operand m_dst_value, t8
|
|
mov t0, BOOLEAN_FALSE
|
|
store_operand m_dst_done, t0
|
|
dispatch_next
|
|
|
|
.done:
|
|
load64 t5, [t3, PROPERTY_NAME_ITERATOR_ITERATOR_CACHE_SLOT]
|
|
branch_zero t5, .store_done
|
|
# Return the exhausted iterator object to the bytecode-site cache so the
|
|
# next execution of this loop can reset and reuse it.
|
|
mov t0, 0
|
|
store64 [t3, PROPERTY_NAME_ITERATOR_OBJECT], t0
|
|
store64 [t5, OBJECT_PROPERTY_ITERATOR_CACHE_REUSABLE_PROPERTY_NAME_ITERATOR], t3
|
|
store64 [t3, PROPERTY_NAME_ITERATOR_ITERATOR_CACHE_SLOT], t0
|
|
|
|
.store_done:
|
|
mov t0, BOOLEAN_TRUE
|
|
store_operand m_dst_done, t0
|
|
dispatch_next
|
|
|
|
.slow:
|
|
call_slow_path asm_slow_path_object_property_iterator_next
|
|
end
|
|
|
|
handler CallConstruct
|
|
call_slow_path asm_slow_path_call_construct
|
|
end
|
|
|
|
handler NewObject
|
|
call_slow_path asm_slow_path_new_object
|
|
end
|
|
|
|
handler CacheObjectShape
|
|
call_slow_path asm_slow_path_cache_object_shape
|
|
end
|
|
|
|
handler InitObjectLiteralProperty
|
|
call_slow_path asm_slow_path_init_object_literal_property
|
|
end
|
|
|
|
handler NewArray
|
|
call_slow_path asm_slow_path_new_array
|
|
end
|
|
|
|
handler InstanceOf
|
|
call_slow_path asm_slow_path_instance_of
|
|
end
|
|
|
|
# Fast path: if this_value register is already cached (non-empty), skip the slow path.
|
|
handler ResolveThisBinding
|
|
load64 t0, [values, THIS_VALUE_REG_OFFSET]
|
|
mov t1, EMPTY_VALUE
|
|
branch_eq t0, t1, .slow
|
|
dispatch_next
|
|
.slow:
|
|
call_slow_path asm_slow_path_resolve_this_binding
|
|
end
|
|
|
|
handler GetPrivateById
|
|
call_slow_path asm_slow_path_get_private_by_id
|
|
end
|
|
|
|
handler PutPrivateById
|
|
call_slow_path asm_slow_path_put_private_by_id
|
|
end
|