| // Copyright 2017 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/interpreter/interpreter-generator.h" |
| |
| #include <array> |
| #include <tuple> |
| |
| #include "src/builtins/builtins-arguments-gen.h" |
| #include "src/builtins/builtins-constructor-gen.h" |
| #include "src/builtins/builtins-iterator-gen.h" |
| #include "src/codegen/code-factory.h" |
| #include "src/debug/debug.h" |
| #include "src/ic/accessor-assembler.h" |
| #include "src/ic/binary-op-assembler.h" |
| #include "src/ic/ic.h" |
| #include "src/interpreter/bytecode-flags.h" |
| #include "src/interpreter/bytecodes.h" |
| #include "src/interpreter/interpreter-assembler.h" |
| #include "src/interpreter/interpreter-intrinsics-generator.h" |
| #include "src/objects/cell.h" |
| #include "src/objects/js-generator.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/oddball.h" |
| #include "src/objects/source-text-module.h" |
| #include "src/utils/ostreams.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace interpreter { |
| |
| namespace { |
| |
| using compiler::Node; |
| using Label = CodeStubAssembler::Label; |
| using Variable = CodeStubAssembler::Variable; |
| |
| #define IGNITION_HANDLER(Name, BaseAssembler) \ |
| class Name##Assembler : public BaseAssembler { \ |
| public: \ |
| explicit Name##Assembler(compiler::CodeAssemblerState* state, \ |
| Bytecode bytecode, OperandScale scale) \ |
| : BaseAssembler(state, bytecode, scale) {} \ |
| static void Generate(compiler::CodeAssemblerState* state, \ |
| OperandScale scale); \ |
| \ |
| private: \ |
| void GenerateImpl(); \ |
| DISALLOW_COPY_AND_ASSIGN(Name##Assembler); \ |
| }; \ |
| void Name##Assembler::Generate(compiler::CodeAssemblerState* state, \ |
| OperandScale scale) { \ |
| Name##Assembler assembler(state, Bytecode::k##Name, scale); \ |
| state->SetInitialDebugInformation(#Name, __FILE__, __LINE__); \ |
| assembler.GenerateImpl(); \ |
| } \ |
| void Name##Assembler::GenerateImpl() |
| |
| // LdaZero |
| // |
| // Load literal '0' into the accumulator. |
| IGNITION_HANDLER(LdaZero, InterpreterAssembler) { |
| Node* zero_value = NumberConstant(0.0); |
| SetAccumulator(zero_value); |
| Dispatch(); |
| } |
| |
| // LdaSmi <imm> |
| // |
| // Load an integer literal into the accumulator as a Smi. |
| IGNITION_HANDLER(LdaSmi, InterpreterAssembler) { |
| Node* smi_int = BytecodeOperandImmSmi(0); |
| SetAccumulator(smi_int); |
| Dispatch(); |
| } |
| |
| // LdaConstant <idx> |
| // |
| // Load constant literal at |idx| in the constant pool into the accumulator. |
| IGNITION_HANDLER(LdaConstant, InterpreterAssembler) { |
| Node* constant = LoadConstantPoolEntryAtOperandIndex(0); |
| SetAccumulator(constant); |
| Dispatch(); |
| } |
| |
| // LdaUndefined |
| // |
| // Load Undefined into the accumulator. |
| IGNITION_HANDLER(LdaUndefined, InterpreterAssembler) { |
| SetAccumulator(UndefinedConstant()); |
| Dispatch(); |
| } |
| |
| // LdaNull |
| // |
| // Load Null into the accumulator. |
| IGNITION_HANDLER(LdaNull, InterpreterAssembler) { |
| SetAccumulator(NullConstant()); |
| Dispatch(); |
| } |
| |
| // LdaTheHole |
| // |
| // Load TheHole into the accumulator. |
| IGNITION_HANDLER(LdaTheHole, InterpreterAssembler) { |
| SetAccumulator(TheHoleConstant()); |
| Dispatch(); |
| } |
| |
| // LdaTrue |
| // |
| // Load True into the accumulator. |
| IGNITION_HANDLER(LdaTrue, InterpreterAssembler) { |
| SetAccumulator(TrueConstant()); |
| Dispatch(); |
| } |
| |
| // LdaFalse |
| // |
| // Load False into the accumulator. |
| IGNITION_HANDLER(LdaFalse, InterpreterAssembler) { |
| SetAccumulator(FalseConstant()); |
| Dispatch(); |
| } |
| |
| // Ldar <src> |
| // |
| // Load accumulator with value from register <src>. |
| IGNITION_HANDLER(Ldar, InterpreterAssembler) { |
| Node* value = LoadRegisterAtOperandIndex(0); |
| SetAccumulator(value); |
| Dispatch(); |
| } |
| |
| // Star <dst> |
| // |
| // Store accumulator to register <dst>. |
| IGNITION_HANDLER(Star, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| StoreRegisterAtOperandIndex(accumulator, 0); |
| Dispatch(); |
| } |
| |
| // Mov <src> <dst> |
| // |
| // Stores the value of register <src> to register <dst>. |
| IGNITION_HANDLER(Mov, InterpreterAssembler) { |
| Node* src_value = LoadRegisterAtOperandIndex(0); |
| StoreRegisterAtOperandIndex(src_value, 1); |
| Dispatch(); |
| } |
| |
| class InterpreterLoadGlobalAssembler : public InterpreterAssembler { |
| public: |
| InterpreterLoadGlobalAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| void LdaGlobal(int slot_operand_index, int name_operand_index, |
| TypeofMode typeof_mode) { |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* feedback_slot = BytecodeOperandIdx(slot_operand_index); |
| |
| AccessorAssembler accessor_asm(state()); |
| ExitPoint exit_point(this, [=](Node* result) { |
| SetAccumulator(result); |
| Dispatch(); |
| }); |
| |
| LazyNode<Context> lazy_context = [=] { return CAST(GetContext()); }; |
| |
| LazyNode<Name> lazy_name = [=] { |
| Node* name = LoadConstantPoolEntryAtOperandIndex(name_operand_index); |
| return CAST(name); |
| }; |
| |
| ParameterMode slot_mode = CodeStubAssembler::INTPTR_PARAMETERS; |
| accessor_asm.LoadGlobalIC(maybe_feedback_vector, feedback_slot, |
| lazy_context, lazy_name, typeof_mode, &exit_point, |
| slot_mode); |
| } |
| }; |
| |
| // LdaGlobal <name_index> <slot> |
| // |
| // Load the global with name in constant pool entry <name_index> into the |
| // accumulator using FeedBackVector slot <slot> outside of a typeof. |
| IGNITION_HANDLER(LdaGlobal, InterpreterLoadGlobalAssembler) { |
| static const int kNameOperandIndex = 0; |
| static const int kSlotOperandIndex = 1; |
| |
| LdaGlobal(kSlotOperandIndex, kNameOperandIndex, NOT_INSIDE_TYPEOF); |
| } |
| |
| // LdaGlobalInsideTypeof <name_index> <slot> |
| // |
| // Load the global with name in constant pool entry <name_index> into the |
| // accumulator using FeedBackVector slot <slot> inside of a typeof. |
| IGNITION_HANDLER(LdaGlobalInsideTypeof, InterpreterLoadGlobalAssembler) { |
| static const int kNameOperandIndex = 0; |
| static const int kSlotOperandIndex = 1; |
| |
| LdaGlobal(kSlotOperandIndex, kNameOperandIndex, INSIDE_TYPEOF); |
| } |
| |
| // StaGlobal <name_index> <slot> |
| // |
| // Store the value in the accumulator into the global with name in constant pool |
| // entry <name_index> using FeedBackVector slot <slot>. |
| IGNITION_HANDLER(StaGlobal, InterpreterAssembler) { |
| Node* context = GetContext(); |
| |
| // Store the global via the StoreGlobalIC. |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* value = GetAccumulator(); |
| Node* raw_slot = BytecodeOperandIdx(1); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* maybe_vector = LoadFeedbackVector(); |
| |
| Label no_feedback(this, Label::kDeferred), end(this); |
| GotoIf(IsUndefined(maybe_vector), &no_feedback); |
| |
| CallBuiltin(Builtins::kStoreGlobalIC, context, name, value, smi_slot, |
| maybe_vector); |
| Goto(&end); |
| |
| Bind(&no_feedback); |
| CallRuntime(Runtime::kStoreGlobalICNoFeedback_Miss, context, value, name); |
| Goto(&end); |
| |
| Bind(&end); |
| Dispatch(); |
| } |
| |
| // LdaContextSlot <context> <slot_index> <depth> |
| // |
| // Load the object in |slot_index| of the context at |depth| in the context |
| // chain starting at |context| into the accumulator. |
| IGNITION_HANDLER(LdaContextSlot, InterpreterAssembler) { |
| Node* context = LoadRegisterAtOperandIndex(0); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* depth = BytecodeOperandUImm(2); |
| Node* slot_context = GetContextAtDepth(context, depth); |
| Node* result = LoadContextElement(slot_context, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // LdaImmutableContextSlot <context> <slot_index> <depth> |
| // |
| // Load the object in |slot_index| of the context at |depth| in the context |
| // chain starting at |context| into the accumulator. |
| IGNITION_HANDLER(LdaImmutableContextSlot, InterpreterAssembler) { |
| Node* context = LoadRegisterAtOperandIndex(0); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* depth = BytecodeOperandUImm(2); |
| Node* slot_context = GetContextAtDepth(context, depth); |
| Node* result = LoadContextElement(slot_context, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // LdaCurrentContextSlot <slot_index> |
| // |
| // Load the object in |slot_index| of the current context into the accumulator. |
| IGNITION_HANDLER(LdaCurrentContextSlot, InterpreterAssembler) { |
| Node* slot_index = BytecodeOperandIdx(0); |
| Node* slot_context = GetContext(); |
| Node* result = LoadContextElement(slot_context, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // LdaImmutableCurrentContextSlot <slot_index> |
| // |
| // Load the object in |slot_index| of the current context into the accumulator. |
| IGNITION_HANDLER(LdaImmutableCurrentContextSlot, InterpreterAssembler) { |
| Node* slot_index = BytecodeOperandIdx(0); |
| Node* slot_context = GetContext(); |
| Node* result = LoadContextElement(slot_context, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // StaContextSlot <context> <slot_index> <depth> |
| // |
| // Stores the object in the accumulator into |slot_index| of the context at |
| // |depth| in the context chain starting at |context|. |
| IGNITION_HANDLER(StaContextSlot, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* context = LoadRegisterAtOperandIndex(0); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* depth = BytecodeOperandUImm(2); |
| Node* slot_context = GetContextAtDepth(context, depth); |
| StoreContextElement(slot_context, slot_index, value); |
| Dispatch(); |
| } |
| |
| // StaCurrentContextSlot <slot_index> |
| // |
| // Stores the object in the accumulator into |slot_index| of the current |
| // context. |
| IGNITION_HANDLER(StaCurrentContextSlot, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* slot_index = BytecodeOperandIdx(0); |
| Node* slot_context = GetContext(); |
| StoreContextElement(slot_context, slot_index, value); |
| Dispatch(); |
| } |
| |
| // LdaLookupSlot <name_index> |
| // |
| // Lookup the object with the name in constant pool entry |name_index| |
| // dynamically. |
| IGNITION_HANDLER(LdaLookupSlot, InterpreterAssembler) { |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* context = GetContext(); |
| Node* result = CallRuntime(Runtime::kLoadLookupSlot, context, name); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // LdaLookupSlotInsideTypeof <name_index> |
| // |
| // Lookup the object with the name in constant pool entry |name_index| |
| // dynamically without causing a NoReferenceError. |
| IGNITION_HANDLER(LdaLookupSlotInsideTypeof, InterpreterAssembler) { |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* context = GetContext(); |
| Node* result = |
| CallRuntime(Runtime::kLoadLookupSlotInsideTypeof, context, name); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| class InterpreterLookupContextSlotAssembler : public InterpreterAssembler { |
| public: |
| InterpreterLookupContextSlotAssembler(CodeAssemblerState* state, |
| Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| void LookupContextSlot(Runtime::FunctionId function_id) { |
| Node* context = GetContext(); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* depth = BytecodeOperandUImm(2); |
| |
| Label slowpath(this, Label::kDeferred); |
| |
| // Check for context extensions to allow the fast path. |
| GotoIfHasContextExtensionUpToDepth(context, depth, &slowpath); |
| |
| // Fast path does a normal load context. |
| { |
| Node* slot_context = GetContextAtDepth(context, depth); |
| Node* result = LoadContextElement(slot_context, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // Slow path when we have to call out to the runtime. |
| BIND(&slowpath); |
| { |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* result = CallRuntime(function_id, context, name); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| }; |
| |
| // LdaLookupContextSlot <name_index> |
| // |
| // Lookup the object with the name in constant pool entry |name_index| |
| // dynamically. |
| IGNITION_HANDLER(LdaLookupContextSlot, InterpreterLookupContextSlotAssembler) { |
| LookupContextSlot(Runtime::kLoadLookupSlot); |
| } |
| |
| // LdaLookupContextSlotInsideTypeof <name_index> |
| // |
| // Lookup the object with the name in constant pool entry |name_index| |
| // dynamically without causing a NoReferenceError. |
| IGNITION_HANDLER(LdaLookupContextSlotInsideTypeof, |
| InterpreterLookupContextSlotAssembler) { |
| LookupContextSlot(Runtime::kLoadLookupSlotInsideTypeof); |
| } |
| |
| class InterpreterLookupGlobalAssembler : public InterpreterLoadGlobalAssembler { |
| public: |
| InterpreterLookupGlobalAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterLoadGlobalAssembler(state, bytecode, operand_scale) {} |
| |
| void LookupGlobalSlot(Runtime::FunctionId function_id) { |
| Node* context = GetContext(); |
| Node* depth = BytecodeOperandUImm(2); |
| |
| Label slowpath(this, Label::kDeferred); |
| |
| // Check for context extensions to allow the fast path |
| GotoIfHasContextExtensionUpToDepth(context, depth, &slowpath); |
| |
| // Fast path does a normal load global |
| { |
| static const int kNameOperandIndex = 0; |
| static const int kSlotOperandIndex = 1; |
| |
| TypeofMode typeof_mode = |
| function_id == Runtime::kLoadLookupSlotInsideTypeof |
| ? INSIDE_TYPEOF |
| : NOT_INSIDE_TYPEOF; |
| |
| LdaGlobal(kSlotOperandIndex, kNameOperandIndex, typeof_mode); |
| } |
| |
| // Slow path when we have to call out to the runtime |
| BIND(&slowpath); |
| { |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* result = CallRuntime(function_id, context, name); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| }; |
| |
| // LdaLookupGlobalSlot <name_index> <feedback_slot> <depth> |
| // |
| // Lookup the object with the name in constant pool entry |name_index| |
| // dynamically. |
| IGNITION_HANDLER(LdaLookupGlobalSlot, InterpreterLookupGlobalAssembler) { |
| LookupGlobalSlot(Runtime::kLoadLookupSlot); |
| } |
| |
| // LdaLookupGlobalSlotInsideTypeof <name_index> <feedback_slot> <depth> |
| // |
| // Lookup the object with the name in constant pool entry |name_index| |
| // dynamically without causing a NoReferenceError. |
| IGNITION_HANDLER(LdaLookupGlobalSlotInsideTypeof, |
| InterpreterLookupGlobalAssembler) { |
| LookupGlobalSlot(Runtime::kLoadLookupSlotInsideTypeof); |
| } |
| |
| // StaLookupSlot <name_index> <flags> |
| // |
| // Store the object in accumulator to the object with the name in constant |
| // pool entry |name_index|. |
| IGNITION_HANDLER(StaLookupSlot, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* bytecode_flags = BytecodeOperandFlag(1); |
| Node* context = GetContext(); |
| Variable var_result(this, MachineRepresentation::kTagged); |
| |
| Label sloppy(this), strict(this), end(this); |
| DCHECK_EQ(0, LanguageMode::kSloppy); |
| DCHECK_EQ(1, LanguageMode::kStrict); |
| DCHECK_EQ(0, static_cast<int>(LookupHoistingMode::kNormal)); |
| DCHECK_EQ(1, static_cast<int>(LookupHoistingMode::kLegacySloppy)); |
| Branch(IsSetWord32<StoreLookupSlotFlags::LanguageModeBit>(bytecode_flags), |
| &strict, &sloppy); |
| |
| BIND(&strict); |
| { |
| CSA_ASSERT(this, IsClearWord32<StoreLookupSlotFlags::LookupHoistingModeBit>( |
| bytecode_flags)); |
| var_result.Bind( |
| CallRuntime(Runtime::kStoreLookupSlot_Strict, context, name, value)); |
| Goto(&end); |
| } |
| |
| BIND(&sloppy); |
| { |
| Label hoisting(this), ordinary(this); |
| Branch(IsSetWord32<StoreLookupSlotFlags::LookupHoistingModeBit>( |
| bytecode_flags), |
| &hoisting, &ordinary); |
| |
| BIND(&hoisting); |
| { |
| var_result.Bind(CallRuntime(Runtime::kStoreLookupSlot_SloppyHoisting, |
| context, name, value)); |
| Goto(&end); |
| } |
| |
| BIND(&ordinary); |
| { |
| var_result.Bind( |
| CallRuntime(Runtime::kStoreLookupSlot_Sloppy, context, name, value)); |
| Goto(&end); |
| } |
| } |
| |
| BIND(&end); |
| { |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| } |
| |
| // LdaNamedProperty <object> <name_index> <slot> |
| // |
| // Calls the LoadIC at FeedBackVector slot <slot> for <object> and the name at |
| // constant pool entry <name_index>. |
| IGNITION_HANDLER(LdaNamedProperty, InterpreterAssembler) { |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* feedback_slot = BytecodeOperandIdx(2); |
| Node* smi_slot = SmiTag(feedback_slot); |
| |
| // Load receiver. |
| Node* recv = LoadRegisterAtOperandIndex(0); |
| |
| // Load the name and context lazily. |
| LazyNode<Name> name = [=] { |
| return CAST(LoadConstantPoolEntryAtOperandIndex(1)); |
| }; |
| LazyNode<Context> context = [=] { return CAST(GetContext()); }; |
| |
| Label done(this); |
| Variable var_result(this, MachineRepresentation::kTagged); |
| ExitPoint exit_point(this, &done, &var_result); |
| |
| AccessorAssembler::LazyLoadICParameters params(context, recv, name, smi_slot, |
| feedback_vector); |
| AccessorAssembler accessor_asm(state()); |
| accessor_asm.LoadIC_BytecodeHandler(¶ms, &exit_point); |
| |
| BIND(&done); |
| { |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| } |
| |
| // LdaPropertyNofeedback <object> <slot> |
| // |
| // Calls the GetProperty builtin for <object> and the key in the accumulator. |
| IGNITION_HANDLER(LdaNamedPropertyNoFeedback, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* name = LoadConstantPoolEntryAtOperandIndex(1); |
| Node* context = GetContext(); |
| Node* result = CallBuiltin(Builtins::kGetProperty, context, object, name); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // KeyedLoadIC <object> <slot> |
| // |
| // Calls the KeyedLoadIC at FeedBackVector slot <slot> for <object> and the key |
| // in the accumulator. |
| IGNITION_HANDLER(LdaKeyedProperty, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* name = GetAccumulator(); |
| Node* raw_slot = BytecodeOperandIdx(1); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| VARIABLE(var_result, MachineRepresentation::kTagged); |
| var_result.Bind(CallBuiltin(Builtins::kKeyedLoadIC, context, object, name, |
| smi_slot, feedback_vector)); |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| |
| class InterpreterStoreNamedPropertyAssembler : public InterpreterAssembler { |
| public: |
| InterpreterStoreNamedPropertyAssembler(CodeAssemblerState* state, |
| Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| void StaNamedProperty(Callable ic, NamedPropertyType property_type) { |
| Node* code_target = HeapConstant(ic.code()); |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* name = LoadConstantPoolEntryAtOperandIndex(1); |
| Node* value = GetAccumulator(); |
| Node* raw_slot = BytecodeOperandIdx(2); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* maybe_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| VARIABLE(var_result, MachineRepresentation::kTagged); |
| var_result.Bind(CallStub(ic.descriptor(), code_target, context, object, |
| name, value, smi_slot, maybe_vector)); |
| // To avoid special logic in the deoptimizer to re-materialize the value in |
| // the accumulator, we overwrite the accumulator after the IC call. It |
| // doesn't really matter what we write to the accumulator here, since we |
| // restore to the correct value on the outside. Storing the result means we |
| // don't need to keep unnecessary state alive across the callstub. |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| }; |
| |
| // StaNamedProperty <object> <name_index> <slot> |
| // |
| // Calls the StoreIC at FeedBackVector slot <slot> for <object> and |
| // the name in constant pool entry <name_index> with the value in the |
| // accumulator. |
| IGNITION_HANDLER(StaNamedProperty, InterpreterStoreNamedPropertyAssembler) { |
| Callable ic = Builtins::CallableFor(isolate(), Builtins::kStoreIC); |
| StaNamedProperty(ic, NamedPropertyType::kNotOwn); |
| } |
| |
| // StaNamedOwnProperty <object> <name_index> <slot> |
| // |
| // Calls the StoreOwnIC at FeedBackVector slot <slot> for <object> and |
| // the name in constant pool entry <name_index> with the value in the |
| // accumulator. |
| IGNITION_HANDLER(StaNamedOwnProperty, InterpreterStoreNamedPropertyAssembler) { |
| Callable ic = CodeFactory::StoreOwnICInOptimizedCode(isolate()); |
| StaNamedProperty(ic, NamedPropertyType::kOwn); |
| } |
| |
| // StaNamedPropertyNoFeedback <object> <name_index> |
| // |
| // Calls the SetPropertyBuiltin for <object> and the name in constant pool entry |
| // <name_index> with the value in the accumulator. |
| IGNITION_HANDLER(StaNamedPropertyNoFeedback, |
| InterpreterStoreNamedPropertyAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* name = LoadConstantPoolEntryAtOperandIndex(1); |
| Node* value = GetAccumulator(); |
| Node* context = GetContext(); |
| |
| Node* result = |
| CallRuntime(Runtime::kSetNamedProperty, context, object, name, value); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // StaKeyedProperty <object> <key> <slot> |
| // |
| // Calls the KeyedStoreIC at FeedbackVector slot <slot> for <object> and |
| // the key <key> with the value in the accumulator. |
| IGNITION_HANDLER(StaKeyedProperty, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* name = LoadRegisterAtOperandIndex(1); |
| Node* value = GetAccumulator(); |
| Node* raw_slot = BytecodeOperandIdx(2); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* maybe_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| VARIABLE(var_result, MachineRepresentation::kTagged); |
| var_result.Bind(CallBuiltin(Builtins::kKeyedStoreIC, context, object, name, |
| value, smi_slot, maybe_vector)); |
| // To avoid special logic in the deoptimizer to re-materialize the value in |
| // the accumulator, we overwrite the accumulator after the IC call. It |
| // doesn't really matter what we write to the accumulator here, since we |
| // restore to the correct value on the outside. Storing the result means we |
| // don't need to keep unnecessary state alive across the callstub. |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| |
| // StaInArrayLiteral <array> <index> <slot> |
| // |
| // Calls the StoreInArrayLiteralIC at FeedbackVector slot <slot> for <array> and |
| // the key <index> with the value in the accumulator. |
| IGNITION_HANDLER(StaInArrayLiteral, InterpreterAssembler) { |
| Node* array = LoadRegisterAtOperandIndex(0); |
| Node* index = LoadRegisterAtOperandIndex(1); |
| Node* value = GetAccumulator(); |
| Node* raw_slot = BytecodeOperandIdx(2); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| VARIABLE(var_result, MachineRepresentation::kTagged); |
| var_result.Bind(CallBuiltin(Builtins::kStoreInArrayLiteralIC, context, array, |
| index, value, smi_slot, feedback_vector)); |
| // To avoid special logic in the deoptimizer to re-materialize the value in |
| // the accumulator, we overwrite the accumulator after the IC call. It |
| // doesn't really matter what we write to the accumulator here, since we |
| // restore to the correct value on the outside. Storing the result means we |
| // don't need to keep unnecessary state alive across the callstub. |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| |
| // StaDataPropertyInLiteral <object> <name> <flags> |
| // |
| // Define a property <name> with value from the accumulator in <object>. |
| // Property attributes and whether set_function_name are stored in |
| // DataPropertyInLiteralFlags <flags>. |
| // |
| // This definition is not observable and is used only for definitions |
| // in object or class literals. |
| IGNITION_HANDLER(StaDataPropertyInLiteral, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* name = LoadRegisterAtOperandIndex(1); |
| Node* value = GetAccumulator(); |
| Node* flags = SmiFromInt32(BytecodeOperandFlag(2)); |
| Node* vector_index = SmiTag(BytecodeOperandIdx(3)); |
| |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| CallRuntime(Runtime::kDefineDataPropertyInLiteral, context, object, name, |
| value, flags, feedback_vector, vector_index); |
| Dispatch(); |
| } |
| |
| IGNITION_HANDLER(CollectTypeProfile, InterpreterAssembler) { |
| Node* position = BytecodeOperandImmSmi(0); |
| Node* value = GetAccumulator(); |
| |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| CallRuntime(Runtime::kCollectTypeProfile, context, position, value, |
| feedback_vector); |
| Dispatch(); |
| } |
| |
| // LdaModuleVariable <cell_index> <depth> |
| // |
| // Load the contents of a module variable into the accumulator. The variable is |
| // identified by <cell_index>. <depth> is the depth of the current context |
| // relative to the module context. |
| IGNITION_HANDLER(LdaModuleVariable, InterpreterAssembler) { |
| Node* cell_index = BytecodeOperandImmIntPtr(0); |
| Node* depth = BytecodeOperandUImm(1); |
| |
| Node* module_context = GetContextAtDepth(GetContext(), depth); |
| Node* module = LoadContextElement(module_context, Context::EXTENSION_INDEX); |
| |
| Label if_export(this), if_import(this), end(this); |
| Branch(IntPtrGreaterThan(cell_index, IntPtrConstant(0)), &if_export, |
| &if_import); |
| |
| BIND(&if_export); |
| { |
| TNode<FixedArray> regular_exports = |
| CAST(LoadObjectField(module, SourceTextModule::kRegularExportsOffset)); |
| // The actual array index is (cell_index - 1). |
| Node* export_index = IntPtrSub(cell_index, IntPtrConstant(1)); |
| Node* cell = LoadFixedArrayElement(regular_exports, export_index); |
| SetAccumulator(LoadObjectField(cell, Cell::kValueOffset)); |
| Goto(&end); |
| } |
| |
| BIND(&if_import); |
| { |
| TNode<FixedArray> regular_imports = |
| CAST(LoadObjectField(module, SourceTextModule::kRegularImportsOffset)); |
| // The actual array index is (-cell_index - 1). |
| Node* import_index = IntPtrSub(IntPtrConstant(-1), cell_index); |
| Node* cell = LoadFixedArrayElement(regular_imports, import_index); |
| SetAccumulator(LoadObjectField(cell, Cell::kValueOffset)); |
| Goto(&end); |
| } |
| |
| BIND(&end); |
| Dispatch(); |
| } |
| |
| // StaModuleVariable <cell_index> <depth> |
| // |
| // Store accumulator to the module variable identified by <cell_index>. |
| // <depth> is the depth of the current context relative to the module context. |
| IGNITION_HANDLER(StaModuleVariable, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* cell_index = BytecodeOperandImmIntPtr(0); |
| Node* depth = BytecodeOperandUImm(1); |
| |
| Node* module_context = GetContextAtDepth(GetContext(), depth); |
| Node* module = LoadContextElement(module_context, Context::EXTENSION_INDEX); |
| |
| Label if_export(this), if_import(this), end(this); |
| Branch(IntPtrGreaterThan(cell_index, IntPtrConstant(0)), &if_export, |
| &if_import); |
| |
| BIND(&if_export); |
| { |
| TNode<FixedArray> regular_exports = |
| CAST(LoadObjectField(module, SourceTextModule::kRegularExportsOffset)); |
| // The actual array index is (cell_index - 1). |
| Node* export_index = IntPtrSub(cell_index, IntPtrConstant(1)); |
| Node* cell = LoadFixedArrayElement(regular_exports, export_index); |
| StoreObjectField(cell, Cell::kValueOffset, value); |
| Goto(&end); |
| } |
| |
| BIND(&if_import); |
| { |
| // Not supported (probably never). |
| Abort(AbortReason::kUnsupportedModuleOperation); |
| Goto(&end); |
| } |
| |
| BIND(&end); |
| Dispatch(); |
| } |
| |
| // PushContext <context> |
| // |
| // Saves the current context in <context>, and pushes the accumulator as the |
| // new current context. |
| IGNITION_HANDLER(PushContext, InterpreterAssembler) { |
| Node* new_context = GetAccumulator(); |
| Node* old_context = GetContext(); |
| StoreRegisterAtOperandIndex(old_context, 0); |
| SetContext(new_context); |
| Dispatch(); |
| } |
| |
| // PopContext <context> |
| // |
| // Pops the current context and sets <context> as the new context. |
| IGNITION_HANDLER(PopContext, InterpreterAssembler) { |
| Node* context = LoadRegisterAtOperandIndex(0); |
| SetContext(context); |
| Dispatch(); |
| } |
| |
| class InterpreterBinaryOpAssembler : public InterpreterAssembler { |
| public: |
| InterpreterBinaryOpAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| using BinaryOpGenerator = |
| Node* (BinaryOpAssembler::*)(Node* context, Node* left, Node* right, |
| Node* slot, Node* vector, bool lhs_is_smi); |
| |
| void BinaryOpWithFeedback(BinaryOpGenerator generator) { |
| Node* lhs = LoadRegisterAtOperandIndex(0); |
| Node* rhs = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| |
| BinaryOpAssembler binop_asm(state()); |
| Node* result = (binop_asm.*generator)(context, lhs, rhs, slot_index, |
| maybe_feedback_vector, false); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| void BinaryOpSmiWithFeedback(BinaryOpGenerator generator) { |
| Node* lhs = GetAccumulator(); |
| Node* rhs = BytecodeOperandImmSmi(0); |
| Node* context = GetContext(); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| |
| BinaryOpAssembler binop_asm(state()); |
| Node* result = (binop_asm.*generator)(context, lhs, rhs, slot_index, |
| maybe_feedback_vector, true); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| }; |
| |
| // Add <src> |
| // |
| // Add register <src> to accumulator. |
| IGNITION_HANDLER(Add, InterpreterBinaryOpAssembler) { |
| BinaryOpWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback); |
| } |
| |
| // Sub <src> |
| // |
| // Subtract register <src> from accumulator. |
| IGNITION_HANDLER(Sub, InterpreterBinaryOpAssembler) { |
| BinaryOpWithFeedback(&BinaryOpAssembler::Generate_SubtractWithFeedback); |
| } |
| |
| // Mul <src> |
| // |
| // Multiply accumulator by register <src>. |
| IGNITION_HANDLER(Mul, InterpreterBinaryOpAssembler) { |
| BinaryOpWithFeedback(&BinaryOpAssembler::Generate_MultiplyWithFeedback); |
| } |
| |
| // Div <src> |
| // |
| // Divide register <src> by accumulator. |
| IGNITION_HANDLER(Div, InterpreterBinaryOpAssembler) { |
| BinaryOpWithFeedback(&BinaryOpAssembler::Generate_DivideWithFeedback); |
| } |
| |
| // Mod <src> |
| // |
| // Modulo register <src> by accumulator. |
| IGNITION_HANDLER(Mod, InterpreterBinaryOpAssembler) { |
| BinaryOpWithFeedback(&BinaryOpAssembler::Generate_ModulusWithFeedback); |
| } |
| |
| // Exp <src> |
| // |
| // Exponentiate register <src> (base) with accumulator (exponent). |
| IGNITION_HANDLER(Exp, InterpreterBinaryOpAssembler) { |
| BinaryOpWithFeedback(&BinaryOpAssembler::Generate_ExponentiateWithFeedback); |
| } |
| |
| // AddSmi <imm> |
| // |
| // Adds an immediate value <imm> to the value in the accumulator. |
| IGNITION_HANDLER(AddSmi, InterpreterBinaryOpAssembler) { |
| BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback); |
| } |
| |
| // SubSmi <imm> |
| // |
| // Subtracts an immediate value <imm> from the value in the accumulator. |
| IGNITION_HANDLER(SubSmi, InterpreterBinaryOpAssembler) { |
| BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_SubtractWithFeedback); |
| } |
| |
| // MulSmi <imm> |
| // |
| // Multiplies an immediate value <imm> to the value in the accumulator. |
| IGNITION_HANDLER(MulSmi, InterpreterBinaryOpAssembler) { |
| BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_MultiplyWithFeedback); |
| } |
| |
| // DivSmi <imm> |
| // |
| // Divides the value in the accumulator by immediate value <imm>. |
| IGNITION_HANDLER(DivSmi, InterpreterBinaryOpAssembler) { |
| BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_DivideWithFeedback); |
| } |
| |
| // ModSmi <imm> |
| // |
| // Modulo accumulator by immediate value <imm>. |
| IGNITION_HANDLER(ModSmi, InterpreterBinaryOpAssembler) { |
| BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_ModulusWithFeedback); |
| } |
| |
| // ExpSmi <imm> |
| // |
| // Exponentiate accumulator (base) with immediate value <imm> (exponent). |
| IGNITION_HANDLER(ExpSmi, InterpreterBinaryOpAssembler) { |
| BinaryOpSmiWithFeedback( |
| &BinaryOpAssembler::Generate_ExponentiateWithFeedback); |
| } |
| |
| class InterpreterBitwiseBinaryOpAssembler : public InterpreterAssembler { |
| public: |
| InterpreterBitwiseBinaryOpAssembler(CodeAssemblerState* state, |
| Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| void BitwiseBinaryOpWithFeedback(Operation bitwise_op) { |
| Node* left = LoadRegisterAtOperandIndex(0); |
| Node* right = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| |
| TVARIABLE(Smi, var_left_feedback); |
| TVARIABLE(Smi, var_right_feedback); |
| VARIABLE(var_left_word32, MachineRepresentation::kWord32); |
| VARIABLE(var_right_word32, MachineRepresentation::kWord32); |
| VARIABLE(var_left_bigint, MachineRepresentation::kTagged, left); |
| VARIABLE(var_right_bigint, MachineRepresentation::kTagged); |
| Label if_left_number(this), do_number_op(this); |
| Label if_left_bigint(this), do_bigint_op(this); |
| |
| TaggedToWord32OrBigIntWithFeedback(context, left, &if_left_number, |
| &var_left_word32, &if_left_bigint, |
| &var_left_bigint, &var_left_feedback); |
| BIND(&if_left_number); |
| TaggedToWord32OrBigIntWithFeedback(context, right, &do_number_op, |
| &var_right_word32, &do_bigint_op, |
| &var_right_bigint, &var_right_feedback); |
| BIND(&do_number_op); |
| TNode<Number> result = BitwiseOp(var_left_word32.value(), |
| var_right_word32.value(), bitwise_op); |
| TNode<Smi> result_type = SelectSmiConstant( |
| TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall, |
| BinaryOperationFeedback::kNumber); |
| TNode<Smi> input_feedback = |
| SmiOr(var_left_feedback.value(), var_right_feedback.value()); |
| UpdateFeedback(SmiOr(result_type, input_feedback), maybe_feedback_vector, |
| slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| |
| // BigInt cases. |
| BIND(&if_left_bigint); |
| TaggedToNumericWithFeedback(context, right, &do_bigint_op, |
| &var_right_bigint, &var_right_feedback); |
| |
| BIND(&do_bigint_op); |
| SetAccumulator( |
| CallRuntime(Runtime::kBigIntBinaryOp, context, var_left_bigint.value(), |
| var_right_bigint.value(), SmiConstant(bitwise_op))); |
| UpdateFeedback(SmiOr(var_left_feedback.value(), var_right_feedback.value()), |
| maybe_feedback_vector, slot_index); |
| Dispatch(); |
| } |
| |
| void BitwiseBinaryOpWithSmi(Operation bitwise_op) { |
| Node* left = GetAccumulator(); |
| Node* right = BytecodeOperandImmSmi(0); |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| TVARIABLE(Smi, var_left_feedback); |
| VARIABLE(var_left_word32, MachineRepresentation::kWord32); |
| VARIABLE(var_left_bigint, MachineRepresentation::kTagged); |
| Label do_smi_op(this), if_bigint_mix(this); |
| |
| TaggedToWord32OrBigIntWithFeedback(context, left, &do_smi_op, |
| &var_left_word32, &if_bigint_mix, |
| &var_left_bigint, &var_left_feedback); |
| BIND(&do_smi_op); |
| TNode<Number> result = |
| BitwiseOp(var_left_word32.value(), SmiToInt32(right), bitwise_op); |
| TNode<Smi> result_type = SelectSmiConstant( |
| TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall, |
| BinaryOperationFeedback::kNumber); |
| UpdateFeedback(SmiOr(result_type, var_left_feedback.value()), |
| maybe_feedback_vector, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| |
| BIND(&if_bigint_mix); |
| UpdateFeedback(var_left_feedback.value(), maybe_feedback_vector, |
| slot_index); |
| ThrowTypeError(context, MessageTemplate::kBigIntMixedTypes); |
| } |
| }; |
| |
| // BitwiseOr <src> |
| // |
| // BitwiseOr register <src> to accumulator. |
| IGNITION_HANDLER(BitwiseOr, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithFeedback(Operation::kBitwiseOr); |
| } |
| |
| // BitwiseXor <src> |
| // |
| // BitwiseXor register <src> to accumulator. |
| IGNITION_HANDLER(BitwiseXor, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithFeedback(Operation::kBitwiseXor); |
| } |
| |
| // BitwiseAnd <src> |
| // |
| // BitwiseAnd register <src> to accumulator. |
| IGNITION_HANDLER(BitwiseAnd, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithFeedback(Operation::kBitwiseAnd); |
| } |
| |
| // ShiftLeft <src> |
| // |
| // Left shifts register <src> by the count specified in the accumulator. |
| // Register <src> is converted to an int32 and the accumulator to uint32 |
| // before the operation. 5 lsb bits from the accumulator are used as count |
| // i.e. <src> << (accumulator & 0x1F). |
| IGNITION_HANDLER(ShiftLeft, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithFeedback(Operation::kShiftLeft); |
| } |
| |
| // ShiftRight <src> |
| // |
| // Right shifts register <src> by the count specified in the accumulator. |
| // Result is sign extended. Register <src> is converted to an int32 and the |
| // accumulator to uint32 before the operation. 5 lsb bits from the accumulator |
| // are used as count i.e. <src> >> (accumulator & 0x1F). |
| IGNITION_HANDLER(ShiftRight, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithFeedback(Operation::kShiftRight); |
| } |
| |
| // ShiftRightLogical <src> |
| // |
| // Right Shifts register <src> by the count specified in the accumulator. |
| // Result is zero-filled. The accumulator and register <src> are converted to |
| // uint32 before the operation 5 lsb bits from the accumulator are used as |
| // count i.e. <src> << (accumulator & 0x1F). |
| IGNITION_HANDLER(ShiftRightLogical, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithFeedback(Operation::kShiftRightLogical); |
| } |
| |
| // BitwiseOrSmi <imm> |
| // |
| // BitwiseOrSmi accumulator with <imm>. |
| IGNITION_HANDLER(BitwiseOrSmi, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithSmi(Operation::kBitwiseOr); |
| } |
| |
| // BitwiseXorSmi <imm> |
| // |
| // BitwiseXorSmi accumulator with <imm>. |
| IGNITION_HANDLER(BitwiseXorSmi, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithSmi(Operation::kBitwiseXor); |
| } |
| |
| // BitwiseAndSmi <imm> |
| // |
| // BitwiseAndSmi accumulator with <imm>. |
| IGNITION_HANDLER(BitwiseAndSmi, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithSmi(Operation::kBitwiseAnd); |
| } |
| |
| // BitwiseNot <feedback_slot> |
| // |
| // Perform bitwise-not on the accumulator. |
| IGNITION_HANDLER(BitwiseNot, InterpreterAssembler) { |
| Node* operand = GetAccumulator(); |
| Node* slot_index = BytecodeOperandIdx(0); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| VARIABLE(var_word32, MachineRepresentation::kWord32); |
| TVARIABLE(Smi, var_feedback); |
| VARIABLE(var_bigint, MachineRepresentation::kTagged); |
| Label if_number(this), if_bigint(this); |
| TaggedToWord32OrBigIntWithFeedback(context, operand, &if_number, &var_word32, |
| &if_bigint, &var_bigint, &var_feedback); |
| |
| // Number case. |
| BIND(&if_number); |
| TNode<Number> result = |
| ChangeInt32ToTagged(Signed(Word32BitwiseNot(var_word32.value()))); |
| TNode<Smi> result_type = SelectSmiConstant( |
| TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall, |
| BinaryOperationFeedback::kNumber); |
| UpdateFeedback(SmiOr(result_type, var_feedback.value()), |
| maybe_feedback_vector, slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| |
| // BigInt case. |
| BIND(&if_bigint); |
| UpdateFeedback(SmiConstant(BinaryOperationFeedback::kBigInt), |
| maybe_feedback_vector, slot_index); |
| SetAccumulator(CallRuntime(Runtime::kBigIntUnaryOp, context, |
| var_bigint.value(), |
| SmiConstant(Operation::kBitwiseNot))); |
| Dispatch(); |
| } |
| |
| // ShiftLeftSmi <imm> |
| // |
| // Left shifts accumulator by the count specified in <imm>. |
| // The accumulator is converted to an int32 before the operation. The 5 |
| // lsb bits from <imm> are used as count i.e. <src> << (<imm> & 0x1F). |
| IGNITION_HANDLER(ShiftLeftSmi, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithSmi(Operation::kShiftLeft); |
| } |
| |
| // ShiftRightSmi <imm> |
| // |
| // Right shifts accumulator by the count specified in <imm>. Result is sign |
| // extended. The accumulator is converted to an int32 before the operation. The |
| // 5 lsb bits from <imm> are used as count i.e. <src> >> (<imm> & 0x1F). |
| IGNITION_HANDLER(ShiftRightSmi, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithSmi(Operation::kShiftRight); |
| } |
| |
| // ShiftRightLogicalSmi <imm> |
| // |
| // Right shifts accumulator by the count specified in <imm>. Result is zero |
| // extended. The accumulator is converted to an int32 before the operation. The |
| // 5 lsb bits from <imm> are used as count i.e. <src> >>> (<imm> & 0x1F). |
| IGNITION_HANDLER(ShiftRightLogicalSmi, InterpreterBitwiseBinaryOpAssembler) { |
| BitwiseBinaryOpWithSmi(Operation::kShiftRightLogical); |
| } |
| |
| class UnaryNumericOpAssembler : public InterpreterAssembler { |
| public: |
| UnaryNumericOpAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| virtual ~UnaryNumericOpAssembler() = default; |
| |
| // Must return a tagged value. |
| virtual TNode<Number> SmiOp(TNode<Smi> smi_value, Variable* var_feedback, |
| Label* do_float_op, Variable* var_float) = 0; |
| // Must return a Float64 value. |
| virtual Node* FloatOp(Node* float_value) = 0; |
| // Must return a tagged value. |
| virtual Node* BigIntOp(Node* bigint_value) = 0; |
| |
| void UnaryOpWithFeedback() { |
| VARIABLE(var_value, MachineRepresentation::kTagged, GetAccumulator()); |
| VARIABLE(var_result, MachineRepresentation::kTagged); |
| VARIABLE(var_float_value, MachineRepresentation::kFloat64); |
| TVARIABLE(Smi, var_feedback, SmiConstant(BinaryOperationFeedback::kNone)); |
| Variable* loop_vars[] = {&var_value, &var_feedback}; |
| Label start(this, arraysize(loop_vars), loop_vars), end(this); |
| Label do_float_op(this, &var_float_value); |
| Goto(&start); |
| // We might have to try again after ToNumeric conversion. |
| BIND(&start); |
| { |
| Label if_smi(this), if_heapnumber(this), if_oddball(this); |
| Label if_bigint(this, Label::kDeferred); |
| Label if_other(this, Label::kDeferred); |
| Node* value = var_value.value(); |
| GotoIf(TaggedIsSmi(value), &if_smi); |
| Node* map = LoadMap(value); |
| GotoIf(IsHeapNumberMap(map), &if_heapnumber); |
| Node* instance_type = LoadMapInstanceType(map); |
| GotoIf(IsBigIntInstanceType(instance_type), &if_bigint); |
| Branch(InstanceTypeEqual(instance_type, ODDBALL_TYPE), &if_oddball, |
| &if_other); |
| |
| BIND(&if_smi); |
| { |
| var_result.Bind( |
| SmiOp(CAST(value), &var_feedback, &do_float_op, &var_float_value)); |
| Goto(&end); |
| } |
| |
| BIND(&if_heapnumber); |
| { |
| var_float_value.Bind(LoadHeapNumberValue(value)); |
| Goto(&do_float_op); |
| } |
| |
| BIND(&if_bigint); |
| { |
| var_result.Bind(BigIntOp(value)); |
| CombineFeedback(&var_feedback, BinaryOperationFeedback::kBigInt); |
| Goto(&end); |
| } |
| |
| BIND(&if_oddball); |
| { |
| // We do not require an Or with earlier feedback here because once we |
| // convert the value to a number, we cannot reach this path. We can |
| // only reach this path on the first pass when the feedback is kNone. |
| CSA_ASSERT(this, SmiEqual(var_feedback.value(), |
| SmiConstant(BinaryOperationFeedback::kNone))); |
| OverwriteFeedback(&var_feedback, |
| BinaryOperationFeedback::kNumberOrOddball); |
| var_value.Bind(LoadObjectField(value, Oddball::kToNumberOffset)); |
| Goto(&start); |
| } |
| |
| BIND(&if_other); |
| { |
| // We do not require an Or with earlier feedback here because once we |
| // convert the value to a number, we cannot reach this path. We can |
| // only reach this path on the first pass when the feedback is kNone. |
| CSA_ASSERT(this, SmiEqual(var_feedback.value(), |
| SmiConstant(BinaryOperationFeedback::kNone))); |
| OverwriteFeedback(&var_feedback, BinaryOperationFeedback::kAny); |
| var_value.Bind( |
| CallBuiltin(Builtins::kNonNumberToNumeric, GetContext(), value)); |
| Goto(&start); |
| } |
| } |
| |
| BIND(&do_float_op); |
| { |
| CombineFeedback(&var_feedback, BinaryOperationFeedback::kNumber); |
| var_result.Bind( |
| AllocateHeapNumberWithValue(FloatOp(var_float_value.value()))); |
| Goto(&end); |
| } |
| |
| BIND(&end); |
| Node* slot_index = BytecodeOperandIdx(0); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| UpdateFeedback(var_feedback.value(), maybe_feedback_vector, slot_index); |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| }; |
| |
| class NegateAssemblerImpl : public UnaryNumericOpAssembler { |
| public: |
| explicit NegateAssemblerImpl(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : UnaryNumericOpAssembler(state, bytecode, operand_scale) {} |
| |
| TNode<Number> SmiOp(TNode<Smi> smi_value, Variable* var_feedback, |
| Label* do_float_op, Variable* var_float) override { |
| TVARIABLE(Number, var_result); |
| Label if_zero(this), if_min_smi(this), end(this); |
| // Return -0 if operand is 0. |
| GotoIf(SmiEqual(smi_value, SmiConstant(0)), &if_zero); |
| |
| // Special-case the minimum Smi to avoid overflow. |
| GotoIf(SmiEqual(smi_value, SmiConstant(Smi::kMinValue)), &if_min_smi); |
| |
| // Else simply subtract operand from 0. |
| CombineFeedback(var_feedback, BinaryOperationFeedback::kSignedSmall); |
| var_result = SmiSub(SmiConstant(0), smi_value); |
| Goto(&end); |
| |
| BIND(&if_zero); |
| CombineFeedback(var_feedback, BinaryOperationFeedback::kNumber); |
| var_result = MinusZeroConstant(); |
| Goto(&end); |
| |
| BIND(&if_min_smi); |
| var_float->Bind(SmiToFloat64(smi_value)); |
| Goto(do_float_op); |
| |
| BIND(&end); |
| return var_result.value(); |
| } |
| |
| Node* FloatOp(Node* float_value) override { return Float64Neg(float_value); } |
| |
| Node* BigIntOp(Node* bigint_value) override { |
| return CallRuntime(Runtime::kBigIntUnaryOp, GetContext(), bigint_value, |
| SmiConstant(Operation::kNegate)); |
| } |
| }; |
| |
| // Negate <feedback_slot> |
| // |
| // Perform arithmetic negation on the accumulator. |
| IGNITION_HANDLER(Negate, NegateAssemblerImpl) { UnaryOpWithFeedback(); } |
| |
| // ToName <dst> |
| // |
| // Convert the object referenced by the accumulator to a name. |
| IGNITION_HANDLER(ToName, InterpreterAssembler) { |
| Node* object = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* result = CallBuiltin(Builtins::kToName, context, object); |
| StoreRegisterAtOperandIndex(result, 0); |
| Dispatch(); |
| } |
| |
| // ToNumber <slot> |
| // |
| // Convert the object referenced by the accumulator to a number. |
| IGNITION_HANDLER(ToNumber, InterpreterAssembler) { |
| ToNumberOrNumeric(Object::Conversion::kToNumber); |
| } |
| |
| // ToNumeric <slot> |
| // |
| // Convert the object referenced by the accumulator to a numeric. |
| IGNITION_HANDLER(ToNumeric, InterpreterAssembler) { |
| ToNumberOrNumeric(Object::Conversion::kToNumeric); |
| } |
| |
| // ToObject <dst> |
| // |
| // Convert the object referenced by the accumulator to a JSReceiver. |
| IGNITION_HANDLER(ToObject, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* result = CallBuiltin(Builtins::kToObject, context, accumulator); |
| StoreRegisterAtOperandIndex(result, 0); |
| Dispatch(); |
| } |
| |
| // ToString |
| // |
| // Convert the accumulator to a String. |
| IGNITION_HANDLER(ToString, InterpreterAssembler) { |
| SetAccumulator(ToString_Inline(GetContext(), GetAccumulator())); |
| Dispatch(); |
| } |
| |
| class IncDecAssembler : public UnaryNumericOpAssembler { |
| public: |
| explicit IncDecAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : UnaryNumericOpAssembler(state, bytecode, operand_scale) {} |
| |
| Operation op() { |
| DCHECK(op_ == Operation::kIncrement || op_ == Operation::kDecrement); |
| return op_; |
| } |
| |
| TNode<Number> SmiOp(TNode<Smi> value, Variable* var_feedback, |
| Label* do_float_op, Variable* var_float) override { |
| TNode<Smi> one = SmiConstant(1); |
| Label if_overflow(this), if_notoverflow(this); |
| TNode<Smi> result = op() == Operation::kIncrement |
| ? TrySmiAdd(value, one, &if_overflow) |
| : TrySmiSub(value, one, &if_overflow); |
| Goto(&if_notoverflow); |
| |
| BIND(&if_overflow); |
| { |
| var_float->Bind(SmiToFloat64(value)); |
| Goto(do_float_op); |
| } |
| |
| BIND(&if_notoverflow); |
| CombineFeedback(var_feedback, BinaryOperationFeedback::kSignedSmall); |
| return result; |
| } |
| |
| Node* FloatOp(Node* float_value) override { |
| return op() == Operation::kIncrement |
| ? Float64Add(float_value, Float64Constant(1.0)) |
| : Float64Sub(float_value, Float64Constant(1.0)); |
| } |
| |
| Node* BigIntOp(Node* bigint_value) override { |
| return CallRuntime(Runtime::kBigIntUnaryOp, GetContext(), bigint_value, |
| SmiConstant(op())); |
| } |
| |
| void IncWithFeedback() { |
| op_ = Operation::kIncrement; |
| UnaryOpWithFeedback(); |
| } |
| |
| void DecWithFeedback() { |
| op_ = Operation::kDecrement; |
| UnaryOpWithFeedback(); |
| } |
| |
| private: |
| Operation op_ = Operation::kEqual; // Dummy initialization. |
| }; |
| |
| // Inc |
| // |
| // Increments value in the accumulator by one. |
| IGNITION_HANDLER(Inc, IncDecAssembler) { IncWithFeedback(); } |
| |
| // Dec |
| // |
| // Decrements value in the accumulator by one. |
| IGNITION_HANDLER(Dec, IncDecAssembler) { DecWithFeedback(); } |
| |
| // ToBooleanLogicalNot |
| // |
| // Perform logical-not on the accumulator, first casting the |
| // accumulator to a boolean value if required. |
| IGNITION_HANDLER(ToBooleanLogicalNot, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Variable result(this, MachineRepresentation::kTagged); |
| Label if_true(this), if_false(this), end(this); |
| BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
| BIND(&if_true); |
| { |
| result.Bind(FalseConstant()); |
| Goto(&end); |
| } |
| BIND(&if_false); |
| { |
| result.Bind(TrueConstant()); |
| Goto(&end); |
| } |
| BIND(&end); |
| SetAccumulator(result.value()); |
| Dispatch(); |
| } |
| |
| // LogicalNot |
| // |
| // Perform logical-not on the accumulator, which must already be a boolean |
| // value. |
| IGNITION_HANDLER(LogicalNot, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Variable result(this, MachineRepresentation::kTagged); |
| Label if_true(this), if_false(this), end(this); |
| Node* true_value = TrueConstant(); |
| Node* false_value = FalseConstant(); |
| Branch(WordEqual(value, true_value), &if_true, &if_false); |
| BIND(&if_true); |
| { |
| result.Bind(false_value); |
| Goto(&end); |
| } |
| BIND(&if_false); |
| { |
| CSA_ASSERT(this, WordEqual(value, false_value)); |
| result.Bind(true_value); |
| Goto(&end); |
| } |
| BIND(&end); |
| SetAccumulator(result.value()); |
| Dispatch(); |
| } |
| |
| // TypeOf |
| // |
| // Load the accumulator with the string representating type of the |
| // object in the accumulator. |
| IGNITION_HANDLER(TypeOf, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* result = Typeof(value); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // DeletePropertyStrict |
| // |
| // Delete the property specified in the accumulator from the object |
| // referenced by the register operand following strict mode semantics. |
| IGNITION_HANDLER(DeletePropertyStrict, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* key = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* result = CallBuiltin(Builtins::kDeleteProperty, context, object, key, |
| SmiConstant(Smi::FromEnum(LanguageMode::kStrict))); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // DeletePropertySloppy |
| // |
| // Delete the property specified in the accumulator from the object |
| // referenced by the register operand following sloppy mode semantics. |
| IGNITION_HANDLER(DeletePropertySloppy, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* key = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* result = CallBuiltin(Builtins::kDeleteProperty, context, object, key, |
| SmiConstant(Smi::FromEnum(LanguageMode::kSloppy))); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // GetSuperConstructor |
| // |
| // Get the super constructor from the object referenced by the accumulator. |
| // The result is stored in register |reg|. |
| IGNITION_HANDLER(GetSuperConstructor, InterpreterAssembler) { |
| Node* active_function = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* result = GetSuperConstructor(context, active_function); |
| StoreRegisterAtOperandIndex(result, 0); |
| Dispatch(); |
| } |
| |
| class InterpreterJSCallAssembler : public InterpreterAssembler { |
| public: |
| InterpreterJSCallAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| // Generates code to perform a JS call that collects type feedback. |
| void JSCall(ConvertReceiverMode receiver_mode) { |
| Node* function = LoadRegisterAtOperandIndex(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* slot_id = BytecodeOperandIdx(3); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| // Collect the {function} feedback. |
| CollectCallFeedback(function, context, maybe_feedback_vector, slot_id); |
| |
| // Call the function and dispatch to the next handler. |
| CallJSAndDispatch(function, context, args, receiver_mode); |
| } |
| |
| // Generates code to perform a JS call without collecting feedback. |
| void JSCallNoFeedback(ConvertReceiverMode receiver_mode) { |
| Node* function = LoadRegisterAtOperandIndex(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* context = GetContext(); |
| |
| // Call the function and dispatch to the next handler. |
| CallJSAndDispatch(function, context, args, receiver_mode); |
| } |
| |
| // Generates code to perform a JS call with a known number of arguments that |
| // collects type feedback. |
| void JSCallN(int arg_count, ConvertReceiverMode receiver_mode) { |
| // Indices and counts of operands on the bytecode. |
| const int kFirstArgumentOperandIndex = 1; |
| const int kReceiverOperandCount = |
| (receiver_mode == ConvertReceiverMode::kNullOrUndefined) ? 0 : 1; |
| const int kRecieverAndArgOperandCount = kReceiverOperandCount + arg_count; |
| const int kSlotOperandIndex = |
| kFirstArgumentOperandIndex + kRecieverAndArgOperandCount; |
| |
| Node* function = LoadRegisterAtOperandIndex(0); |
| Node* slot_id = BytecodeOperandIdx(kSlotOperandIndex); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| // Collect the {function} feedback. |
| CollectCallFeedback(function, context, maybe_feedback_vector, slot_id); |
| |
| switch (kRecieverAndArgOperandCount) { |
| case 0: |
| CallJSAndDispatch(function, context, Int32Constant(arg_count), |
| receiver_mode); |
| break; |
| case 1: |
| CallJSAndDispatch( |
| function, context, Int32Constant(arg_count), receiver_mode, |
| LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex)); |
| break; |
| case 2: |
| CallJSAndDispatch( |
| function, context, Int32Constant(arg_count), receiver_mode, |
| LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex), |
| LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex + 1)); |
| break; |
| case 3: |
| CallJSAndDispatch( |
| function, context, Int32Constant(arg_count), receiver_mode, |
| LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex), |
| LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex + 1), |
| LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex + 2)); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| }; |
| |
| // Call <callable> <receiver> <arg_count> <feedback_slot_id> |
| // |
| // Call a JSfunction or Callable in |callable| with the |receiver| and |
| // |arg_count| arguments in subsequent registers. Collect type feedback |
| // into |feedback_slot_id| |
| IGNITION_HANDLER(CallAnyReceiver, InterpreterJSCallAssembler) { |
| JSCall(ConvertReceiverMode::kAny); |
| } |
| |
| IGNITION_HANDLER(CallProperty, InterpreterJSCallAssembler) { |
| JSCall(ConvertReceiverMode::kNotNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallProperty0, InterpreterJSCallAssembler) { |
| JSCallN(0, ConvertReceiverMode::kNotNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallProperty1, InterpreterJSCallAssembler) { |
| JSCallN(1, ConvertReceiverMode::kNotNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallProperty2, InterpreterJSCallAssembler) { |
| JSCallN(2, ConvertReceiverMode::kNotNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallUndefinedReceiver, InterpreterJSCallAssembler) { |
| JSCall(ConvertReceiverMode::kNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallUndefinedReceiver0, InterpreterJSCallAssembler) { |
| JSCallN(0, ConvertReceiverMode::kNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallUndefinedReceiver1, InterpreterJSCallAssembler) { |
| JSCallN(1, ConvertReceiverMode::kNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallUndefinedReceiver2, InterpreterJSCallAssembler) { |
| JSCallN(2, ConvertReceiverMode::kNullOrUndefined); |
| } |
| |
| IGNITION_HANDLER(CallNoFeedback, InterpreterJSCallAssembler) { |
| JSCallNoFeedback(ConvertReceiverMode::kAny); |
| } |
| |
| // CallRuntime <function_id> <first_arg> <arg_count> |
| // |
| // Call the runtime function |function_id| with the first argument in |
| // register |first_arg| and |arg_count| arguments in subsequent |
| // registers. |
| IGNITION_HANDLER(CallRuntime, InterpreterAssembler) { |
| Node* function_id = BytecodeOperandRuntimeId(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* context = GetContext(); |
| Node* result = CallRuntimeN(function_id, context, args); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // InvokeIntrinsic <function_id> <first_arg> <arg_count> |
| // |
| // Implements the semantic equivalent of calling the runtime function |
| // |function_id| with the first argument in |first_arg| and |arg_count| |
| // arguments in subsequent registers. |
| IGNITION_HANDLER(InvokeIntrinsic, InterpreterAssembler) { |
| Node* function_id = BytecodeOperandIntrinsicId(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* context = GetContext(); |
| Node* result = GenerateInvokeIntrinsic(this, function_id, context, args); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // CallRuntimeForPair <function_id> <first_arg> <arg_count> <first_return> |
| // |
| // Call the runtime function |function_id| which returns a pair, with the |
| // first argument in register |first_arg| and |arg_count| arguments in |
| // subsequent registers. Returns the result in <first_return> and |
| // <first_return + 1> |
| IGNITION_HANDLER(CallRuntimeForPair, InterpreterAssembler) { |
| // Call the runtime function. |
| Node* function_id = BytecodeOperandRuntimeId(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* context = GetContext(); |
| Node* result_pair = CallRuntimeN(function_id, context, args, 2); |
| // Store the results in <first_return> and <first_return + 1> |
| Node* result0 = Projection(0, result_pair); |
| Node* result1 = Projection(1, result_pair); |
| StoreRegisterPairAtOperandIndex(result0, result1, 3); |
| Dispatch(); |
| } |
| |
| // CallJSRuntime <context_index> <receiver> <arg_count> |
| // |
| // Call the JS runtime function that has the |context_index| with the receiver |
| // in register |receiver| and |arg_count| arguments in subsequent registers. |
| IGNITION_HANDLER(CallJSRuntime, InterpreterAssembler) { |
| Node* context_index = BytecodeOperandNativeContextIndex(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| |
| // Get the function to call from the native context. |
| Node* context = GetContext(); |
| Node* native_context = LoadNativeContext(context); |
| Node* function = LoadContextElement(native_context, context_index); |
| |
| // Call the function. |
| CallJSAndDispatch(function, context, args, |
| ConvertReceiverMode::kNullOrUndefined); |
| } |
| |
| // CallWithSpread <callable> <first_arg> <arg_count> |
| // |
| // Call a JSfunction or Callable in |callable| with the receiver in |
| // |first_arg| and |arg_count - 1| arguments in subsequent registers. The |
| // final argument is always a spread. |
| // |
| IGNITION_HANDLER(CallWithSpread, InterpreterAssembler) { |
| Node* callable = LoadRegisterAtOperandIndex(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* slot_id = BytecodeOperandIdx(3); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| // Call into Runtime function CallWithSpread which does everything. |
| CallJSWithSpreadAndDispatch(callable, context, args, slot_id, |
| maybe_feedback_vector); |
| } |
| |
| // ConstructWithSpread <first_arg> <arg_count> |
| // |
| // Call the constructor in |constructor| with the first argument in register |
| // |first_arg| and |arg_count| arguments in subsequent registers. The final |
| // argument is always a spread. The new.target is in the accumulator. |
| // |
| IGNITION_HANDLER(ConstructWithSpread, InterpreterAssembler) { |
| Node* new_target = GetAccumulator(); |
| Node* constructor = LoadRegisterAtOperandIndex(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* slot_id = BytecodeOperandIdx(3); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| Node* result = ConstructWithSpread(constructor, context, new_target, args, |
| slot_id, feedback_vector); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // Construct <constructor> <first_arg> <arg_count> |
| // |
| // Call operator construct with |constructor| and the first argument in |
| // register |first_arg| and |arg_count| arguments in subsequent |
| // registers. The new.target is in the accumulator. |
| // |
| IGNITION_HANDLER(Construct, InterpreterAssembler) { |
| Node* new_target = GetAccumulator(); |
| Node* constructor = LoadRegisterAtOperandIndex(0); |
| RegListNodePair args = GetRegisterListAtOperandIndex(1); |
| Node* slot_id = BytecodeOperandIdx(3); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| Node* result = Construct(constructor, context, new_target, args, slot_id, |
| feedback_vector); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| class InterpreterCompareOpAssembler : public InterpreterAssembler { |
| public: |
| InterpreterCompareOpAssembler(CodeAssemblerState* state, Bytecode bytecode, |
| OperandScale operand_scale) |
| : InterpreterAssembler(state, bytecode, operand_scale) {} |
| |
| void CompareOpWithFeedback(Operation compare_op) { |
| Node* lhs = LoadRegisterAtOperandIndex(0); |
| Node* rhs = GetAccumulator(); |
| Node* context = GetContext(); |
| |
| Variable var_type_feedback(this, MachineRepresentation::kTagged); |
| Node* result; |
| switch (compare_op) { |
| case Operation::kEqual: |
| result = Equal(lhs, rhs, context, &var_type_feedback); |
| break; |
| case Operation::kStrictEqual: |
| result = StrictEqual(lhs, rhs, &var_type_feedback); |
| break; |
| case Operation::kLessThan: |
| case Operation::kGreaterThan: |
| case Operation::kLessThanOrEqual: |
| case Operation::kGreaterThanOrEqual: |
| result = RelationalComparison(compare_op, lhs, rhs, context, |
| &var_type_feedback); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| Node* slot_index = BytecodeOperandIdx(1); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector, |
| slot_index); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| }; |
| |
| // TestEqual <src> |
| // |
| // Test if the value in the <src> register equals the accumulator. |
| IGNITION_HANDLER(TestEqual, InterpreterCompareOpAssembler) { |
| CompareOpWithFeedback(Operation::kEqual); |
| } |
| |
| // TestEqualStrict <src> |
| // |
| // Test if the value in the <src> register is strictly equal to the accumulator. |
| IGNITION_HANDLER(TestEqualStrict, InterpreterCompareOpAssembler) { |
| CompareOpWithFeedback(Operation::kStrictEqual); |
| } |
| |
| // TestLessThan <src> |
| // |
| // Test if the value in the <src> register is less than the accumulator. |
| IGNITION_HANDLER(TestLessThan, InterpreterCompareOpAssembler) { |
| CompareOpWithFeedback(Operation::kLessThan); |
| } |
| |
| // TestGreaterThan <src> |
| // |
| // Test if the value in the <src> register is greater than the accumulator. |
| IGNITION_HANDLER(TestGreaterThan, InterpreterCompareOpAssembler) { |
| CompareOpWithFeedback(Operation::kGreaterThan); |
| } |
| |
| // TestLessThanOrEqual <src> |
| // |
| // Test if the value in the <src> register is less than or equal to the |
| // accumulator. |
| IGNITION_HANDLER(TestLessThanOrEqual, InterpreterCompareOpAssembler) { |
| CompareOpWithFeedback(Operation::kLessThanOrEqual); |
| } |
| |
| // TestGreaterThanOrEqual <src> |
| // |
| // Test if the value in the <src> register is greater than or equal to the |
| // accumulator. |
| IGNITION_HANDLER(TestGreaterThanOrEqual, InterpreterCompareOpAssembler) { |
| CompareOpWithFeedback(Operation::kGreaterThanOrEqual); |
| } |
| |
| // TestReferenceEqual <src> |
| // |
| // Test if the value in the <src> register is equal to the accumulator |
| // by means of simple comparison. For SMIs and simple reference comparisons. |
| IGNITION_HANDLER(TestReferenceEqual, InterpreterAssembler) { |
| Node* lhs = LoadRegisterAtOperandIndex(0); |
| Node* rhs = GetAccumulator(); |
| Node* result = SelectBooleanConstant(WordEqual(lhs, rhs)); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // TestIn <src> <feedback_slot> |
| // |
| // Test if the object referenced by the register operand is a property of the |
| // object referenced by the accumulator. |
| IGNITION_HANDLER(TestIn, InterpreterAssembler) { |
| Node* name = LoadRegisterAtOperandIndex(0); |
| Node* object = GetAccumulator(); |
| Node* raw_slot = BytecodeOperandIdx(1); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| VARIABLE(var_result, MachineRepresentation::kTagged); |
| var_result.Bind(CallBuiltin(Builtins::kKeyedHasIC, context, object, name, |
| smi_slot, feedback_vector)); |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| |
| // TestInstanceOf <src> <feedback_slot> |
| // |
| // Test if the object referenced by the <src> register is an an instance of type |
| // referenced by the accumulator. |
| IGNITION_HANDLER(TestInstanceOf, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* callable = GetAccumulator(); |
| Node* slot_id = BytecodeOperandIdx(1); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| Label feedback_done(this); |
| GotoIf(IsUndefined(feedback_vector), &feedback_done); |
| |
| // Record feedback for the {callable} in the {feedback_vector}. |
| CollectCallableFeedback(callable, context, feedback_vector, slot_id); |
| Goto(&feedback_done); |
| |
| BIND(&feedback_done); |
| // Perform the actual instanceof operation. |
| SetAccumulator(InstanceOf(object, callable, context)); |
| Dispatch(); |
| } |
| |
| // TestUndetectable |
| // |
| // Test if the value in the accumulator is undetectable (null, undefined or |
| // document.all). |
| IGNITION_HANDLER(TestUndetectable, InterpreterAssembler) { |
| Label return_false(this), end(this); |
| Node* object = GetAccumulator(); |
| |
| // If the object is an Smi then return false. |
| SetAccumulator(FalseConstant()); |
| GotoIf(TaggedIsSmi(object), &end); |
| |
| // If it is a HeapObject, load the map and check for undetectable bit. |
| Node* result = SelectBooleanConstant(IsUndetectableMap(LoadMap(object))); |
| SetAccumulator(result); |
| Goto(&end); |
| |
| BIND(&end); |
| Dispatch(); |
| } |
| |
| // TestNull |
| // |
| // Test if the value in accumulator is strictly equal to null. |
| IGNITION_HANDLER(TestNull, InterpreterAssembler) { |
| Node* object = GetAccumulator(); |
| Node* result = SelectBooleanConstant(WordEqual(object, NullConstant())); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // TestUndefined |
| // |
| // Test if the value in the accumulator is strictly equal to undefined. |
| IGNITION_HANDLER(TestUndefined, InterpreterAssembler) { |
| Node* object = GetAccumulator(); |
| Node* result = SelectBooleanConstant(WordEqual(object, UndefinedConstant())); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // TestTypeOf <literal_flag> |
| // |
| // Tests if the object in the <accumulator> is typeof the literal represented |
| // by |literal_flag|. |
| IGNITION_HANDLER(TestTypeOf, InterpreterAssembler) { |
| Node* object = GetAccumulator(); |
| Node* literal_flag = BytecodeOperandFlag(0); |
| |
| #define MAKE_LABEL(name, lower_case) Label if_##lower_case(this); |
| TYPEOF_LITERAL_LIST(MAKE_LABEL) |
| #undef MAKE_LABEL |
| |
| #define LABEL_POINTER(name, lower_case) &if_##lower_case, |
| Label* labels[] = {TYPEOF_LITERAL_LIST(LABEL_POINTER)}; |
| #undef LABEL_POINTER |
| |
| #define CASE(name, lower_case) \ |
| static_cast<int32_t>(TestTypeOfFlags::LiteralFlag::k##name), |
| int32_t cases[] = {TYPEOF_LITERAL_LIST(CASE)}; |
| #undef CASE |
| |
| Label if_true(this), if_false(this), end(this); |
| |
| // We juse use the final label as the default and properly CSA_ASSERT |
| // that the {literal_flag} is valid here; this significantly improves |
| // the generated code (compared to having a default label that aborts). |
| unsigned const num_cases = arraysize(cases); |
| CSA_ASSERT(this, Uint32LessThan(literal_flag, Int32Constant(num_cases))); |
| Switch(literal_flag, labels[num_cases - 1], cases, labels, num_cases - 1); |
| |
| BIND(&if_number); |
| { |
| Comment("IfNumber"); |
| GotoIfNumber(object, &if_true); |
| Goto(&if_false); |
| } |
| BIND(&if_string); |
| { |
| Comment("IfString"); |
| GotoIf(TaggedIsSmi(object), &if_false); |
| Branch(IsString(object), &if_true, &if_false); |
| } |
| BIND(&if_symbol); |
| { |
| Comment("IfSymbol"); |
| GotoIf(TaggedIsSmi(object), &if_false); |
| Branch(IsSymbol(object), &if_true, &if_false); |
| } |
| BIND(&if_boolean); |
| { |
| Comment("IfBoolean"); |
| GotoIf(WordEqual(object, TrueConstant()), &if_true); |
| Branch(WordEqual(object, FalseConstant()), &if_true, &if_false); |
| } |
| BIND(&if_bigint); |
| { |
| Comment("IfBigInt"); |
| GotoIf(TaggedIsSmi(object), &if_false); |
| Branch(IsBigInt(object), &if_true, &if_false); |
| } |
| BIND(&if_undefined); |
| { |
| Comment("IfUndefined"); |
| GotoIf(TaggedIsSmi(object), &if_false); |
| // Check it is not null and the map has the undetectable bit set. |
| GotoIf(IsNull(object), &if_false); |
| Branch(IsUndetectableMap(LoadMap(object)), &if_true, &if_false); |
| } |
| BIND(&if_function); |
| { |
| Comment("IfFunction"); |
| GotoIf(TaggedIsSmi(object), &if_false); |
| // Check if callable bit is set and not undetectable. |
| Node* map_bitfield = LoadMapBitField(LoadMap(object)); |
| Node* callable_undetectable = |
| Word32And(map_bitfield, Int32Constant(Map::IsUndetectableBit::kMask | |
| Map::IsCallableBit::kMask)); |
| Branch(Word32Equal(callable_undetectable, |
| Int32Constant(Map::IsCallableBit::kMask)), |
| &if_true, &if_false); |
| } |
| BIND(&if_object); |
| { |
| Comment("IfObject"); |
| GotoIf(TaggedIsSmi(object), &if_false); |
| |
| // If the object is null then return true. |
| GotoIf(IsNull(object), &if_true); |
| |
| // Check if the object is a receiver type and is not undefined or callable. |
| Node* map = LoadMap(object); |
| GotoIfNot(IsJSReceiverMap(map), &if_false); |
| Node* map_bitfield = LoadMapBitField(map); |
| Node* callable_undetectable = |
| Word32And(map_bitfield, Int32Constant(Map::IsUndetectableBit::kMask | |
| Map::IsCallableBit::kMask)); |
| Branch(Word32Equal(callable_undetectable, Int32Constant(0)), &if_true, |
| &if_false); |
| } |
| BIND(&if_other); |
| { |
| // Typeof doesn't return any other string value. |
| Goto(&if_false); |
| } |
| |
| BIND(&if_false); |
| { |
| SetAccumulator(FalseConstant()); |
| Goto(&end); |
| } |
| BIND(&if_true); |
| { |
| SetAccumulator(TrueConstant()); |
| Goto(&end); |
| } |
| BIND(&end); |
| Dispatch(); |
| } |
| |
| // Jump <imm> |
| // |
| // Jump by the number of bytes represented by the immediate operand |imm|. |
| IGNITION_HANDLER(Jump, InterpreterAssembler) { |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| Jump(relative_jump); |
| } |
| |
| // JumpConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool. |
| IGNITION_HANDLER(JumpConstant, InterpreterAssembler) { |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| Jump(relative_jump); |
| } |
| |
| // JumpIfTrue <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the |
| // accumulator contains true. This only works for boolean inputs, and |
| // will misbehave if passed arbitrary input values. |
| IGNITION_HANDLER(JumpIfTrue, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| CSA_ASSERT(this, TaggedIsNotSmi(accumulator)); |
| CSA_ASSERT(this, IsBoolean(accumulator)); |
| JumpIfWordEqual(accumulator, TrueConstant(), relative_jump); |
| } |
| |
| // JumpIfTrueConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the accumulator contains true. This only works for boolean inputs, |
| // and will misbehave if passed arbitrary input values. |
| IGNITION_HANDLER(JumpIfTrueConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| CSA_ASSERT(this, TaggedIsNotSmi(accumulator)); |
| CSA_ASSERT(this, IsBoolean(accumulator)); |
| JumpIfWordEqual(accumulator, TrueConstant(), relative_jump); |
| } |
| |
| // JumpIfFalse <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the |
| // accumulator contains false. This only works for boolean inputs, and |
| // will misbehave if passed arbitrary input values. |
| IGNITION_HANDLER(JumpIfFalse, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| CSA_ASSERT(this, TaggedIsNotSmi(accumulator)); |
| CSA_ASSERT(this, IsBoolean(accumulator)); |
| JumpIfWordEqual(accumulator, FalseConstant(), relative_jump); |
| } |
| |
| // JumpIfFalseConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the accumulator contains false. This only works for boolean inputs, |
| // and will misbehave if passed arbitrary input values. |
| IGNITION_HANDLER(JumpIfFalseConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| CSA_ASSERT(this, TaggedIsNotSmi(accumulator)); |
| CSA_ASSERT(this, IsBoolean(accumulator)); |
| JumpIfWordEqual(accumulator, FalseConstant(), relative_jump); |
| } |
| |
| // JumpIfToBooleanTrue <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is true when the object is cast to boolean. |
| IGNITION_HANDLER(JumpIfToBooleanTrue, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| Label if_true(this), if_false(this); |
| BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
| BIND(&if_true); |
| Jump(relative_jump); |
| BIND(&if_false); |
| Dispatch(); |
| } |
| |
| // JumpIfToBooleanTrueConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is true when the object is |
| // cast to boolean. |
| IGNITION_HANDLER(JumpIfToBooleanTrueConstant, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| Label if_true(this), if_false(this); |
| BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
| BIND(&if_true); |
| Jump(relative_jump); |
| BIND(&if_false); |
| Dispatch(); |
| } |
| |
| // JumpIfToBooleanFalse <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is false when the object is cast to boolean. |
| IGNITION_HANDLER(JumpIfToBooleanFalse, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| Label if_true(this), if_false(this); |
| BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
| BIND(&if_true); |
| Dispatch(); |
| BIND(&if_false); |
| Jump(relative_jump); |
| } |
| |
| // JumpIfToBooleanFalseConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is false when the object is |
| // cast to boolean. |
| IGNITION_HANDLER(JumpIfToBooleanFalseConstant, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| Label if_true(this), if_false(this); |
| BranchIfToBooleanIsTrue(value, &if_true, &if_false); |
| BIND(&if_true); |
| Dispatch(); |
| BIND(&if_false); |
| Jump(relative_jump); |
| } |
| |
| // JumpIfNull <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is the null constant. |
| IGNITION_HANDLER(JumpIfNull, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| JumpIfWordEqual(accumulator, NullConstant(), relative_jump); |
| } |
| |
| // JumpIfNullConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is the null constant. |
| IGNITION_HANDLER(JumpIfNullConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| JumpIfWordEqual(accumulator, NullConstant(), relative_jump); |
| } |
| |
| // JumpIfNotNull <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is not the null constant. |
| IGNITION_HANDLER(JumpIfNotNull, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| JumpIfWordNotEqual(accumulator, NullConstant(), relative_jump); |
| } |
| |
| // JumpIfNotNullConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is not the null constant. |
| IGNITION_HANDLER(JumpIfNotNullConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| JumpIfWordNotEqual(accumulator, NullConstant(), relative_jump); |
| } |
| |
| // JumpIfUndefined <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is the undefined constant. |
| IGNITION_HANDLER(JumpIfUndefined, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| JumpIfWordEqual(accumulator, UndefinedConstant(), relative_jump); |
| } |
| |
| // JumpIfUndefinedConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is the undefined constant. |
| IGNITION_HANDLER(JumpIfUndefinedConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| JumpIfWordEqual(accumulator, UndefinedConstant(), relative_jump); |
| } |
| |
| // JumpIfNotUndefined <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is not the undefined constant. |
| IGNITION_HANDLER(JumpIfNotUndefined, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| JumpIfWordNotEqual(accumulator, UndefinedConstant(), relative_jump); |
| } |
| |
| // JumpIfNotUndefinedConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is not the undefined |
| // constant. |
| IGNITION_HANDLER(JumpIfNotUndefinedConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| JumpIfWordNotEqual(accumulator, UndefinedConstant(), relative_jump); |
| } |
| |
| // JumpIfJSReceiver <imm> |
| // |
| // Jump by the number of bytes represented by an immediate operand if the object |
| // referenced by the accumulator is a JSReceiver. |
| IGNITION_HANDLER(JumpIfJSReceiver, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| |
| Label if_object(this), if_notobject(this, Label::kDeferred), if_notsmi(this); |
| Branch(TaggedIsSmi(accumulator), &if_notobject, &if_notsmi); |
| |
| BIND(&if_notsmi); |
| Branch(IsJSReceiver(accumulator), &if_object, &if_notobject); |
| BIND(&if_object); |
| Jump(relative_jump); |
| |
| BIND(&if_notobject); |
| Dispatch(); |
| } |
| |
| // JumpIfJSReceiverConstant <idx> |
| // |
| // Jump by the number of bytes in the Smi in the |idx| entry in the constant |
| // pool if the object referenced by the accumulator is a JSReceiver. |
| IGNITION_HANDLER(JumpIfJSReceiverConstant, InterpreterAssembler) { |
| Node* accumulator = GetAccumulator(); |
| Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0); |
| |
| Label if_object(this), if_notobject(this), if_notsmi(this); |
| Branch(TaggedIsSmi(accumulator), &if_notobject, &if_notsmi); |
| |
| BIND(&if_notsmi); |
| Branch(IsJSReceiver(accumulator), &if_object, &if_notobject); |
| |
| BIND(&if_object); |
| Jump(relative_jump); |
| |
| BIND(&if_notobject); |
| Dispatch(); |
| } |
| |
| // JumpLoop <imm> <loop_depth> |
| // |
| // Jump by the number of bytes represented by the immediate operand |imm|. Also |
| // performs a loop nesting check and potentially triggers OSR in case the |
| // current OSR level matches (or exceeds) the specified |loop_depth|. |
| IGNITION_HANDLER(JumpLoop, InterpreterAssembler) { |
| Node* relative_jump = BytecodeOperandUImmWord(0); |
| Node* loop_depth = BytecodeOperandImm(1); |
| Node* osr_level = LoadOsrNestingLevel(); |
| |
| // Check if OSR points at the given {loop_depth} are armed by comparing it to |
| // the current {osr_level} loaded from the header of the BytecodeArray. |
| Label ok(this), osr_armed(this, Label::kDeferred); |
| Node* condition = Int32GreaterThanOrEqual(loop_depth, osr_level); |
| Branch(condition, &ok, &osr_armed); |
| |
| BIND(&ok); |
| JumpBackward(relative_jump); |
| |
| BIND(&osr_armed); |
| { |
| Callable callable = CodeFactory::InterpreterOnStackReplacement(isolate()); |
| Node* target = HeapConstant(callable.code()); |
| Node* context = GetContext(); |
| CallStub(callable.descriptor(), target, context); |
| JumpBackward(relative_jump); |
| } |
| } |
| |
| // SwitchOnSmiNoFeedback <table_start> <table_length> <case_value_base> |
| // |
| // Jump by the number of bytes defined by a Smi in a table in the constant pool, |
| // where the table starts at |table_start| and has |table_length| entries. |
| // The table is indexed by the accumulator, minus |case_value_base|. If the |
| // case_value falls outside of the table |table_length|, fall-through to the |
| // next bytecode. |
| IGNITION_HANDLER(SwitchOnSmiNoFeedback, InterpreterAssembler) { |
| Node* acc = GetAccumulator(); |
| Node* table_start = BytecodeOperandIdx(0); |
| Node* table_length = BytecodeOperandUImmWord(1); |
| Node* case_value_base = BytecodeOperandImmIntPtr(2); |
| |
| Label fall_through(this); |
| |
| // The accumulator must be a Smi. |
| // TODO(leszeks): Add a bytecode with type feedback that allows other |
| // accumulator values. |
| CSA_ASSERT(this, TaggedIsSmi(acc)); |
| |
| Node* case_value = IntPtrSub(SmiUntag(acc), case_value_base); |
| GotoIf(IntPtrLessThan(case_value, IntPtrConstant(0)), &fall_through); |
| GotoIf(IntPtrGreaterThanOrEqual(case_value, table_length), &fall_through); |
| Node* entry = IntPtrAdd(table_start, case_value); |
| Node* relative_jump = LoadAndUntagConstantPoolEntry(entry); |
| Jump(relative_jump); |
| |
| BIND(&fall_through); |
| Dispatch(); |
| } |
| |
| // CreateRegExpLiteral <pattern_idx> <literal_idx> <flags> |
| // |
| // Creates a regular expression literal for literal index <literal_idx> with |
| // <flags> and the pattern in <pattern_idx>. |
| IGNITION_HANDLER(CreateRegExpLiteral, InterpreterAssembler) { |
| Node* pattern = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* slot_id = BytecodeOperandIdx(1); |
| Node* flags = SmiFromInt32(BytecodeOperandFlag(2)); |
| Node* context = GetContext(); |
| |
| VARIABLE(result, MachineRepresentation::kTagged); |
| |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| result.Bind(constructor_assembler.EmitCreateRegExpLiteral( |
| feedback_vector, slot_id, pattern, flags, context)); |
| SetAccumulator(result.value()); |
| Dispatch(); |
| } |
| |
| // CreateArrayLiteral <element_idx> <literal_idx> <flags> |
| // |
| // Creates an array literal for literal index <literal_idx> with |
| // CreateArrayLiteral flags <flags> and constant elements in <element_idx>. |
| IGNITION_HANDLER(CreateArrayLiteral, InterpreterAssembler) { |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* slot_id = BytecodeOperandIdx(1); |
| Node* context = GetContext(); |
| Node* bytecode_flags = BytecodeOperandFlag(2); |
| |
| Label fast_shallow_clone(this), call_runtime(this, Label::kDeferred); |
| // No feedback, so handle it as a slow case. |
| GotoIf(IsUndefined(feedback_vector), &call_runtime); |
| |
| Branch(IsSetWord32<CreateArrayLiteralFlags::FastCloneSupportedBit>( |
| bytecode_flags), |
| &fast_shallow_clone, &call_runtime); |
| |
| BIND(&fast_shallow_clone); |
| { |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| Node* result = constructor_assembler.EmitCreateShallowArrayLiteral( |
| feedback_vector, slot_id, context, &call_runtime, |
| TRACK_ALLOCATION_SITE); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| BIND(&call_runtime); |
| { |
| Node* flags_raw = DecodeWordFromWord32<CreateArrayLiteralFlags::FlagsBits>( |
| bytecode_flags); |
| Node* flags = SmiTag(flags_raw); |
| Node* constant_elements = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* result = |
| CallRuntime(Runtime::kCreateArrayLiteral, context, feedback_vector, |
| SmiTag(slot_id), constant_elements, flags); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| |
| // CreateEmptyArrayLiteral <literal_idx> |
| // |
| // Creates an empty JSArray literal for literal index <literal_idx>. |
| IGNITION_HANDLER(CreateEmptyArrayLiteral, InterpreterAssembler) { |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* slot_id = BytecodeOperandIdx(0); |
| Node* context = GetContext(); |
| |
| Label no_feedback(this, Label::kDeferred), end(this); |
| VARIABLE(result, MachineRepresentation::kTagged); |
| GotoIf(IsUndefined(feedback_vector), &no_feedback); |
| |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| result.Bind(constructor_assembler.EmitCreateEmptyArrayLiteral( |
| feedback_vector, slot_id, context)); |
| Goto(&end); |
| |
| BIND(&no_feedback); |
| { |
| TNode<Map> array_map = LoadJSArrayElementsMap(GetInitialFastElementsKind(), |
| LoadNativeContext(context)); |
| result.Bind(AllocateJSArray(GetInitialFastElementsKind(), array_map, |
| SmiConstant(0), SmiConstant(0), nullptr, |
| ParameterMode::SMI_PARAMETERS)); |
| Goto(&end); |
| } |
| |
| BIND(&end); |
| SetAccumulator(result.value()); |
| Dispatch(); |
| } |
| |
| // CreateArrayFromIterable |
| // |
| // Spread the given iterable from the accumulator into a new JSArray. |
| // TODO(neis): Turn this into an intrinsic when we're running out of bytecodes. |
| IGNITION_HANDLER(CreateArrayFromIterable, InterpreterAssembler) { |
| Node* iterable = GetAccumulator(); |
| Node* context = GetContext(); |
| Node* result = |
| CallBuiltin(Builtins::kIterableToListWithSymbolLookup, context, iterable); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // CreateObjectLiteral <element_idx> <literal_idx> <flags> |
| // |
| // Creates an object literal for literal index <literal_idx> with |
| // CreateObjectLiteralFlags <flags> and constant elements in <element_idx>. |
| IGNITION_HANDLER(CreateObjectLiteral, InterpreterAssembler) { |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* slot_id = BytecodeOperandIdx(1); |
| Node* bytecode_flags = BytecodeOperandFlag(2); |
| |
| Label if_fast_clone(this), if_not_fast_clone(this, Label::kDeferred); |
| // No feedback, so handle it as a slow case. |
| GotoIf(IsUndefined(feedback_vector), &if_not_fast_clone); |
| |
| // Check if we can do a fast clone or have to call the runtime. |
| Branch(IsSetWord32<CreateObjectLiteralFlags::FastCloneSupportedBit>( |
| bytecode_flags), |
| &if_fast_clone, &if_not_fast_clone); |
| |
| BIND(&if_fast_clone); |
| { |
| // If we can do a fast clone do the fast-path in CreateShallowObjectLiteral. |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| Node* result = constructor_assembler.EmitCreateShallowObjectLiteral( |
| feedback_vector, slot_id, &if_not_fast_clone); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| BIND(&if_not_fast_clone); |
| { |
| // If we can't do a fast clone, call into the runtime. |
| Node* object_boilerplate_description = |
| LoadConstantPoolEntryAtOperandIndex(0); |
| Node* context = GetContext(); |
| |
| Node* flags_raw = DecodeWordFromWord32<CreateObjectLiteralFlags::FlagsBits>( |
| bytecode_flags); |
| Node* flags = SmiTag(flags_raw); |
| |
| Node* result = |
| CallRuntime(Runtime::kCreateObjectLiteral, context, feedback_vector, |
| SmiTag(slot_id), object_boilerplate_description, flags); |
| SetAccumulator(result); |
| // TODO(klaasb) build a single dispatch once the call is inlined |
| Dispatch(); |
| } |
| } |
| |
| // CreateEmptyObjectLiteral |
| // |
| // Creates an empty JSObject literal. |
| IGNITION_HANDLER(CreateEmptyObjectLiteral, InterpreterAssembler) { |
| Node* context = GetContext(); |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| Node* result = constructor_assembler.EmitCreateEmptyObjectLiteral(context); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // CloneObject <source_idx> <flags> <feedback_slot> |
| // |
| // Allocates a new JSObject with each enumerable own property copied from |
| // {source}, converting getters into data properties. |
| IGNITION_HANDLER(CloneObject, InterpreterAssembler) { |
| Node* source = LoadRegisterAtOperandIndex(0); |
| Node* bytecode_flags = BytecodeOperandFlag(1); |
| Node* raw_flags = |
| DecodeWordFromWord32<CreateObjectLiteralFlags::FlagsBits>(bytecode_flags); |
| Node* smi_flags = SmiTag(raw_flags); |
| Node* raw_slot = BytecodeOperandIdx(2); |
| Node* smi_slot = SmiTag(raw_slot); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| Node* context = GetContext(); |
| |
| Variable var_result(this, MachineRepresentation::kTagged); |
| var_result.Bind(CallBuiltin(Builtins::kCloneObjectIC, context, source, |
| smi_flags, smi_slot, maybe_feedback_vector)); |
| SetAccumulator(var_result.value()); |
| Dispatch(); |
| } |
| |
| // GetTemplateObject <descriptor_idx> <literal_idx> |
| // |
| // Creates the template to pass for tagged templates and returns it in the |
| // accumulator, creating and caching the site object on-demand as per the |
| // specification. |
| IGNITION_HANDLER(GetTemplateObject, InterpreterAssembler) { |
| Node* feedback_vector = LoadFeedbackVector(); |
| Node* slot = BytecodeOperandIdx(1); |
| |
| Label call_runtime(this, Label::kDeferred); |
| GotoIf(IsUndefined(feedback_vector), &call_runtime); |
| |
| TNode<Object> cached_value = |
| CAST(LoadFeedbackVectorSlot(feedback_vector, slot, 0, INTPTR_PARAMETERS)); |
| |
| GotoIf(WordEqual(cached_value, SmiConstant(0)), &call_runtime); |
| |
| SetAccumulator(cached_value); |
| Dispatch(); |
| |
| BIND(&call_runtime); |
| { |
| Node* description = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* slot_smi = SmiTag(slot); |
| Node* closure = LoadRegister(Register::function_closure()); |
| Node* shared_info = |
| LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset); |
| Node* context = GetContext(); |
| Node* result = CallRuntime(Runtime::kGetTemplateObject, context, |
| description, shared_info, slot_smi); |
| |
| Label end(this); |
| GotoIf(IsUndefined(feedback_vector), &end); |
| StoreFeedbackVectorSlot(feedback_vector, slot, result); |
| Goto(&end); |
| |
| Bind(&end); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| |
| // CreateClosure <index> <slot> <flags> |
| // |
| // Creates a new closure for SharedFunctionInfo at position |index| in the |
| // constant pool and with pretenuring controlled by |flags|. |
| IGNITION_HANDLER(CreateClosure, InterpreterAssembler) { |
| Node* shared = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* flags = BytecodeOperandFlag(2); |
| Node* context = GetContext(); |
| Node* slot = BytecodeOperandIdx(1); |
| |
| Label if_undefined(this); |
| TNode<FixedArray> feedback_cell_array = |
| LoadClosureFeedbackArray(LoadRegister(Register::function_closure())); |
| TNode<FeedbackCell> feedback_cell = |
| CAST(LoadFixedArrayElement(feedback_cell_array, slot)); |
| |
| Label if_fast(this), if_slow(this, Label::kDeferred); |
| Branch(IsSetWord32<CreateClosureFlags::FastNewClosureBit>(flags), &if_fast, |
| &if_slow); |
| |
| BIND(&if_fast); |
| { |
| Node* result = |
| CallBuiltin(Builtins::kFastNewClosure, context, shared, feedback_cell); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| BIND(&if_slow); |
| { |
| Label if_newspace(this), if_oldspace(this); |
| Branch(IsSetWord32<CreateClosureFlags::PretenuredBit>(flags), &if_oldspace, |
| &if_newspace); |
| |
| BIND(&if_newspace); |
| { |
| Node* result = |
| CallRuntime(Runtime::kNewClosure, context, shared, feedback_cell); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| BIND(&if_oldspace); |
| { |
| Node* result = CallRuntime(Runtime::kNewClosure_Tenured, context, shared, |
| feedback_cell); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| } |
| |
| // CreateBlockContext <index> |
| // |
| // Creates a new block context with the scope info constant at |index|. |
| IGNITION_HANDLER(CreateBlockContext, InterpreterAssembler) { |
| Node* scope_info = LoadConstantPoolEntryAtOperandIndex(0); |
| Node* context = GetContext(); |
| SetAccumulator(CallRuntime(Runtime::kPushBlockContext, context, scope_info)); |
| Dispatch(); |
| } |
| |
| // CreateCatchContext <exception> <scope_info_idx> |
| // |
| // Creates a new context for a catch block with the |exception| in a register |
| // and the ScopeInfo at |scope_info_idx|. |
| IGNITION_HANDLER(CreateCatchContext, InterpreterAssembler) { |
| Node* exception = LoadRegisterAtOperandIndex(0); |
| Node* scope_info = LoadConstantPoolEntryAtOperandIndex(1); |
| Node* context = GetContext(); |
| SetAccumulator( |
| CallRuntime(Runtime::kPushCatchContext, context, exception, scope_info)); |
| Dispatch(); |
| } |
| |
| // CreateFunctionContext <scope_info_idx> <slots> |
| // |
| // Creates a new context with number of |slots| for the function closure. |
| IGNITION_HANDLER(CreateFunctionContext, InterpreterAssembler) { |
| Node* scope_info_idx = BytecodeOperandIdx(0); |
| Node* scope_info = LoadConstantPoolEntry(scope_info_idx); |
| Node* slots = BytecodeOperandUImm(1); |
| Node* context = GetContext(); |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| SetAccumulator(constructor_assembler.EmitFastNewFunctionContext( |
| scope_info, slots, context, FUNCTION_SCOPE)); |
| Dispatch(); |
| } |
| |
| // CreateEvalContext <scope_info_idx> <slots> |
| // |
| // Creates a new context with number of |slots| for an eval closure. |
| IGNITION_HANDLER(CreateEvalContext, InterpreterAssembler) { |
| Node* scope_info_idx = BytecodeOperandIdx(0); |
| Node* scope_info = LoadConstantPoolEntry(scope_info_idx); |
| Node* slots = BytecodeOperandUImm(1); |
| Node* context = GetContext(); |
| ConstructorBuiltinsAssembler constructor_assembler(state()); |
| SetAccumulator(constructor_assembler.EmitFastNewFunctionContext( |
| scope_info, slots, context, EVAL_SCOPE)); |
| Dispatch(); |
| } |
| |
| // CreateWithContext <register> <scope_info_idx> |
| // |
| // Creates a new context with the ScopeInfo at |scope_info_idx| for a |
| // with-statement with the object in |register|. |
| IGNITION_HANDLER(CreateWithContext, InterpreterAssembler) { |
| Node* object = LoadRegisterAtOperandIndex(0); |
| Node* scope_info = LoadConstantPoolEntryAtOperandIndex(1); |
| Node* context = GetContext(); |
| SetAccumulator( |
| CallRuntime(Runtime::kPushWithContext, context, object, scope_info)); |
| Dispatch(); |
| } |
| |
| // CreateMappedArguments |
| // |
| // Creates a new mapped arguments object. |
| IGNITION_HANDLER(CreateMappedArguments, InterpreterAssembler) { |
| Node* closure = LoadRegister(Register::function_closure()); |
| Node* context = GetContext(); |
| |
| Label if_duplicate_parameters(this, Label::kDeferred); |
| Label if_not_duplicate_parameters(this); |
| |
| // Check if function has duplicate parameters. |
| // TODO(rmcilroy): Remove this check when FastNewSloppyArgumentsStub supports |
| // duplicate parameters. |
| Node* shared_info = |
| LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset); |
| Node* flags = LoadObjectField(shared_info, SharedFunctionInfo::kFlagsOffset, |
| MachineType::Uint32()); |
| Node* has_duplicate_parameters = |
| IsSetWord32<SharedFunctionInfo::HasDuplicateParametersBit>(flags); |
| Branch(has_duplicate_parameters, &if_duplicate_parameters, |
| &if_not_duplicate_parameters); |
| |
| BIND(&if_not_duplicate_parameters); |
| { |
| ArgumentsBuiltinsAssembler constructor_assembler(state()); |
| Node* result = |
| constructor_assembler.EmitFastNewSloppyArguments(context, closure); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| BIND(&if_duplicate_parameters); |
| { |
| Node* result = |
| CallRuntime(Runtime::kNewSloppyArguments_Generic, context, closure); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| |
| // CreateUnmappedArguments |
| // |
| // Creates a new unmapped arguments object. |
| IGNITION_HANDLER(CreateUnmappedArguments, InterpreterAssembler) { |
| Node* context = GetContext(); |
| Node* closure = LoadRegister(Register::function_closure()); |
| ArgumentsBuiltinsAssembler builtins_assembler(state()); |
| Node* result = |
| builtins_assembler.EmitFastNewStrictArguments(context, closure); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // CreateRestParameter |
| // |
| // Creates a new rest parameter array. |
| IGNITION_HANDLER(CreateRestParameter, InterpreterAssembler) { |
| Node* closure = LoadRegister(Register::function_closure()); |
| Node* context = GetContext(); |
| ArgumentsBuiltinsAssembler builtins_assembler(state()); |
| Node* result = builtins_assembler.EmitFastNewRestParameter(context, closure); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // StackCheck |
| // |
| // Performs a stack guard check. |
| IGNITION_HANDLER(StackCheck, InterpreterAssembler) { |
| TNode<Context> context = CAST(GetContext()); |
| PerformStackCheck(context); |
| Dispatch(); |
| } |
| |
| // SetPendingMessage |
| // |
| // Sets the pending message to the value in the accumulator, and returns the |
| // previous pending message in the accumulator. |
| IGNITION_HANDLER(SetPendingMessage, InterpreterAssembler) { |
| Node* pending_message = ExternalConstant( |
| ExternalReference::address_of_pending_message_obj(isolate())); |
| Node* previous_message = Load(MachineType::TaggedPointer(), pending_message); |
| Node* new_message = GetAccumulator(); |
| StoreFullTaggedNoWriteBarrier(pending_message, new_message); |
| SetAccumulator(previous_message); |
| Dispatch(); |
| } |
| |
| // Throw |
| // |
| // Throws the exception in the accumulator. |
| IGNITION_HANDLER(Throw, InterpreterAssembler) { |
| Node* exception = GetAccumulator(); |
| Node* context = GetContext(); |
| CallRuntime(Runtime::kThrow, context, exception); |
| // We shouldn't ever return from a throw. |
| Abort(AbortReason::kUnexpectedReturnFromThrow); |
| Unreachable(); |
| } |
| |
| // ReThrow |
| // |
| // Re-throws the exception in the accumulator. |
| IGNITION_HANDLER(ReThrow, InterpreterAssembler) { |
| Node* exception = GetAccumulator(); |
| Node* context = GetContext(); |
| CallRuntime(Runtime::kReThrow, context, exception); |
| // We shouldn't ever return from a throw. |
| Abort(AbortReason::kUnexpectedReturnFromThrow); |
| Unreachable(); |
| } |
| |
| // Abort <abort_reason> |
| // |
| // Aborts execution (via a call to the runtime function). |
| IGNITION_HANDLER(Abort, InterpreterAssembler) { |
| Node* reason = BytecodeOperandIdx(0); |
| CallRuntime(Runtime::kAbort, NoContextConstant(), SmiTag(reason)); |
| Unreachable(); |
| } |
| |
| // Return |
| // |
| // Return the value in the accumulator. |
| IGNITION_HANDLER(Return, InterpreterAssembler) { |
| UpdateInterruptBudgetOnReturn(); |
| Node* accumulator = GetAccumulator(); |
| Return(accumulator); |
| } |
| |
| // ThrowReferenceErrorIfHole <variable_name> |
| // |
| // Throws an exception if the value in the accumulator is TheHole. |
| IGNITION_HANDLER(ThrowReferenceErrorIfHole, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| |
| Label throw_error(this, Label::kDeferred); |
| GotoIf(WordEqual(value, TheHoleConstant()), &throw_error); |
| Dispatch(); |
| |
| BIND(&throw_error); |
| { |
| Node* name = LoadConstantPoolEntryAtOperandIndex(0); |
| CallRuntime(Runtime::kThrowAccessedUninitializedVariable, GetContext(), |
| name); |
| // We shouldn't ever return from a throw. |
| Abort(AbortReason::kUnexpectedReturnFromThrow); |
| Unreachable(); |
| } |
| } |
| |
| // ThrowSuperNotCalledIfHole |
| // |
| // Throws an exception if the value in the accumulator is TheHole. |
| IGNITION_HANDLER(ThrowSuperNotCalledIfHole, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| |
| Label throw_error(this, Label::kDeferred); |
| GotoIf(WordEqual(value, TheHoleConstant()), &throw_error); |
| Dispatch(); |
| |
| BIND(&throw_error); |
| { |
| CallRuntime(Runtime::kThrowSuperNotCalled, GetContext()); |
| // We shouldn't ever return from a throw. |
| Abort(AbortReason::kUnexpectedReturnFromThrow); |
| Unreachable(); |
| } |
| } |
| |
| // ThrowSuperAlreadyCalledIfNotHole |
| // |
| // Throws SuperAleradyCalled exception if the value in the accumulator is not |
| // TheHole. |
| IGNITION_HANDLER(ThrowSuperAlreadyCalledIfNotHole, InterpreterAssembler) { |
| Node* value = GetAccumulator(); |
| |
| Label throw_error(this, Label::kDeferred); |
| GotoIf(WordNotEqual(value, TheHoleConstant()), &throw_error); |
| Dispatch(); |
| |
| BIND(&throw_error); |
| { |
| CallRuntime(Runtime::kThrowSuperAlreadyCalledError, GetContext()); |
| // We shouldn't ever return from a throw. |
| Abort(AbortReason::kUnexpectedReturnFromThrow); |
| Unreachable(); |
| } |
| } |
| |
| // Debugger |
| // |
| // Call runtime to handle debugger statement. |
| IGNITION_HANDLER(Debugger, InterpreterAssembler) { |
| Node* context = GetContext(); |
| CallStub(CodeFactory::HandleDebuggerStatement(isolate()), context); |
| Dispatch(); |
| } |
| |
| // DebugBreak |
| // |
| // Call runtime to handle a debug break. |
| #define DEBUG_BREAK(Name, ...) \ |
| IGNITION_HANDLER(Name, InterpreterAssembler) { \ |
| Node* context = GetContext(); \ |
| Node* accumulator = GetAccumulator(); \ |
| Node* result_pair = \ |
| CallRuntime(Runtime::kDebugBreakOnBytecode, context, accumulator); \ |
| Node* return_value = Projection(0, result_pair); \ |
| Node* original_bytecode = SmiUntag(Projection(1, result_pair)); \ |
| MaybeDropFrames(context); \ |
| SetAccumulator(return_value); \ |
| DispatchToBytecode(original_bytecode, BytecodeOffset()); \ |
| } |
| DEBUG_BREAK_BYTECODE_LIST(DEBUG_BREAK) |
| #undef DEBUG_BREAK |
| |
| // IncBlockCounter <slot> |
| // |
| // Increment the execution count for the given slot. Used for block code |
| // coverage. |
| IGNITION_HANDLER(IncBlockCounter, InterpreterAssembler) { |
| Node* closure = LoadRegister(Register::function_closure()); |
| Node* coverage_array_slot = BytecodeOperandIdxSmi(0); |
| Node* context = GetContext(); |
| |
| CallBuiltin(Builtins::kIncBlockCounter, context, closure, |
| coverage_array_slot); |
| |
| Dispatch(); |
| } |
| |
| // ForInEnumerate <receiver> |
| // |
| // Enumerates the enumerable keys of the |receiver| and either returns the |
| // map of the |receiver| if it has a usable enum cache or a fixed array |
| // with the keys to enumerate in the accumulator. |
| IGNITION_HANDLER(ForInEnumerate, InterpreterAssembler) { |
| Node* receiver = LoadRegisterAtOperandIndex(0); |
| Node* context = GetContext(); |
| |
| Label if_empty(this), if_runtime(this, Label::kDeferred); |
| Node* receiver_map = CheckEnumCache(receiver, &if_empty, &if_runtime); |
| SetAccumulator(receiver_map); |
| Dispatch(); |
| |
| BIND(&if_empty); |
| { |
| Node* result = EmptyFixedArrayConstant(); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| BIND(&if_runtime); |
| { |
| Node* result = CallRuntime(Runtime::kForInEnumerate, context, receiver); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| |
| // ForInPrepare <cache_info_triple> |
| // |
| // Returns state for for..in loop execution based on the enumerator in |
| // the accumulator register, which is the result of calling ForInEnumerate |
| // on a JSReceiver object. |
| // The result is output in registers |cache_info_triple| to |
| // |cache_info_triple + 2|, with the registers holding cache_type, cache_array, |
| // and cache_length respectively. |
| IGNITION_HANDLER(ForInPrepare, InterpreterAssembler) { |
| Node* enumerator = GetAccumulator(); |
| Node* vector_index = BytecodeOperandIdx(1); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| |
| // The {enumerator} is either a Map or a FixedArray. |
| CSA_ASSERT(this, TaggedIsNotSmi(enumerator)); |
| |
| // Check if we're using an enum cache. |
| Label if_fast(this), if_slow(this); |
| Branch(IsMap(enumerator), &if_fast, &if_slow); |
| |
| BIND(&if_fast); |
| { |
| // Load the enumeration length and cache from the {enumerator}. |
| Node* enum_length = LoadMapEnumLength(enumerator); |
| CSA_ASSERT(this, WordNotEqual(enum_length, |
| IntPtrConstant(kInvalidEnumCacheSentinel))); |
| Node* descriptors = LoadMapDescriptors(enumerator); |
| Node* enum_cache = |
| LoadObjectField(descriptors, DescriptorArray::kEnumCacheOffset); |
| Node* enum_keys = LoadObjectField(enum_cache, EnumCache::kKeysOffset); |
| |
| // Check if we have enum indices available. |
| Node* enum_indices = LoadObjectField(enum_cache, EnumCache::kIndicesOffset); |
| Node* enum_indices_length = LoadAndUntagFixedArrayBaseLength(enum_indices); |
| Node* feedback = SelectSmiConstant( |
| IntPtrLessThanOrEqual(enum_length, enum_indices_length), |
| ForInFeedback::kEnumCacheKeysAndIndices, ForInFeedback::kEnumCacheKeys); |
| UpdateFeedback(feedback, maybe_feedback_vector, vector_index); |
| |
| // Construct the cache info triple. |
| Node* cache_type = enumerator; |
| Node* cache_array = enum_keys; |
| Node* cache_length = SmiTag(enum_length); |
| StoreRegisterTripleAtOperandIndex(cache_type, cache_array, cache_length, 0); |
| Dispatch(); |
| } |
| |
| BIND(&if_slow); |
| { |
| // The {enumerator} is a FixedArray with all the keys to iterate. |
| CSA_ASSERT(this, IsFixedArray(enumerator)); |
| |
| // Record the fact that we hit the for-in slow-path. |
| UpdateFeedback(SmiConstant(ForInFeedback::kAny), maybe_feedback_vector, |
| vector_index); |
| |
| // Construct the cache info triple. |
| Node* cache_type = enumerator; |
| Node* cache_array = enumerator; |
| Node* cache_length = LoadFixedArrayBaseLength(enumerator); |
| StoreRegisterTripleAtOperandIndex(cache_type, cache_array, cache_length, 0); |
| Dispatch(); |
| } |
| } |
| |
| // ForInNext <receiver> <index> <cache_info_pair> |
| // |
| // Returns the next enumerable property in the the accumulator. |
| IGNITION_HANDLER(ForInNext, InterpreterAssembler) { |
| Node* receiver = LoadRegisterAtOperandIndex(0); |
| Node* index = LoadRegisterAtOperandIndex(1); |
| Node* cache_type; |
| Node* cache_array; |
| std::tie(cache_type, cache_array) = LoadRegisterPairAtOperandIndex(2); |
| Node* vector_index = BytecodeOperandIdx(3); |
| Node* maybe_feedback_vector = LoadFeedbackVector(); |
| |
| // Load the next key from the enumeration array. |
| Node* key = LoadFixedArrayElement(CAST(cache_array), index, 0, |
| CodeStubAssembler::SMI_PARAMETERS); |
| |
| // Check if we can use the for-in fast path potentially using the enum cache. |
| Label if_fast(this), if_slow(this, Label::kDeferred); |
| Node* receiver_map = LoadMap(receiver); |
| Branch(WordEqual(receiver_map, cache_type), &if_fast, &if_slow); |
| BIND(&if_fast); |
| { |
| // Enum cache in use for {receiver}, the {key} is definitely valid. |
| SetAccumulator(key); |
| Dispatch(); |
| } |
| BIND(&if_slow); |
| { |
| // Record the fact that we hit the for-in slow-path. |
| UpdateFeedback(SmiConstant(ForInFeedback::kAny), maybe_feedback_vector, |
| vector_index); |
| |
| // Need to filter the {key} for the {receiver}. |
| Node* context = GetContext(); |
| Node* result = CallBuiltin(Builtins::kForInFilter, context, key, receiver); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| } |
| |
| // ForInContinue <index> <cache_length> |
| // |
| // Returns false if the end of the enumerable properties has been reached. |
| IGNITION_HANDLER(ForInContinue, InterpreterAssembler) { |
| Node* index = LoadRegisterAtOperandIndex(0); |
| Node* cache_length = LoadRegisterAtOperandIndex(1); |
| |
| // Check if {index} is at {cache_length} already. |
| Label if_true(this), if_false(this), end(this); |
| Branch(WordEqual(index, cache_length), &if_true, &if_false); |
| BIND(&if_true); |
| { |
| SetAccumulator(FalseConstant()); |
| Goto(&end); |
| } |
| BIND(&if_false); |
| { |
| SetAccumulator(TrueConstant()); |
| Goto(&end); |
| } |
| BIND(&end); |
| Dispatch(); |
| } |
| |
| // ForInStep <index> |
| // |
| // Increments the loop counter in register |index| and stores the result |
| // in the accumulator. |
| IGNITION_HANDLER(ForInStep, InterpreterAssembler) { |
| TNode<Smi> index = CAST(LoadRegisterAtOperandIndex(0)); |
| TNode<Smi> one = SmiConstant(1); |
| TNode<Smi> result = SmiAdd(index, one); |
| SetAccumulator(result); |
| Dispatch(); |
| } |
| |
| // Wide |
| // |
| // Prefix bytecode indicating next bytecode has wide (16-bit) operands. |
| IGNITION_HANDLER(Wide, InterpreterAssembler) { |
| DispatchWide(OperandScale::kDouble); |
| } |
| |
| // ExtraWide |
| // |
| // Prefix bytecode indicating next bytecode has extra-wide (32-bit) operands. |
| IGNITION_HANDLER(ExtraWide, InterpreterAssembler) { |
| DispatchWide(OperandScale::kQuadruple); |
| } |
| |
| // Illegal |
| // |
| // An invalid bytecode aborting execution if dispatched. |
| IGNITION_HANDLER(Illegal, InterpreterAssembler) { |
| Abort(AbortReason::kInvalidBytecode); |
| Unreachable(); |
| } |
| |
| // SuspendGenerator <generator> <first input register> <register count> |
| // <suspend_id> |
| // |
| // Stores the parameters and the register file in the generator. Also stores |
| // the current context, |suspend_id|, and the current bytecode offset |
| // (for debugging purposes) into the generator. Then, returns the value |
| // in the accumulator. |
| IGNITION_HANDLER(SuspendGenerator, InterpreterAssembler) { |
| Node* generator = LoadRegisterAtOperandIndex(0); |
| TNode<FixedArray> array = CAST(LoadObjectField( |
| generator, JSGeneratorObject::kParametersAndRegistersOffset)); |
| Node* closure = LoadRegister(Register::function_closure()); |
| Node* context = GetContext(); |
| RegListNodePair registers = GetRegisterListAtOperandIndex(1); |
| Node* suspend_id = BytecodeOperandUImmSmi(3); |
| |
| Node* shared = |
| LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset); |
| TNode<Int32T> formal_parameter_count = UncheckedCast<Int32T>( |
| LoadObjectField(shared, SharedFunctionInfo::kFormalParameterCountOffset, |
| MachineType::Uint16())); |
| |
| ExportParametersAndRegisterFile(array, registers, formal_parameter_count); |
| StoreObjectField(generator, JSGeneratorObject::kContextOffset, context); |
| StoreObjectField(generator, JSGeneratorObject::kContinuationOffset, |
| suspend_id); |
| |
| // Store the bytecode offset in the [input_or_debug_pos] field, to be used by |
| // the inspector. |
| Node* offset = SmiTag(BytecodeOffset()); |
| StoreObjectField(generator, JSGeneratorObject::kInputOrDebugPosOffset, |
| offset); |
| |
| UpdateInterruptBudgetOnReturn(); |
| Return(GetAccumulator()); |
| } |
| |
| // SwitchOnGeneratorState <generator> <table_start> <table_length> |
| // |
| // If |generator| is undefined, falls through. Otherwise, loads the |
| // generator's state (overwriting it with kGeneratorExecuting), sets the context |
| // to the generator's resume context, and performs state dispatch on the |
| // generator's state by looking up the generator state in a jump table in the |
| // constant pool, starting at |table_start|, and of length |table_length|. |
| IGNITION_HANDLER(SwitchOnGeneratorState, InterpreterAssembler) { |
| Node* generator = LoadRegisterAtOperandIndex(0); |
| |
| Label fallthrough(this); |
| GotoIf(WordEqual(generator, UndefinedConstant()), &fallthrough); |
| |
| Node* state = |
| LoadObjectField(generator, JSGeneratorObject::kContinuationOffset); |
| Node* new_state = SmiConstant(JSGeneratorObject::kGeneratorExecuting); |
| StoreObjectField(generator, JSGeneratorObject::kContinuationOffset, |
| new_state); |
| |
| Node* context = LoadObjectField(generator, JSGeneratorObject::kContextOffset); |
| SetContext(context); |
| |
| Node* table_start = BytecodeOperandIdx(1); |
| // TODO(leszeks): table_length is only used for a CSA_ASSERT, we don't |
| // actually need it otherwise. |
| Node* table_length = BytecodeOperandUImmWord(2); |
| |
| // The state must be a Smi. |
| CSA_ASSERT(this, TaggedIsSmi(state)); |
| |
| Node* case_value = SmiUntag(state); |
| |
| CSA_ASSERT(this, IntPtrGreaterThanOrEqual(case_value, IntPtrConstant(0))); |
| CSA_ASSERT(this, IntPtrLessThan(case_value, table_length)); |
| USE(table_length); |
| |
| Node* entry = IntPtrAdd(table_start, case_value); |
| Node* relative_jump = LoadAndUntagConstantPoolEntry(entry); |
| Jump(relative_jump); |
| |
| BIND(&fallthrough); |
| Dispatch(); |
| } |
| |
| // ResumeGenerator <generator> <first output register> <register count> |
| // |
| // Imports the register file stored in the generator and marks the generator |
| // state as executing. |
| IGNITION_HANDLER(ResumeGenerator, InterpreterAssembler) { |
| Node* generator = LoadRegisterAtOperandIndex(0); |
| Node* closure = LoadRegister(Register::function_closure()); |
| RegListNodePair registers = GetRegisterListAtOperandIndex(1); |
| |
| Node* shared = |
| LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset); |
| TNode<Int32T> formal_parameter_count = UncheckedCast<Int32T>( |
| LoadObjectField(shared, SharedFunctionInfo::kFormalParameterCountOffset, |
| MachineType::Uint16())); |
| |
| ImportRegisterFile( |
| CAST(LoadObjectField(generator, |
| JSGeneratorObject::kParametersAndRegistersOffset)), |
| registers, formal_parameter_count); |
| |
| // Return the generator's input_or_debug_pos in the accumulator. |
| SetAccumulator( |
| LoadObjectField(generator, JSGeneratorObject::kInputOrDebugPosOffset)); |
| |
| Dispatch(); |
| } |
| |
| } // namespace |
| |
| Handle<Code> GenerateBytecodeHandler(Isolate* isolate, Bytecode bytecode, |
| OperandScale operand_scale, |
| int builtin_index, |
| const AssemblerOptions& options) { |
| Zone zone(isolate->allocator(), ZONE_NAME); |
| compiler::CodeAssemblerState state( |
| isolate, &zone, InterpreterDispatchDescriptor{}, Code::BYTECODE_HANDLER, |
| Bytecodes::ToString(bytecode), |
| FLAG_untrusted_code_mitigations |
| ? PoisoningMitigationLevel::kPoisonCriticalOnly |
| : PoisoningMitigationLevel::kDontPoison, |
| builtin_index); |
| |
| switch (bytecode) { |
| #define CALL_GENERATOR(Name, ...) \ |
| case Bytecode::k##Name: \ |
| Name##Assembler::Generate(&state, operand_scale); \ |
| break; |
| BYTECODE_LIST(CALL_GENERATOR); |
| #undef CALL_GENERATOR |
| } |
| |
| Handle<Code> code = compiler::CodeAssembler::GenerateCode(&state, options); |
| |
| #ifdef ENABLE_DISASSEMBLER |
| if (FLAG_trace_ignition_codegen) { |
| StdoutStream os; |
| code->Disassemble(Bytecodes::ToString(bytecode), os); |
| os << std::flush; |
| } |
| #endif // ENABLE_DISASSEMBLER |
| |
| return code; |
| } |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |