| // 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/wasm/baseline/liftoff-compiler.h" |
| |
| #include "src/base/enum-set.h" |
| #include "src/base/optional.h" |
| #include "src/codegen/assembler-inl.h" |
| // TODO(clemensb): Remove dependences on compiler stuff. |
| #include "src/codegen/external-reference.h" |
| #include "src/codegen/interface-descriptors-inl.h" |
| #include "src/codegen/machine-type.h" |
| #include "src/codegen/macro-assembler-inl.h" |
| #include "src/compiler/linkage.h" |
| #include "src/compiler/wasm-compiler.h" |
| #include "src/logging/counters.h" |
| #include "src/logging/log.h" |
| #include "src/objects/smi.h" |
| #include "src/tracing/trace-event.h" |
| #include "src/utils/ostreams.h" |
| #include "src/utils/utils.h" |
| #include "src/wasm/assembler-buffer-cache.h" |
| #include "src/wasm/baseline/liftoff-assembler.h" |
| #include "src/wasm/baseline/liftoff-register.h" |
| #include "src/wasm/function-body-decoder-impl.h" |
| #include "src/wasm/function-compiler.h" |
| #include "src/wasm/memory-tracing.h" |
| #include "src/wasm/object-access.h" |
| #include "src/wasm/simd-shuffle.h" |
| #include "src/wasm/wasm-debug.h" |
| #include "src/wasm/wasm-engine.h" |
| #include "src/wasm/wasm-linkage.h" |
| #include "src/wasm/wasm-objects.h" |
| #include "src/wasm/wasm-opcodes-inl.h" |
| |
| namespace v8::internal::wasm { |
| |
| constexpr auto kRegister = LiftoffAssembler::VarState::kRegister; |
| constexpr auto kIntConst = LiftoffAssembler::VarState::kIntConst; |
| constexpr auto kStack = LiftoffAssembler::VarState::kStack; |
| |
| namespace { |
| |
| #define __ asm_. |
| |
| // It's important that we don't modify the LiftoffAssembler's cache state |
| // in conditionally-executed code paths. Creating these witnesses helps |
| // enforce that (using DCHECKs in the cache state). |
| // Conditional jump instructions require a witness to have been created (to |
| // make sure we don't forget); the witness should stay alive until the label |
| // is bound where regular control flow resumes. This implies that when we're |
| // jumping to a trap, the live range of the witness isn't important. |
| #define FREEZE_STATE(witness_name) FreezeCacheState witness_name(asm_) |
| |
| #define TRACE(...) \ |
| do { \ |
| if (v8_flags.trace_liftoff) PrintF("[liftoff] " __VA_ARGS__); \ |
| } while (false) |
| |
| #define WASM_INSTANCE_OBJECT_FIELD_OFFSET(name) \ |
| ObjectAccess::ToTagged(WasmInstanceObject::k##name##Offset) |
| |
| template <int expected_size, int actual_size> |
| struct assert_field_size { |
| static_assert(expected_size == actual_size, |
| "field in WasmInstance does not have the expected size"); |
| static constexpr int size = actual_size; |
| }; |
| |
| #define WASM_INSTANCE_OBJECT_FIELD_SIZE(name) \ |
| FIELD_SIZE(WasmInstanceObject::k##name##Offset) |
| |
| #define LOAD_INSTANCE_FIELD(dst, name, load_size, pinned) \ |
| __ LoadFromInstance(dst, LoadInstanceIntoRegister(pinned, dst), \ |
| WASM_INSTANCE_OBJECT_FIELD_OFFSET(name), \ |
| assert_field_size<WASM_INSTANCE_OBJECT_FIELD_SIZE(name), \ |
| load_size>::size); |
| |
| #define LOAD_TAGGED_PTR_INSTANCE_FIELD(dst, name, pinned) \ |
| static_assert(WASM_INSTANCE_OBJECT_FIELD_SIZE(name) == kTaggedSize, \ |
| "field in WasmInstance does not have the expected size"); \ |
| __ LoadTaggedPointerFromInstance(dst, LoadInstanceIntoRegister(pinned, dst), \ |
| WASM_INSTANCE_OBJECT_FIELD_OFFSET(name)); |
| |
| #ifdef V8_CODE_COMMENTS |
| #define CODE_COMMENT(str) __ RecordComment(str) |
| #define SCOPED_CODE_COMMENT(str) \ |
| AssemblerBase::CodeComment scoped_comment_##__LINE__(&asm_, str) |
| #else |
| #define CODE_COMMENT(str) ((void)0) |
| #define SCOPED_CODE_COMMENT(str) ((void)0) |
| #endif |
| |
| constexpr LoadType::LoadTypeValue kPointerLoadType = |
| kSystemPointerSize == 8 ? LoadType::kI64Load : LoadType::kI32Load; |
| |
| constexpr ValueKind kIntPtrKind = LiftoffAssembler::kIntPtrKind; |
| constexpr ValueKind kSmiKind = LiftoffAssembler::kSmiKind; |
| |
| // Used to construct fixed-size signatures: MakeSig::Returns(...).Params(...); |
| using MakeSig = FixedSizeSignature<ValueKind>; |
| |
| #if V8_TARGET_ARCH_ARM64 |
| // On ARM64, the Assembler keeps track of pointers to Labels to resolve |
| // branches to distant targets. Moving labels would confuse the Assembler, |
| // thus store the label in the Zone. |
| class MovableLabel { |
| public: |
| MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(MovableLabel); |
| explicit MovableLabel(Zone* zone) : label_(zone->New<Label>()) {} |
| |
| Label* get() { return label_; } |
| |
| private: |
| Label* label_; |
| }; |
| #else |
| // On all other platforms, just store the Label directly. |
| class MovableLabel { |
| public: |
| MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(MovableLabel); |
| explicit MovableLabel(Zone*) {} |
| |
| Label* get() { return &label_; } |
| |
| private: |
| Label label_; |
| }; |
| #endif |
| |
| compiler::CallDescriptor* GetLoweredCallDescriptor( |
| Zone* zone, compiler::CallDescriptor* call_desc) { |
| return kSystemPointerSize == 4 |
| ? compiler::GetI32WasmCallDescriptor(zone, call_desc) |
| : call_desc; |
| } |
| |
| constexpr Condition GetCompareCondition(WasmOpcode opcode) { |
| switch (opcode) { |
| case kExprI32Eq: |
| return kEqual; |
| case kExprI32Ne: |
| return kNotEqual; |
| case kExprI32LtS: |
| return kLessThan; |
| case kExprI32LtU: |
| return kUnsignedLessThan; |
| case kExprI32GtS: |
| return kGreaterThan; |
| case kExprI32GtU: |
| return kUnsignedGreaterThan; |
| case kExprI32LeS: |
| return kLessThanEqual; |
| case kExprI32LeU: |
| return kUnsignedLessThanEqual; |
| case kExprI32GeS: |
| return kGreaterThanEqual; |
| case kExprI32GeU: |
| return kUnsignedGreaterThanEqual; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| // Builds a {DebugSideTable}. |
| class DebugSideTableBuilder { |
| using Entry = DebugSideTable::Entry; |
| using Value = Entry::Value; |
| |
| public: |
| enum AssumeSpilling { |
| // All register values will be spilled before the pc covered by the debug |
| // side table entry. Register slots will be marked as stack slots in the |
| // generated debug side table entry. |
| kAssumeSpilling, |
| // Register slots will be written out as they are. |
| kAllowRegisters, |
| // Register slots cannot appear since we already spilled. |
| kDidSpill |
| }; |
| |
| class EntryBuilder { |
| public: |
| explicit EntryBuilder(int pc_offset, int stack_height, |
| std::vector<Value> changed_values) |
| : pc_offset_(pc_offset), |
| stack_height_(stack_height), |
| changed_values_(std::move(changed_values)) {} |
| |
| Entry ToTableEntry() { |
| return Entry{pc_offset_, stack_height_, std::move(changed_values_)}; |
| } |
| |
| void MinimizeBasedOnPreviousStack(const std::vector<Value>& last_values) { |
| auto dst = changed_values_.begin(); |
| auto end = changed_values_.end(); |
| for (auto src = dst; src != end; ++src) { |
| if (src->index < static_cast<int>(last_values.size()) && |
| *src == last_values[src->index]) { |
| continue; |
| } |
| if (dst != src) *dst = *src; |
| ++dst; |
| } |
| changed_values_.erase(dst, end); |
| } |
| |
| int pc_offset() const { return pc_offset_; } |
| void set_pc_offset(int new_pc_offset) { pc_offset_ = new_pc_offset; } |
| |
| private: |
| int pc_offset_; |
| int stack_height_; |
| std::vector<Value> changed_values_; |
| }; |
| |
| // Adds a new entry in regular code. |
| void NewEntry(int pc_offset, |
| base::Vector<DebugSideTable::Entry::Value> values) { |
| entries_.emplace_back(pc_offset, static_cast<int>(values.size()), |
| GetChangedStackValues(last_values_, values)); |
| } |
| |
| // Adds a new entry for OOL code, and returns a pointer to a builder for |
| // modifying that entry. |
| EntryBuilder* NewOOLEntry(base::Vector<DebugSideTable::Entry::Value> values) { |
| constexpr int kNoPcOffsetYet = -1; |
| ool_entries_.emplace_back(kNoPcOffsetYet, static_cast<int>(values.size()), |
| GetChangedStackValues(last_ool_values_, values)); |
| return &ool_entries_.back(); |
| } |
| |
| void SetNumLocals(int num_locals) { |
| DCHECK_EQ(-1, num_locals_); |
| DCHECK_LE(0, num_locals); |
| num_locals_ = num_locals; |
| } |
| |
| std::unique_ptr<DebugSideTable> GenerateDebugSideTable() { |
| DCHECK_LE(0, num_locals_); |
| |
| // Connect {entries_} and {ool_entries_} by removing redundant stack |
| // information from the first {ool_entries_} entry (based on |
| // {last_values_}). |
| if (!entries_.empty() && !ool_entries_.empty()) { |
| ool_entries_.front().MinimizeBasedOnPreviousStack(last_values_); |
| } |
| |
| std::vector<Entry> entries; |
| entries.reserve(entries_.size() + ool_entries_.size()); |
| for (auto& entry : entries_) entries.push_back(entry.ToTableEntry()); |
| for (auto& entry : ool_entries_) entries.push_back(entry.ToTableEntry()); |
| DCHECK(std::is_sorted( |
| entries.begin(), entries.end(), |
| [](Entry& a, Entry& b) { return a.pc_offset() < b.pc_offset(); })); |
| return std::make_unique<DebugSideTable>(num_locals_, std::move(entries)); |
| } |
| |
| private: |
| static std::vector<Value> GetChangedStackValues( |
| std::vector<Value>& last_values, |
| base::Vector<DebugSideTable::Entry::Value> values) { |
| std::vector<Value> changed_values; |
| int old_stack_size = static_cast<int>(last_values.size()); |
| last_values.resize(values.size()); |
| |
| int index = 0; |
| for (const auto& value : values) { |
| if (index >= old_stack_size || last_values[index] != value) { |
| changed_values.push_back(value); |
| last_values[index] = value; |
| } |
| ++index; |
| } |
| return changed_values; |
| } |
| |
| int num_locals_ = -1; |
| // Keep a snapshot of the stack of the last entry, to generate a delta to the |
| // next entry. |
| std::vector<Value> last_values_; |
| std::vector<EntryBuilder> entries_; |
| // Keep OOL code entries separate so we can do proper delta-encoding (more |
| // entries might be added between the existing {entries_} and the |
| // {ool_entries_}). Store the entries in a list so the pointer is not |
| // invalidated by adding more entries. |
| std::vector<Value> last_ool_values_; |
| std::list<EntryBuilder> ool_entries_; |
| }; |
| |
| void CheckBailoutAllowed(LiftoffBailoutReason reason, const char* detail, |
| const CompilationEnv* env) { |
| // Decode errors are ok. |
| if (reason == kDecodeError) return; |
| |
| // --liftoff-only ensures that tests actually exercise the Liftoff path |
| // without bailing out. We also fail for missing CPU support, to avoid |
| // running any TurboFan code under --liftoff-only. |
| if (v8_flags.liftoff_only) { |
| FATAL("--liftoff-only: treating bailout as fatal error. Cause: %s", detail); |
| } |
| |
| // Missing CPU features are generally OK, except with --liftoff-only. |
| if (reason == kMissingCPUFeature) return; |
| |
| // If --enable-testing-opcode-in-wasm is set, we are expected to bailout with |
| // "testing opcode". |
| if (v8_flags.enable_testing_opcode_in_wasm && |
| strcmp(detail, "testing opcode") == 0) { |
| return; |
| } |
| |
| // Some externally maintained architectures don't fully implement Liftoff yet. |
| #if V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_S390X || V8_TARGET_ARCH_PPC || \ |
| V8_TARGET_ARCH_PPC64 || V8_TARGET_ARCH_LOONG64 |
| return; |
| #endif |
| |
| #if V8_TARGET_ARCH_ARM |
| // Allow bailout for missing ARMv7 support. |
| if (!CpuFeatures::IsSupported(ARMv7) && reason == kUnsupportedArchitecture) { |
| return; |
| } |
| #endif |
| |
| #define LIST_FEATURE(name, ...) kFeature_##name, |
| constexpr WasmFeatures kExperimentalFeatures{ |
| FOREACH_WASM_EXPERIMENTAL_FEATURE_FLAG(LIST_FEATURE)}; |
| #undef LIST_FEATURE |
| |
| // Bailout is allowed if any experimental feature is enabled. |
| if (env->enabled_features.contains_any(kExperimentalFeatures)) return; |
| |
| // Otherwise, bailout is not allowed. |
| FATAL("Liftoff bailout should not happen. Cause: %s\n", detail); |
| } |
| |
| class LiftoffCompiler { |
| public: |
| using ValidationTag = Decoder::NoValidationTag; |
| using Value = ValueBase<ValidationTag>; |
| |
| struct ElseState { |
| explicit ElseState(Zone* zone) : label(zone), state(zone) {} |
| MovableLabel label; |
| LiftoffAssembler::CacheState state; |
| }; |
| |
| struct TryInfo { |
| explicit TryInfo(Zone* zone) : catch_state(zone) {} |
| LiftoffAssembler::CacheState catch_state; |
| Label catch_label; |
| bool catch_reached = false; |
| bool in_handler = false; |
| }; |
| |
| struct Control : public ControlBase<Value, ValidationTag> { |
| ElseState* else_state = nullptr; |
| LiftoffAssembler::CacheState label_state; |
| MovableLabel label; |
| TryInfo* try_info = nullptr; |
| // Number of exceptions on the stack below this control. |
| int num_exceptions = 0; |
| |
| MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Control); |
| |
| template <typename... Args> |
| explicit Control(Zone* zone, Args&&... args) V8_NOEXCEPT |
| : ControlBase(zone, std::forward<Args>(args)...), |
| label_state(zone), |
| label(zone) {} |
| }; |
| |
| using FullDecoder = WasmFullDecoder<ValidationTag, LiftoffCompiler>; |
| using ValueKindSig = LiftoffAssembler::ValueKindSig; |
| |
| class MostlySmallValueKindSig : public Signature<ValueKind> { |
| public: |
| MostlySmallValueKindSig(Zone* zone, const FunctionSig* sig) |
| : Signature<ValueKind>(sig->return_count(), sig->parameter_count(), |
| MakeKinds(inline_storage_, zone, sig)) {} |
| |
| private: |
| static constexpr size_t kInlineStorage = 8; |
| |
| static ValueKind* MakeKinds(ValueKind* storage, Zone* zone, |
| const FunctionSig* sig) { |
| const size_t size = sig->parameter_count() + sig->return_count(); |
| if (V8_UNLIKELY(size > kInlineStorage)) { |
| storage = zone->NewArray<ValueKind>(size); |
| } |
| std::transform(sig->all().begin(), sig->all().end(), storage, |
| [](ValueType type) { return type.kind(); }); |
| return storage; |
| } |
| |
| ValueKind inline_storage_[kInlineStorage]; |
| }; |
| |
| // For debugging, we need to spill registers before a trap or a stack check to |
| // be able to inspect them. |
| struct SpilledRegistersForInspection : public ZoneObject { |
| struct Entry { |
| int offset; |
| LiftoffRegister reg; |
| ValueKind kind; |
| }; |
| ZoneVector<Entry> entries; |
| |
| explicit SpilledRegistersForInspection(Zone* zone) : entries(zone) {} |
| }; |
| |
| struct OutOfLineSafepointInfo { |
| ZoneVector<int> slots; |
| LiftoffRegList spills; |
| |
| explicit OutOfLineSafepointInfo(Zone* zone) : slots(zone) {} |
| }; |
| |
| struct OutOfLineCode { |
| MovableLabel label; |
| MovableLabel continuation; |
| WasmCode::RuntimeStubId stub; |
| WasmCodePosition position; |
| LiftoffRegList regs_to_save; |
| Register cached_instance; |
| OutOfLineSafepointInfo* safepoint_info; |
| uint32_t pc; // for trap handler. |
| // These two pointers will only be used for debug code: |
| SpilledRegistersForInspection* spilled_registers; |
| DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder; |
| |
| // Named constructors: |
| static OutOfLineCode Trap( |
| Zone* zone, WasmCode::RuntimeStubId s, WasmCodePosition pos, |
| SpilledRegistersForInspection* spilled_registers, |
| OutOfLineSafepointInfo* safepoint_info, uint32_t pc, |
| DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) { |
| DCHECK_LT(0, pos); |
| return { |
| MovableLabel{zone}, // label |
| MovableLabel{zone}, // continuation |
| s, // stub |
| pos, // position |
| {}, // regs_to_save |
| no_reg, // cached_instance |
| safepoint_info, // safepoint_info |
| pc, // pc |
| spilled_registers, // spilled_registers |
| debug_sidetable_entry_builder // debug_side_table_entry_builder |
| }; |
| } |
| static OutOfLineCode StackCheck( |
| Zone* zone, WasmCodePosition pos, LiftoffRegList regs_to_save, |
| Register cached_instance, SpilledRegistersForInspection* spilled_regs, |
| OutOfLineSafepointInfo* safepoint_info, |
| DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) { |
| return { |
| MovableLabel{zone}, // label |
| MovableLabel{zone}, // continuation |
| WasmCode::kWasmStackGuard, // stub |
| pos, // position |
| regs_to_save, // regs_to_save |
| cached_instance, // cached_instance |
| safepoint_info, // safepoint_info |
| 0, // pc |
| spilled_regs, // spilled_registers |
| debug_sidetable_entry_builder // debug_side_table_entry_builder |
| }; |
| } |
| static OutOfLineCode TierupCheck( |
| Zone* zone, WasmCodePosition pos, LiftoffRegList regs_to_save, |
| Register cached_instance, SpilledRegistersForInspection* spilled_regs, |
| OutOfLineSafepointInfo* safepoint_info, |
| DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) { |
| return { |
| MovableLabel{zone}, // label |
| MovableLabel{zone}, // continuation, |
| WasmCode::kWasmTriggerTierUp, // stub |
| pos, // position |
| regs_to_save, // regs_to_save |
| cached_instance, // cached_instance |
| safepoint_info, // safepoint_info |
| 0, // pc |
| spilled_regs, // spilled_registers |
| debug_sidetable_entry_builder // debug_side_table_entry_builder |
| }; |
| } |
| }; |
| |
| LiftoffCompiler(compiler::CallDescriptor* call_descriptor, |
| CompilationEnv* env, Zone* zone, |
| std::unique_ptr<AssemblerBuffer> buffer, |
| DebugSideTableBuilder* debug_sidetable_builder, |
| const LiftoffOptions& options) |
| : asm_(zone, std::move(buffer)), |
| descriptor_(GetLoweredCallDescriptor(zone, call_descriptor)), |
| env_(env), |
| debug_sidetable_builder_(debug_sidetable_builder), |
| for_debugging_(options.for_debugging), |
| func_index_(options.func_index), |
| out_of_line_code_(zone), |
| source_position_table_builder_(zone), |
| protected_instructions_(zone), |
| zone_(zone), |
| safepoint_table_builder_(zone_), |
| next_breakpoint_ptr_(options.breakpoints.begin()), |
| next_breakpoint_end_(options.breakpoints.end()), |
| dead_breakpoint_(options.dead_breakpoint), |
| handlers_(zone), |
| max_steps_(options.max_steps), |
| nondeterminism_(options.nondeterminism) { |
| // We often see huge numbers of traps per function, so pre-reserve some |
| // space in that vector. 128 entries is enough for ~94% of functions on |
| // modern modules, as of 2022-06-03. |
| out_of_line_code_.reserve(128); |
| |
| DCHECK(options.is_initialized()); |
| // If there are no breakpoints, both pointers should be nullptr. |
| DCHECK_IMPLIES( |
| next_breakpoint_ptr_ == next_breakpoint_end_, |
| next_breakpoint_ptr_ == nullptr && next_breakpoint_end_ == nullptr); |
| } |
| |
| bool did_bailout() const { return bailout_reason_ != kSuccess; } |
| LiftoffBailoutReason bailout_reason() const { return bailout_reason_; } |
| |
| void GetCode(CodeDesc* desc) { |
| asm_.GetCode(nullptr, desc, &safepoint_table_builder_, |
| handler_table_offset_); |
| } |
| |
| std::unique_ptr<AssemblerBuffer> ReleaseBuffer() { |
| return asm_.ReleaseBuffer(); |
| } |
| |
| base::OwnedVector<uint8_t> GetSourcePositionTable() { |
| return source_position_table_builder_.ToSourcePositionTableVector(); |
| } |
| |
| base::OwnedVector<uint8_t> GetProtectedInstructionsData() const { |
| return base::OwnedVector<uint8_t>::Of(base::Vector<const uint8_t>::cast( |
| base::VectorOf(protected_instructions_))); |
| } |
| |
| uint32_t GetTotalFrameSlotCountForGC() const { |
| return __ GetTotalFrameSlotCountForGC(); |
| } |
| |
| void unsupported(FullDecoder* decoder, LiftoffBailoutReason reason, |
| const char* detail) { |
| DCHECK_NE(kSuccess, reason); |
| if (did_bailout()) return; |
| bailout_reason_ = reason; |
| TRACE("unsupported: %s\n", detail); |
| decoder->errorf(decoder->pc_offset(), "unsupported liftoff operation: %s", |
| detail); |
| UnuseLabels(decoder); |
| CheckBailoutAllowed(reason, detail, env_); |
| } |
| |
| bool DidAssemblerBailout(FullDecoder* decoder) { |
| if (decoder->failed() || !__ did_bailout()) return false; |
| unsupported(decoder, __ bailout_reason(), __ bailout_detail()); |
| return true; |
| } |
| |
| V8_INLINE bool CheckSupportedType(FullDecoder* decoder, ValueKind kind, |
| const char* context) { |
| if (V8_LIKELY(supported_types_.contains(kind))) return true; |
| return MaybeBailoutForUnsupportedType(decoder, kind, context); |
| } |
| |
| V8_NOINLINE bool MaybeBailoutForUnsupportedType(FullDecoder* decoder, |
| ValueKind kind, |
| const char* context) { |
| DCHECK(!supported_types_.contains(kind)); |
| |
| // Lazily update {supported_types_}; then check again. |
| if (CpuFeatures::SupportsWasmSimd128()) supported_types_.Add(kS128); |
| if (supported_types_.contains(kind)) return true; |
| |
| LiftoffBailoutReason bailout_reason; |
| switch (kind) { |
| case kS128: |
| bailout_reason = kSimd; |
| break; |
| case kRef: |
| case kRefNull: |
| case kRtt: |
| case kI8: |
| case kI16: |
| bailout_reason = kGC; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| base::EmbeddedVector<char, 128> buffer; |
| SNPrintF(buffer, "%s %s", name(kind), context); |
| unsupported(decoder, bailout_reason, buffer.begin()); |
| return false; |
| } |
| |
| void UnuseLabels(FullDecoder* decoder) { |
| #ifdef DEBUG |
| auto Unuse = [](Label* label) { |
| label->Unuse(); |
| label->UnuseNear(); |
| }; |
| // Unuse all labels now, otherwise their destructor will fire a DCHECK error |
| // if they where referenced before. |
| uint32_t control_depth = decoder ? decoder->control_depth() : 0; |
| for (uint32_t i = 0; i < control_depth; ++i) { |
| Control* c = decoder->control_at(i); |
| Unuse(c->label.get()); |
| if (c->else_state) Unuse(c->else_state->label.get()); |
| if (c->try_info != nullptr) Unuse(&c->try_info->catch_label); |
| } |
| for (auto& ool : out_of_line_code_) Unuse(ool.label.get()); |
| #endif |
| } |
| |
| void StartFunction(FullDecoder* decoder) { |
| if (v8_flags.trace_liftoff && !v8_flags.trace_wasm_decoder) { |
| StdoutStream{} << "hint: add --trace-wasm-decoder to also see the wasm " |
| "instructions being decoded\n"; |
| } |
| int num_locals = decoder->num_locals(); |
| __ set_num_locals(num_locals); |
| for (int i = 0; i < num_locals; ++i) { |
| ValueKind kind = decoder->local_type(i).kind(); |
| __ set_local_kind(i, kind); |
| } |
| } |
| |
| class ParameterProcessor { |
| public: |
| ParameterProcessor(LiftoffCompiler* compiler, uint32_t num_params) |
| : compiler_(compiler), num_params_(num_params) {} |
| |
| void Process() { |
| // First pass: collect parameter registers. |
| while (NextParam()) { |
| MaybeCollectRegister(); |
| if (needs_gp_pair_) { |
| NextLocation(); |
| MaybeCollectRegister(); |
| } |
| } |
| // Second pass: allocate parameters. |
| param_idx_ = 0; |
| input_idx_ = kFirstInputIdx; |
| while (NextParam()) { |
| LiftoffRegister reg = LoadToReg(param_regs_); |
| if (needs_gp_pair_) { |
| NextLocation(); |
| LiftoffRegister reg2 = LoadToReg(param_regs_ | LiftoffRegList{reg}); |
| reg = LiftoffRegister::ForPair(reg.gp(), reg2.gp()); |
| } |
| compiler_->asm_.PushRegister(kind_, reg); |
| } |
| } |
| |
| private: |
| bool NextParam() { |
| if (param_idx_ >= num_params_) { |
| DCHECK_EQ(input_idx_, compiler_->descriptor_->InputCount()); |
| return false; |
| } |
| kind_ = compiler_->asm_.local_kind(param_idx_++); |
| needs_gp_pair_ = needs_gp_reg_pair(kind_); |
| reg_kind_ = needs_gp_pair_ ? kI32 : kind_; |
| rc_ = reg_class_for(reg_kind_); |
| NextLocation(); |
| return true; |
| } |
| |
| void NextLocation() { |
| location_ = compiler_->descriptor_->GetInputLocation(input_idx_++); |
| } |
| |
| LiftoffRegister CurrentRegister() { |
| DCHECK(!location_.IsAnyRegister()); |
| return LiftoffRegister::from_external_code(rc_, reg_kind_, |
| location_.AsRegister()); |
| } |
| |
| void MaybeCollectRegister() { |
| if (!location_.IsRegister()) return; |
| DCHECK(!param_regs_.has(CurrentRegister())); |
| param_regs_.set(CurrentRegister()); |
| } |
| |
| LiftoffRegister LoadToReg(LiftoffRegList pinned) { |
| if (location_.IsRegister()) { |
| LiftoffRegister reg = CurrentRegister(); |
| DCHECK(compiler_->asm_.cache_state()->is_free(reg)); |
| // Unpin the register, to avoid depending on the set of allocatable |
| // registers being larger than the set of parameter registers. |
| param_regs_.clear(reg); |
| return reg; |
| } |
| DCHECK(location_.IsCallerFrameSlot()); |
| LiftoffRegister reg = compiler_->asm_.GetUnusedRegister(rc_, pinned); |
| compiler_->asm_.LoadCallerFrameSlot(reg, -location_.AsCallerFrameSlot(), |
| reg_kind_); |
| return reg; |
| } |
| |
| // Input 0 is the code target, 1 is the instance. |
| static constexpr uint32_t kFirstInputIdx = 2; |
| |
| LiftoffCompiler* compiler_; |
| const uint32_t num_params_; |
| uint32_t param_idx_{0}; |
| uint32_t input_idx_{kFirstInputIdx}; |
| ValueKind kind_; |
| bool needs_gp_pair_; |
| ValueKind reg_kind_; |
| RegClass rc_; |
| compiler::LinkageLocation location_{ |
| compiler::LinkageLocation::ForAnyRegister()}; |
| LiftoffRegList param_regs_; |
| }; |
| |
| void StackCheck(FullDecoder* decoder, WasmCodePosition position) { |
| CODE_COMMENT("stack check"); |
| if (!v8_flags.wasm_stack_checks || !env_->runtime_exception_support) return; |
| |
| // Loading the limit address can change the stack state, hence do this |
| // before storing information about registers. |
| Register limit_address = __ GetUnusedRegister(kGpReg, {}).gp(); |
| LOAD_INSTANCE_FIELD(limit_address, StackLimitAddress, kSystemPointerSize, |
| {}); |
| |
| LiftoffRegList regs_to_save = __ cache_state()->used_registers; |
| // The cached instance will be reloaded separately. |
| if (__ cache_state()->cached_instance != no_reg) { |
| DCHECK(regs_to_save.has(__ cache_state()->cached_instance)); |
| regs_to_save.clear(__ cache_state()->cached_instance); |
| } |
| SpilledRegistersForInspection* spilled_regs = nullptr; |
| |
| OutOfLineSafepointInfo* safepoint_info = |
| zone_->New<OutOfLineSafepointInfo>(zone_); |
| __ cache_state()->GetTaggedSlotsForOOLCode( |
| &safepoint_info->slots, &safepoint_info->spills, |
| for_debugging_ |
| ? LiftoffAssembler::CacheState::SpillLocation::kStackSlots |
| : LiftoffAssembler::CacheState::SpillLocation::kTopOfStack); |
| if (V8_UNLIKELY(for_debugging_)) { |
| // When debugging, we do not just push all registers to the stack, but we |
| // spill them to their proper stack locations such that we can inspect |
| // them. |
| // The only exception is the cached memory start, which we just push |
| // before the stack check and pop afterwards. |
| regs_to_save = {}; |
| if (__ cache_state()->cached_mem_start != no_reg) { |
| regs_to_save.set(__ cache_state()->cached_mem_start); |
| } |
| spilled_regs = GetSpilledRegistersForInspection(); |
| } |
| out_of_line_code_.push_back(OutOfLineCode::StackCheck( |
| zone_, position, regs_to_save, __ cache_state()->cached_instance, |
| spilled_regs, safepoint_info, RegisterOOLDebugSideTableEntry(decoder))); |
| OutOfLineCode& ool = out_of_line_code_.back(); |
| __ StackCheck(ool.label.get(), limit_address); |
| __ bind(ool.continuation.get()); |
| } |
| |
| void TierupCheck(FullDecoder* decoder, WasmCodePosition position, |
| int budget_used, Register tmp1, Register tmp2) { |
| // We should always decrement the budget, and we don't expect integer |
| // overflows in the budget calculation. |
| DCHECK_LE(1, budget_used); |
| |
| if (for_debugging_ != kNotForDebugging) return; |
| CODE_COMMENT("tierup check"); |
| // We never want to blow the entire budget at once. |
| const int kMax = v8_flags.wasm_tiering_budget / 4; |
| if (budget_used > kMax) budget_used = kMax; |
| |
| LiftoffRegister budget_reg(tmp2); |
| // Be careful not to cause caching of the instance. |
| Register instance = __ cache_state()->cached_instance; |
| if (instance == no_reg) { |
| instance = tmp1; |
| __ LoadInstanceFromFrame(instance); |
| } |
| constexpr int kArraySize = kSystemPointerSize; |
| constexpr int kArrayOffset = |
| WASM_INSTANCE_OBJECT_FIELD_OFFSET(TieringBudgetArray); |
| static_assert(WASM_INSTANCE_OBJECT_FIELD_SIZE(TieringBudgetArray) == |
| kArraySize); |
| Register array_reg = tmp1; // Overwriting {instance}. |
| __ LoadFromInstance(array_reg, instance, kArrayOffset, kArraySize); |
| uint32_t offset = |
| kInt32Size * declared_function_index(env_->module, func_index_); |
| #if V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_ARM64 |
| // Platforms where both this load and the later store would have to |
| // explicitly add the offset can save code size by performing the addition |
| // only once. |
| __ emit_ptrsize_addi(array_reg, array_reg, offset); |
| offset = 0; |
| #endif |
| __ Load(budget_reg, array_reg, no_reg, offset, LoadType::kI32Load, {}); |
| LiftoffRegList regs_to_save = __ cache_state()->used_registers; |
| // The cached instance will be reloaded separately. |
| if (__ cache_state()->cached_instance != no_reg) { |
| DCHECK(regs_to_save.has(__ cache_state()->cached_instance)); |
| regs_to_save.clear(__ cache_state()->cached_instance); |
| } |
| SpilledRegistersForInspection* spilled_regs = nullptr; |
| |
| OutOfLineSafepointInfo* safepoint_info = |
| zone_->New<OutOfLineSafepointInfo>(zone_); |
| __ cache_state()->GetTaggedSlotsForOOLCode( |
| &safepoint_info->slots, &safepoint_info->spills, |
| LiftoffAssembler::CacheState::SpillLocation::kTopOfStack); |
| out_of_line_code_.push_back(OutOfLineCode::TierupCheck( |
| zone_, position, regs_to_save, __ cache_state()->cached_instance, |
| spilled_regs, safepoint_info, RegisterOOLDebugSideTableEntry(decoder))); |
| OutOfLineCode& ool = out_of_line_code_.back(); |
| FREEZE_STATE(trapping); |
| __ emit_i32_subi_jump_negative(budget_reg.gp(), budget_used, |
| ool.label.get(), trapping); |
| __ Store(array_reg, no_reg, offset, budget_reg, StoreType::kI32Store, {}); |
| __ bind(ool.continuation.get()); |
| } |
| |
| bool SpillLocalsInitially(FullDecoder* decoder, uint32_t num_params) { |
| int actual_locals = __ num_locals() - num_params; |
| DCHECK_LE(0, actual_locals); |
| constexpr int kNumCacheRegisters = kLiftoffAssemblerGpCacheRegs.Count(); |
| // If we have many locals, we put them on the stack initially. This avoids |
| // having to spill them on merge points. Use of these initial values should |
| // be rare anyway. |
| if (actual_locals > kNumCacheRegisters / 2) return true; |
| // If there are locals which are not i32 or i64, we also spill all locals, |
| // because other types cannot be initialized to constants. |
| for (uint32_t param_idx = num_params; param_idx < __ num_locals(); |
| ++param_idx) { |
| ValueKind kind = __ local_kind(param_idx); |
| if (kind != kI32 && kind != kI64) return true; |
| } |
| return false; |
| } |
| |
| V8_NOINLINE V8_PRESERVE_MOST void TraceFunctionEntry(FullDecoder* decoder) { |
| CODE_COMMENT("trace function entry"); |
| __ SpillAllRegisters(); |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), false); |
| __ CallRuntimeStub(WasmCode::kWasmTraceEnter); |
| DefineSafepoint(); |
| } |
| |
| bool dynamic_tiering() { |
| return env_->dynamic_tiering && for_debugging_ == kNotForDebugging && |
| (v8_flags.wasm_tier_up_filter == -1 || |
| v8_flags.wasm_tier_up_filter == func_index_); |
| } |
| |
| void StartFunctionBody(FullDecoder* decoder, Control* block) { |
| for (uint32_t i = 0; i < __ num_locals(); ++i) { |
| if (!CheckSupportedType(decoder, __ local_kind(i), "param")) return; |
| } |
| |
| // Parameter 0 is the instance parameter. |
| uint32_t num_params = |
| static_cast<uint32_t>(decoder->sig_->parameter_count()); |
| |
| __ CodeEntry(); |
| |
| if (decoder->enabled_.has_inlining()) { |
| CODE_COMMENT("frame setup"); |
| int declared_func_index = |
| func_index_ - env_->module->num_imported_functions; |
| DCHECK_GE(declared_func_index, 0); |
| __ CallFrameSetupStub(declared_func_index); |
| } else { |
| __ EnterFrame(StackFrame::WASM); |
| } |
| __ set_has_frame(true); |
| pc_offset_stack_frame_construction_ = __ PrepareStackFrame(); |
| // {PrepareStackFrame} is the first platform-specific assembler method. |
| // If this failed, we can bail out immediately, avoiding runtime overhead |
| // and potential failures because of other unimplemented methods. |
| // A platform implementing {PrepareStackFrame} must ensure that we can |
| // finish compilation without errors even if we hit unimplemented |
| // LiftoffAssembler methods. |
| if (DidAssemblerBailout(decoder)) return; |
| |
| // Input 0 is the call target, the instance is at 1. |
| constexpr int kInstanceParameterIndex = 1; |
| // Check that {kWasmInstanceRegister} matches our call descriptor. |
| DCHECK_EQ(kWasmInstanceRegister, |
| Register::from_code( |
| descriptor_->GetInputLocation(kInstanceParameterIndex) |
| .AsRegister())); |
| USE(kInstanceParameterIndex); |
| __ cache_state()->SetInstanceCacheRegister(kWasmInstanceRegister); |
| |
| if (for_debugging_) __ ResetOSRTarget(); |
| |
| if (num_params) { |
| CODE_COMMENT("process parameters"); |
| ParameterProcessor processor(this, num_params); |
| processor.Process(); |
| } |
| int params_size = __ TopSpillOffset(); |
| |
| // Initialize locals beyond parameters. |
| if (num_params < __ num_locals()) CODE_COMMENT("init locals"); |
| if (SpillLocalsInitially(decoder, num_params)) { |
| bool has_refs = false; |
| for (uint32_t param_idx = num_params; param_idx < __ num_locals(); |
| ++param_idx) { |
| ValueKind kind = __ local_kind(param_idx); |
| has_refs |= is_reference(kind); |
| __ PushStack(kind); |
| } |
| int spill_size = __ TopSpillOffset() - params_size; |
| __ FillStackSlotsWithZero(params_size, spill_size); |
| |
| // Initialize all reference type locals with ref.null. |
| if (has_refs) { |
| LiftoffRegList pinned; |
| Register null_ref_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned).gp()); |
| Register wasm_null_ref_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned).gp()); |
| LoadNullValue(null_ref_reg, pinned, kWasmExternRef); |
| LoadNullValue(wasm_null_ref_reg, pinned, kWasmAnyRef); |
| for (uint32_t local_index = num_params; local_index < __ num_locals(); |
| ++local_index) { |
| ValueType type = decoder->local_types_[local_index]; |
| if (type.is_reference()) { |
| __ Spill(__ cache_state()->stack_state[local_index].offset(), |
| IsSubtypeOf(type, kWasmExternRef, decoder->module_) |
| ? LiftoffRegister(null_ref_reg) |
| : LiftoffRegister(wasm_null_ref_reg), |
| type.kind()); |
| } |
| } |
| } |
| } else { |
| for (uint32_t param_idx = num_params; param_idx < __ num_locals(); |
| ++param_idx) { |
| ValueKind kind = __ local_kind(param_idx); |
| // Anything which is not i32 or i64 requires spilling. |
| DCHECK(kind == kI32 || kind == kI64); |
| __ PushConstant(kind, int32_t{0}); |
| } |
| } |
| |
| DCHECK_EQ(__ num_locals(), __ cache_state()->stack_height()); |
| |
| if (V8_UNLIKELY(debug_sidetable_builder_)) { |
| debug_sidetable_builder_->SetNumLocals(__ num_locals()); |
| } |
| |
| // The function-prologue stack check is associated with position 0, which |
| // is never a position of any instruction in the function. |
| StackCheck(decoder, 0); |
| |
| if (V8_UNLIKELY(max_steps_)) { |
| // Subtract 16 steps for the function call itself (including the function |
| // prologue), plus 1 for each local (including parameters). |
| // Do this only *after* setting up the frame completely, even though we |
| // already executed the work then. |
| CheckMaxSteps(decoder, 16 + __ num_locals()); |
| } |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm)) TraceFunctionEntry(decoder); |
| } |
| |
| void GenerateOutOfLineCode(OutOfLineCode* ool) { |
| CODE_COMMENT( |
| (std::string("OOL: ") + GetRuntimeStubName(ool->stub)).c_str()); |
| __ bind(ool->label.get()); |
| const bool is_stack_check = ool->stub == WasmCode::kWasmStackGuard; |
| const bool is_tierup = ool->stub == WasmCode::kWasmTriggerTierUp; |
| |
| // Only memory OOB traps need a {pc}, but not unconditionally. Static OOB |
| // accesses do not need protected instruction information, hence they also |
| // do not set {pc}. |
| DCHECK_IMPLIES(ool->stub != WasmCode::kThrowWasmTrapMemOutOfBounds, |
| ool->pc == 0); |
| |
| if (env_->bounds_checks == kTrapHandler && ool->pc != 0) { |
| uint32_t pc = static_cast<uint32_t>(__ pc_offset()); |
| DCHECK_EQ(pc, __ pc_offset()); |
| protected_instructions_.emplace_back( |
| trap_handler::ProtectedInstructionData{ool->pc, pc}); |
| } |
| |
| if (!env_->runtime_exception_support) { |
| // We cannot test calls to the runtime in cctest/test-run-wasm. |
| // Therefore we emit a call to C here instead of a call to the runtime. |
| // In this mode, we never generate stack checks. |
| DCHECK(!is_stack_check); |
| __ CallTrapCallbackForTesting(); |
| __ LeaveFrame(StackFrame::WASM); |
| __ DropStackSlotsAndRet( |
| static_cast<uint32_t>(descriptor_->ParameterSlotCount())); |
| return; |
| } |
| |
| if (!ool->regs_to_save.is_empty()) { |
| __ PushRegisters(ool->regs_to_save); |
| } |
| if (V8_UNLIKELY(ool->spilled_registers != nullptr)) { |
| for (auto& entry : ool->spilled_registers->entries) { |
| // We should not push and spill the same register. |
| DCHECK(!ool->regs_to_save.has(entry.reg)); |
| __ Spill(entry.offset, entry.reg, entry.kind); |
| } |
| } |
| |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(ool->position), true); |
| __ CallRuntimeStub(ool->stub); |
| auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_); |
| |
| if (ool->safepoint_info) { |
| for (auto index : ool->safepoint_info->slots) { |
| safepoint.DefineTaggedStackSlot(index); |
| } |
| |
| int total_frame_size = __ GetTotalFrameSize(); |
| LiftoffRegList gp_regs = ool->regs_to_save & kGpCacheRegList; |
| // {total_frame_size} is the highest offset from the FP that is used to |
| // store a value. The offset of the first spill slot should therefore be |
| // {(total_frame_size / kSystemPointerSize) + 1}. However, spill slots |
| // don't start at offset '0' but at offset '-1' (or |
| // {-kSystemPointerSize}). Therefore we have to add another '+ 1' to the |
| // index of the first spill slot. |
| int index = (total_frame_size / kSystemPointerSize) + 2; |
| |
| __ RecordSpillsInSafepoint(safepoint, gp_regs, |
| ool->safepoint_info->spills, index); |
| } |
| |
| DCHECK_EQ(!debug_sidetable_builder_, !ool->debug_sidetable_entry_builder); |
| if (V8_UNLIKELY(ool->debug_sidetable_entry_builder)) { |
| ool->debug_sidetable_entry_builder->set_pc_offset(__ pc_offset()); |
| } |
| DCHECK_EQ(ool->continuation.get()->is_bound(), is_stack_check || is_tierup); |
| if (is_stack_check) { |
| MaybeOSR(); |
| } |
| if (!ool->regs_to_save.is_empty()) __ PopRegisters(ool->regs_to_save); |
| if (is_stack_check || is_tierup) { |
| if (V8_UNLIKELY(ool->spilled_registers != nullptr)) { |
| DCHECK(for_debugging_); |
| for (auto& entry : ool->spilled_registers->entries) { |
| __ Fill(entry.reg, entry.offset, entry.kind); |
| } |
| } |
| if (ool->cached_instance != no_reg) { |
| __ LoadInstanceFromFrame(ool->cached_instance); |
| } |
| __ emit_jump(ool->continuation.get()); |
| } else { |
| __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap); |
| } |
| } |
| |
| void FinishFunction(FullDecoder* decoder) { |
| if (DidAssemblerBailout(decoder)) return; |
| __ AlignFrameSize(); |
| #if DEBUG |
| int frame_size = __ GetTotalFrameSize(); |
| #endif |
| for (OutOfLineCode& ool : out_of_line_code_) { |
| GenerateOutOfLineCode(&ool); |
| } |
| DCHECK_EQ(frame_size, __ GetTotalFrameSize()); |
| __ PatchPrepareStackFrame(pc_offset_stack_frame_construction_, |
| &safepoint_table_builder_, |
| decoder->enabled_.has_inlining()); |
| __ FinishCode(); |
| safepoint_table_builder_.Emit(&asm_, __ GetTotalFrameSlotCountForGC()); |
| // Emit the handler table. |
| if (!handlers_.empty()) { |
| handler_table_offset_ = HandlerTable::EmitReturnTableStart(&asm_); |
| for (auto& handler : handlers_) { |
| HandlerTable::EmitReturnEntry(&asm_, handler.pc_offset, |
| handler.handler.get()->pos()); |
| } |
| } |
| __ MaybeEmitOutOfLineConstantPool(); |
| // The previous calls may have also generated a bailout. |
| DidAssemblerBailout(decoder); |
| DCHECK_EQ(num_exceptions_, 0); |
| |
| if (decoder->enabled_.has_inlining() && |
| !encountered_call_instructions_.empty()) { |
| // Update the call targets stored in the WasmModule. |
| TypeFeedbackStorage& type_feedback = env_->module->type_feedback; |
| base::SharedMutexGuard<base::kExclusive> mutex_guard( |
| &type_feedback.mutex); |
| base::OwnedVector<uint32_t>& call_targets = |
| type_feedback.feedback_for_function[func_index_].call_targets; |
| if (call_targets.empty()) { |
| call_targets = |
| base::OwnedVector<uint32_t>::Of(encountered_call_instructions_); |
| } else { |
| DCHECK_EQ(call_targets.as_vector(), |
| base::VectorOf(encountered_call_instructions_)); |
| } |
| } |
| } |
| |
| void OnFirstError(FullDecoder* decoder) { |
| if (!did_bailout()) bailout_reason_ = kDecodeError; |
| UnuseLabels(decoder); |
| asm_.AbortCompilation(); |
| } |
| |
| void CheckMaxSteps(FullDecoder* decoder, int steps_done = 1) { |
| DCHECK_LE(1, steps_done); |
| CODE_COMMENT("check max steps"); |
| LiftoffRegList pinned; |
| LiftoffRegister max_steps = pinned.set(__ GetUnusedRegister(kGpReg, {})); |
| LiftoffRegister max_steps_addr = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| { |
| FREEZE_STATE(frozen); |
| __ LoadConstant( |
| max_steps_addr, |
| WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(max_steps_))); |
| __ Load(max_steps, max_steps_addr.gp(), no_reg, 0, LoadType::kI32Load); |
| // Subtract first (and store the result), so the caller sees that |
| // max_steps ran negative. Since we never subtract too much at once, we |
| // cannot underflow. |
| DCHECK_GE(kMaxInt / 16, steps_done); // An arbitrary limit. |
| __ emit_i32_subi(max_steps.gp(), max_steps.gp(), steps_done); |
| __ Store(max_steps_addr.gp(), no_reg, 0, max_steps, StoreType::kI32Store, |
| pinned); |
| Label cont; |
| __ emit_i32_cond_jumpi(kGreaterThanEqual, &cont, max_steps.gp(), 0, |
| frozen); |
| // Abort. |
| Trap(decoder, kTrapUnreachable); |
| __ bind(&cont); |
| } |
| } |
| |
| V8_NOINLINE void EmitDebuggingInfo(FullDecoder* decoder, WasmOpcode opcode) { |
| DCHECK(for_debugging_); |
| if (!WasmOpcodes::IsBreakable(opcode)) return; |
| bool has_breakpoint = false; |
| if (next_breakpoint_ptr_) { |
| if (*next_breakpoint_ptr_ == 0) { |
| // A single breakpoint at offset 0 indicates stepping. |
| DCHECK_EQ(next_breakpoint_ptr_ + 1, next_breakpoint_end_); |
| has_breakpoint = true; |
| } else { |
| while (next_breakpoint_ptr_ != next_breakpoint_end_ && |
| *next_breakpoint_ptr_ < decoder->position()) { |
| // Skip unreachable breakpoints. |
| ++next_breakpoint_ptr_; |
| } |
| if (next_breakpoint_ptr_ == next_breakpoint_end_) { |
| next_breakpoint_ptr_ = next_breakpoint_end_ = nullptr; |
| } else if (*next_breakpoint_ptr_ == decoder->position()) { |
| has_breakpoint = true; |
| } |
| } |
| } |
| if (has_breakpoint) { |
| CODE_COMMENT("breakpoint"); |
| EmitBreakpoint(decoder); |
| // Once we emitted an unconditional breakpoint, we don't need to check |
| // function entry breaks any more. |
| did_function_entry_break_checks_ = true; |
| } else if (!did_function_entry_break_checks_) { |
| did_function_entry_break_checks_ = true; |
| CODE_COMMENT("check function entry break"); |
| Label do_break; |
| Label no_break; |
| Register flag = __ GetUnusedRegister(kGpReg, {}).gp(); |
| |
| // Check the "hook on function call" flag. If set, trigger a break. |
| LOAD_INSTANCE_FIELD(flag, HookOnFunctionCallAddress, kSystemPointerSize, |
| {}); |
| FREEZE_STATE(frozen); |
| __ Load(LiftoffRegister{flag}, flag, no_reg, 0, LoadType::kI32Load8U, {}); |
| __ emit_cond_jump(kNotZero, &do_break, kI32, flag, no_reg, frozen); |
| |
| // Check if we should stop on "script entry". |
| LOAD_INSTANCE_FIELD(flag, BreakOnEntry, kUInt8Size, {}); |
| __ emit_cond_jump(kZero, &no_break, kI32, flag, no_reg, frozen); |
| |
| __ bind(&do_break); |
| EmitBreakpoint(decoder); |
| __ bind(&no_break); |
| } else if (dead_breakpoint_ == decoder->position()) { |
| DCHECK(!next_breakpoint_ptr_ || |
| *next_breakpoint_ptr_ != dead_breakpoint_); |
| // The top frame is paused at this position, but the breakpoint was |
| // removed. Adding a dead breakpoint here ensures that the source |
| // position exists, and that the offset to the return address is the |
| // same as in the old code. |
| CODE_COMMENT("dead breakpoint"); |
| Label cont; |
| __ emit_jump(&cont); |
| EmitBreakpoint(decoder); |
| __ bind(&cont); |
| } |
| if (V8_UNLIKELY(max_steps_ != nullptr)) { |
| CheckMaxSteps(decoder); |
| } |
| } |
| |
| void NextInstruction(FullDecoder* decoder, WasmOpcode opcode) { |
| // Add a single check, so that the fast path can be inlined while |
| // {EmitDebuggingInfo} stays outlined. |
| if (V8_UNLIKELY(for_debugging_)) EmitDebuggingInfo(decoder, opcode); |
| TraceCacheState(decoder); |
| SLOW_DCHECK(__ ValidateCacheState()); |
| CODE_COMMENT(WasmOpcodes::OpcodeName( |
| WasmOpcodes::IsPrefixOpcode(opcode) |
| ? decoder->read_prefixed_opcode<ValidationTag>(decoder->pc()).first |
| : opcode)); |
| |
| if (!has_outstanding_op() && decoder->control_at(0)->reachable()) { |
| // Decoder stack and liftoff stack have to be in sync if current code |
| // path is reachable. |
| DCHECK_EQ(decoder->stack_size() + __ num_locals() + num_exceptions_, |
| __ cache_state()->stack_state.size()); |
| } |
| } |
| |
| void EmitBreakpoint(FullDecoder* decoder) { |
| DCHECK(for_debugging_); |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), true); |
| __ CallRuntimeStub(WasmCode::kWasmDebugBreak); |
| DefineSafepointWithCalleeSavedRegisters(); |
| RegisterDebugSideTableEntry(decoder, |
| DebugSideTableBuilder::kAllowRegisters); |
| MaybeOSR(); |
| } |
| |
| void PushControl(Control* block) { |
| // The Liftoff stack includes implicit exception refs stored for catch |
| // blocks, so that they can be rethrown. |
| block->num_exceptions = num_exceptions_; |
| } |
| |
| void Block(FullDecoder* decoder, Control* block) { PushControl(block); } |
| |
| void Loop(FullDecoder* decoder, Control* loop) { |
| // Before entering a loop, spill all locals to the stack, in order to free |
| // the cache registers, and to avoid unnecessarily reloading stack values |
| // into registers at branches. |
| // TODO(clemensb): Come up with a better strategy here, involving |
| // pre-analysis of the function. |
| __ SpillLocals(); |
| |
| __ PrepareLoopArgs(loop->start_merge.arity); |
| |
| // Loop labels bind at the beginning of the block. |
| __ bind(loop->label.get()); |
| |
| // Save the current cache state for the merge when jumping to this loop. |
| loop->label_state.Split(*__ cache_state()); |
| |
| PushControl(loop); |
| |
| if (!dynamic_tiering()) { |
| // When the budget-based tiering mechanism is enabled, use that to |
| // check for interrupt requests; otherwise execute a stack check in the |
| // loop header. |
| StackCheck(decoder, decoder->position()); |
| } |
| } |
| |
| void Try(FullDecoder* decoder, Control* block) { |
| block->try_info = zone_->New<TryInfo>(zone_); |
| PushControl(block); |
| } |
| |
| // Load the property in {kReturnRegister0}. |
| LiftoffRegister GetExceptionProperty( |
| const LiftoffAssembler::VarState& exception, RootIndex root_index) { |
| DCHECK(root_index == RootIndex::kwasm_exception_tag_symbol || |
| root_index == RootIndex::kwasm_exception_values_symbol); |
| |
| LiftoffRegList pinned; |
| LiftoffRegister tag_symbol_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadExceptionSymbol(tag_symbol_reg.gp(), pinned, root_index); |
| LiftoffRegister context_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(context_reg.gp(), NativeContext, pinned); |
| |
| LiftoffAssembler::VarState tag_symbol{kRef, tag_symbol_reg, 0}; |
| LiftoffAssembler::VarState context{kRef, context_reg, 0}; |
| |
| CallRuntimeStub(WasmCode::kWasmGetOwnProperty, |
| MakeSig::Returns(kRef).Params(kRef, kRef, kRef), |
| {exception, tag_symbol, context}, kNoSourcePosition); |
| |
| return LiftoffRegister(kReturnRegister0); |
| } |
| |
| void CatchException(FullDecoder* decoder, const TagIndexImmediate& imm, |
| Control* block, base::Vector<Value> values) { |
| DCHECK(block->is_try_catch()); |
| __ emit_jump(block->label.get()); |
| |
| // The catch block is unreachable if no possible throws in the try block |
| // exist. We only build a landing pad if some node in the try block can |
| // (possibly) throw. Otherwise the catch environments remain empty. |
| if (!block->try_info->catch_reached) { |
| block->reachability = kSpecOnlyReachable; |
| return; |
| } |
| |
| // This is the last use of this label. Re-use the field for the label of the |
| // next catch block, and jump there if the tag does not match. |
| __ bind(&block->try_info->catch_label); |
| new (&block->try_info->catch_label) Label(); |
| |
| __ cache_state()->Split(block->try_info->catch_state); |
| |
| CODE_COMMENT("load caught exception tag"); |
| DCHECK_EQ(__ cache_state()->stack_state.back().kind(), kRef); |
| LiftoffRegister caught_tag = |
| GetExceptionProperty(__ cache_state()->stack_state.back(), |
| RootIndex::kwasm_exception_tag_symbol); |
| LiftoffRegList pinned; |
| pinned.set(caught_tag); |
| |
| CODE_COMMENT("load expected exception tag"); |
| Register imm_tag = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(imm_tag, TagsTable, pinned); |
| __ LoadTaggedPointer( |
| imm_tag, imm_tag, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index)); |
| |
| CODE_COMMENT("compare tags"); |
| { |
| FREEZE_STATE(frozen); |
| Label caught; |
| __ emit_cond_jump(kEqual, &caught, kI32, imm_tag, caught_tag.gp(), |
| frozen); |
| // The tags don't match, merge the current state into the catch state and |
| // jump to the next handler. |
| __ MergeFullStackWith(block->try_info->catch_state); |
| __ emit_jump(&block->try_info->catch_label); |
| __ bind(&caught); |
| } |
| if (!block->try_info->in_handler) { |
| block->try_info->in_handler = true; |
| num_exceptions_++; |
| } |
| GetExceptionValues(decoder, __ cache_state()->stack_state.back(), imm.tag); |
| } |
| |
| void Rethrow(FullDecoder* decoder, |
| const LiftoffAssembler::VarState& exception) { |
| CallRuntimeStub(WasmCode::kWasmRethrow, MakeSig::Params(kRef), {exception}, |
| decoder->position()); |
| } |
| |
| void Delegate(FullDecoder* decoder, uint32_t depth, Control* block) { |
| DCHECK_EQ(block, decoder->control_at(0)); |
| Control* target = decoder->control_at(depth); |
| DCHECK(block->is_incomplete_try()); |
| __ bind(&block->try_info->catch_label); |
| if (block->try_info->catch_reached) { |
| __ cache_state()->Steal(block->try_info->catch_state); |
| if (depth == decoder->control_depth() - 1) { |
| // Delegate to the caller, do not emit a landing pad. |
| Rethrow(decoder, __ cache_state()->stack_state.back()); |
| MaybeOSR(); |
| } else { |
| DCHECK(target->is_incomplete_try()); |
| if (target->try_info->catch_reached) { |
| __ MergeStackWith(target->try_info->catch_state, 1, |
| LiftoffAssembler::kForwardJump); |
| } else { |
| target->try_info->catch_state = __ MergeIntoNewState( |
| __ num_locals(), 1, target->stack_depth + target->num_exceptions); |
| target->try_info->catch_reached = true; |
| } |
| __ emit_jump(&target->try_info->catch_label); |
| } |
| } |
| } |
| |
| void Rethrow(FullDecoder* decoder, Control* try_block) { |
| int index = try_block->try_info->catch_state.stack_height() - 1; |
| auto& exception = __ cache_state()->stack_state[index]; |
| Rethrow(decoder, exception); |
| int pc_offset = __ pc_offset(); |
| MaybeOSR(); |
| EmitLandingPad(decoder, pc_offset); |
| } |
| |
| void CatchAll(FullDecoder* decoder, Control* block) { |
| DCHECK(block->is_try_catchall() || block->is_try_catch()); |
| DCHECK_EQ(decoder->control_at(0), block); |
| |
| // The catch block is unreachable if no possible throws in the try block |
| // exist. We only build a landing pad if some node in the try block can |
| // (possibly) throw. Otherwise the catch environments remain empty. |
| if (!block->try_info->catch_reached) { |
| decoder->SetSucceedingCodeDynamicallyUnreachable(); |
| return; |
| } |
| |
| __ bind(&block->try_info->catch_label); |
| __ cache_state()->Split(block->try_info->catch_state); |
| if (!block->try_info->in_handler) { |
| block->try_info->in_handler = true; |
| num_exceptions_++; |
| } |
| } |
| |
| // Before emitting the conditional branch, {will_freeze} will be initialized |
| // to prevent cache state changes in conditionally executed code. |
| void JumpIfFalse(FullDecoder* decoder, Label* false_dst, |
| base::Optional<FreezeCacheState>& will_freeze) { |
| DCHECK(!will_freeze.has_value()); |
| Condition cond = |
| test_and_reset_outstanding_op(kExprI32Eqz) ? kNotZero : kZero; |
| |
| if (!has_outstanding_op()) { |
| // Unary comparison. |
| Register value = __ PopToRegister().gp(); |
| will_freeze.emplace(asm_); |
| __ emit_cond_jump(cond, false_dst, kI32, value, no_reg, *will_freeze); |
| return; |
| } |
| |
| // Binary comparison of i32 values. |
| cond = Negate(GetCompareCondition(outstanding_op_)); |
| outstanding_op_ = kNoOutstandingOp; |
| LiftoffAssembler::VarState rhs_slot = __ cache_state()->stack_state.back(); |
| if (rhs_slot.is_const()) { |
| // Compare to a constant. |
| int32_t rhs_imm = rhs_slot.i32_const(); |
| __ cache_state()->stack_state.pop_back(); |
| Register lhs = __ PopToRegister().gp(); |
| will_freeze.emplace(asm_); |
| __ emit_i32_cond_jumpi(cond, false_dst, lhs, rhs_imm, *will_freeze); |
| return; |
| } |
| |
| Register rhs = __ PopToRegister().gp(); |
| LiftoffAssembler::VarState lhs_slot = __ cache_state()->stack_state.back(); |
| if (lhs_slot.is_const()) { |
| // Compare a constant to an arbitrary value. |
| int32_t lhs_imm = lhs_slot.i32_const(); |
| __ cache_state()->stack_state.pop_back(); |
| // Flip the condition, because {lhs} and {rhs} are swapped. |
| will_freeze.emplace(asm_); |
| __ emit_i32_cond_jumpi(Flip(cond), false_dst, rhs, lhs_imm, *will_freeze); |
| return; |
| } |
| |
| // Compare two arbitrary values. |
| Register lhs = __ PopToRegister(LiftoffRegList{rhs}).gp(); |
| will_freeze.emplace(asm_); |
| __ emit_cond_jump(cond, false_dst, kI32, lhs, rhs, *will_freeze); |
| } |
| |
| void If(FullDecoder* decoder, const Value& cond, Control* if_block) { |
| DCHECK_EQ(if_block, decoder->control_at(0)); |
| DCHECK(if_block->is_if()); |
| |
| // Allocate the else state. |
| if_block->else_state = zone_->New<ElseState>(zone_); |
| |
| // Test the condition on the value stack, jump to else if zero. |
| base::Optional<FreezeCacheState> frozen; |
| JumpIfFalse(decoder, if_block->else_state->label.get(), frozen); |
| frozen.reset(); |
| |
| // Store the state (after popping the value) for executing the else branch. |
| if_block->else_state->state.Split(*__ cache_state()); |
| |
| PushControl(if_block); |
| } |
| |
| void FallThruTo(FullDecoder* decoder, Control* c) { |
| DCHECK_IMPLIES(c->is_try_catchall(), !c->end_merge.reached); |
| if (c->end_merge.reached) { |
| __ MergeStackWith(c->label_state, c->br_merge()->arity, |
| LiftoffAssembler::kForwardJump); |
| } else { |
| c->label_state = __ MergeIntoNewState(__ num_locals(), c->end_merge.arity, |
| c->stack_depth + c->num_exceptions); |
| } |
| __ emit_jump(c->label.get()); |
| TraceCacheState(decoder); |
| } |
| |
| void FinishOneArmedIf(FullDecoder* decoder, Control* c) { |
| DCHECK(c->is_onearmed_if()); |
| if (c->end_merge.reached) { |
| // Someone already merged to the end of the if. Merge both arms into that. |
| if (c->reachable()) { |
| // Merge the if state into the end state. |
| __ MergeFullStackWith(c->label_state); |
| __ emit_jump(c->label.get()); |
| } |
| // Merge the else state into the end state. Set this state as the current |
| // state first so helper functions know which registers are in use. |
| __ bind(c->else_state->label.get()); |
| __ cache_state()->Steal(c->else_state->state); |
| __ MergeFullStackWith(c->label_state); |
| __ cache_state()->Steal(c->label_state); |
| } else if (c->reachable()) { |
| // No merge yet at the end of the if, but we need to create a merge for |
| // the both arms of this if. Thus init the merge point from the current |
| // state, then merge the else state into that. |
| DCHECK_EQ(c->start_merge.arity, c->end_merge.arity); |
| c->label_state = |
| __ MergeIntoNewState(__ num_locals(), c->start_merge.arity, |
| c->stack_depth + c->num_exceptions); |
| __ emit_jump(c->label.get()); |
| // Merge the else state into the end state. Set this state as the current |
| // state first so helper functions know which registers are in use. |
| __ bind(c->else_state->label.get()); |
| __ cache_state()->Steal(c->else_state->state); |
| __ MergeFullStackWith(c->label_state); |
| __ cache_state()->Steal(c->label_state); |
| } else { |
| // No merge needed, just continue with the else state. |
| __ bind(c->else_state->label.get()); |
| __ cache_state()->Steal(c->else_state->state); |
| } |
| } |
| |
| void FinishTry(FullDecoder* decoder, Control* c) { |
| DCHECK(c->is_try_catch() || c->is_try_catchall()); |
| if (!c->end_merge.reached) { |
| if (c->try_info->catch_reached) { |
| // Drop the implicit exception ref. |
| __ DropExceptionValueAtOffset(__ num_locals() + c->stack_depth + |
| c->num_exceptions); |
| } |
| // Else we did not enter the catch state, continue with the current state. |
| } else { |
| if (c->reachable()) { |
| __ MergeStackWith(c->label_state, c->br_merge()->arity, |
| LiftoffAssembler::kForwardJump); |
| } |
| __ cache_state()->Steal(c->label_state); |
| } |
| if (c->try_info->catch_reached) { |
| num_exceptions_--; |
| } |
| } |
| |
| void PopControl(FullDecoder* decoder, Control* c) { |
| if (c->is_loop()) return; // A loop just falls through. |
| if (c->is_onearmed_if()) { |
| // Special handling for one-armed ifs. |
| FinishOneArmedIf(decoder, c); |
| } else if (c->is_try_catch() || c->is_try_catchall()) { |
| FinishTry(decoder, c); |
| } else if (c->end_merge.reached) { |
| // There is a merge already. Merge our state into that, then continue with |
| // that state. |
| if (c->reachable()) { |
| __ MergeFullStackWith(c->label_state); |
| } |
| __ cache_state()->Steal(c->label_state); |
| } else { |
| // No merge, just continue with our current state. |
| } |
| |
| if (!c->label.get()->is_bound()) __ bind(c->label.get()); |
| } |
| |
| void GenerateCCall(const LiftoffRegister* result_regs, |
| const ValueKindSig* sig, ValueKind out_argument_kind, |
| const LiftoffRegister* arg_regs, |
| ExternalReference ext_ref) { |
| // Before making a call, spill all cache registers. |
| __ SpillAllRegisters(); |
| |
| // Store arguments on our stack, then align the stack for calling to C. |
| int param_bytes = 0; |
| for (ValueKind param_kind : sig->parameters()) { |
| param_bytes += value_kind_size(param_kind); |
| } |
| int out_arg_bytes = |
| out_argument_kind == kVoid ? 0 : value_kind_size(out_argument_kind); |
| int stack_bytes = std::max(param_bytes, out_arg_bytes); |
| __ CallC(sig, arg_regs, result_regs, out_argument_kind, stack_bytes, |
| ext_ref); |
| } |
| |
| template <typename EmitFn, typename... Args> |
| typename std::enable_if<!std::is_member_function_pointer<EmitFn>::value>::type |
| CallEmitFn(EmitFn fn, Args... args) { |
| fn(args...); |
| } |
| |
| template <typename EmitFn, typename... Args> |
| typename std::enable_if<std::is_member_function_pointer<EmitFn>::value>::type |
| CallEmitFn(EmitFn fn, Args... args) { |
| (asm_.*fn)(ConvertAssemblerArg(args)...); |
| } |
| |
| // Wrap a {LiftoffRegister} with implicit conversions to {Register} and |
| // {DoubleRegister}. |
| struct AssemblerRegisterConverter { |
| LiftoffRegister reg; |
| operator LiftoffRegister() { return reg; } |
| operator Register() { return reg.gp(); } |
| operator DoubleRegister() { return reg.fp(); } |
| }; |
| |
| // Convert {LiftoffRegister} to {AssemblerRegisterConverter}, other types stay |
| // unchanged. |
| template <typename T> |
| typename std::conditional<std::is_same<LiftoffRegister, T>::value, |
| AssemblerRegisterConverter, T>::type |
| ConvertAssemblerArg(T t) { |
| return {t}; |
| } |
| |
| template <typename EmitFn, typename ArgType> |
| struct EmitFnWithFirstArg { |
| EmitFn fn; |
| ArgType first_arg; |
| }; |
| |
| template <typename EmitFn, typename ArgType> |
| EmitFnWithFirstArg<EmitFn, ArgType> BindFirst(EmitFn fn, ArgType arg) { |
| return {fn, arg}; |
| } |
| |
| template <typename EmitFn, typename T, typename... Args> |
| void CallEmitFn(EmitFnWithFirstArg<EmitFn, T> bound_fn, Args... args) { |
| CallEmitFn(bound_fn.fn, bound_fn.first_arg, ConvertAssemblerArg(args)...); |
| } |
| |
| template <ValueKind src_kind, ValueKind result_kind, |
| ValueKind result_lane_kind = kVoid, class EmitFn> |
| void EmitUnOp(EmitFn fn) { |
| constexpr RegClass src_rc = reg_class_for(src_kind); |
| constexpr RegClass result_rc = reg_class_for(result_kind); |
| LiftoffRegister src = __ PopToRegister(); |
| LiftoffRegister dst = src_rc == result_rc |
| ? __ GetUnusedRegister(result_rc, {src}, {}) |
| : __ GetUnusedRegister(result_rc, {}); |
| CallEmitFn(fn, dst, src); |
| if (V8_UNLIKELY(nondeterminism_)) { |
| LiftoffRegList pinned{dst}; |
| if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) { |
| CheckNan(dst, pinned, result_kind); |
| } else if (result_kind == ValueKind::kS128 && |
| (result_lane_kind == kF32 || result_lane_kind == kF64)) { |
| CheckS128Nan(dst, pinned, result_lane_kind); |
| } |
| } |
| __ PushRegister(result_kind, dst); |
| } |
| |
| template <ValueKind kind> |
| void EmitFloatUnOpWithCFallback( |
| bool (LiftoffAssembler::*emit_fn)(DoubleRegister, DoubleRegister), |
| ExternalReference (*fallback_fn)()) { |
| auto emit_with_c_fallback = [this, emit_fn, fallback_fn]( |
| LiftoffRegister dst, LiftoffRegister src) { |
| if ((asm_.*emit_fn)(dst.fp(), src.fp())) return; |
| ExternalReference ext_ref = fallback_fn(); |
| auto sig = MakeSig::Params(kind); |
| GenerateCCall(&dst, &sig, kind, &src, ext_ref); |
| }; |
| EmitUnOp<kind, kind>(emit_with_c_fallback); |
| } |
| |
| enum TypeConversionTrapping : bool { kCanTrap = true, kNoTrap = false }; |
| template <ValueKind dst_kind, ValueKind src_kind, |
| TypeConversionTrapping can_trap> |
| void EmitTypeConversion(FullDecoder* decoder, WasmOpcode opcode, |
| ExternalReference (*fallback_fn)()) { |
| static constexpr RegClass src_rc = reg_class_for(src_kind); |
| static constexpr RegClass dst_rc = reg_class_for(dst_kind); |
| LiftoffRegister src = __ PopToRegister(); |
| LiftoffRegister dst = src_rc == dst_rc |
| ? __ GetUnusedRegister(dst_rc, {src}, {}) |
| : __ GetUnusedRegister(dst_rc, {}); |
| Label* trap = |
| can_trap ? AddOutOfLineTrap( |
| decoder, WasmCode::kThrowWasmTrapFloatUnrepresentable) |
| : nullptr; |
| if (!__ emit_type_conversion(opcode, dst, src, trap)) { |
| DCHECK_NOT_NULL(fallback_fn); |
| ExternalReference ext_ref = fallback_fn(); |
| if (can_trap) { |
| // External references for potentially trapping conversions return int. |
| auto sig = MakeSig::Returns(kI32).Params(src_kind); |
| LiftoffRegister ret_reg = |
| __ GetUnusedRegister(kGpReg, LiftoffRegList{dst}); |
| LiftoffRegister dst_regs[] = {ret_reg, dst}; |
| GenerateCCall(dst_regs, &sig, dst_kind, &src, ext_ref); |
| // It's okay that this is short-lived: we're trapping anyway. |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kEqual, trap, kI32, ret_reg.gp(), no_reg, trapping); |
| } else { |
| ValueKind sig_kinds[] = {src_kind}; |
| ValueKindSig sig(0, 1, sig_kinds); |
| GenerateCCall(&dst, &sig, dst_kind, &src, ext_ref); |
| } |
| } |
| __ PushRegister(dst_kind, dst); |
| } |
| |
| void EmitIsNull(WasmOpcode opcode, ValueType type) { |
| LiftoffRegList pinned; |
| LiftoffRegister ref = pinned.set(__ PopToRegister()); |
| LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); |
| LoadNullValueForCompare(null.gp(), pinned, type); |
| // Prefer to overwrite one of the input registers with the result |
| // of the comparison. |
| LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {ref, null}, {}); |
| #if defined(V8_COMPRESS_POINTERS) |
| // As the value in the {null} register is only the tagged pointer part, |
| // we may only compare 32 bits, not the full pointer size. |
| __ emit_i32_set_cond(opcode == kExprRefIsNull ? kEqual : kNotEqual, |
| dst.gp(), ref.gp(), null.gp()); |
| #else |
| __ emit_ptrsize_set_cond(opcode == kExprRefIsNull ? kEqual : kNotEqual, |
| dst.gp(), ref, null); |
| #endif |
| __ PushRegister(kI32, dst); |
| } |
| |
| void UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& value, |
| Value* result) { |
| #define CASE_I32_UNOP(opcode, fn) \ |
| case kExpr##opcode: \ |
| return EmitUnOp<kI32, kI32>(&LiftoffAssembler::emit_##fn); |
| #define CASE_I64_UNOP(opcode, fn) \ |
| case kExpr##opcode: \ |
| return EmitUnOp<kI64, kI64>(&LiftoffAssembler::emit_##fn); |
| #define CASE_FLOAT_UNOP(opcode, kind, fn) \ |
| case kExpr##opcode: \ |
| return EmitUnOp<k##kind, k##kind>(&LiftoffAssembler::emit_##fn); |
| #define CASE_FLOAT_UNOP_WITH_CFALLBACK(opcode, kind, fn) \ |
| case kExpr##opcode: \ |
| return EmitFloatUnOpWithCFallback<k##kind>(&LiftoffAssembler::emit_##fn, \ |
| &ExternalReference::wasm_##fn); |
| #define CASE_TYPE_CONVERSION(opcode, dst_kind, src_kind, ext_ref, can_trap) \ |
| case kExpr##opcode: \ |
| return EmitTypeConversion<k##dst_kind, k##src_kind, can_trap>( \ |
| decoder, kExpr##opcode, ext_ref); |
| switch (opcode) { |
| CASE_I32_UNOP(I32Clz, i32_clz) |
| CASE_I32_UNOP(I32Ctz, i32_ctz) |
| CASE_FLOAT_UNOP(F32Abs, F32, f32_abs) |
| CASE_FLOAT_UNOP(F32Neg, F32, f32_neg) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Ceil, F32, f32_ceil) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Floor, F32, f32_floor) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Trunc, F32, f32_trunc) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F32NearestInt, F32, f32_nearest_int) |
| CASE_FLOAT_UNOP(F32Sqrt, F32, f32_sqrt) |
| CASE_FLOAT_UNOP(F64Abs, F64, f64_abs) |
| CASE_FLOAT_UNOP(F64Neg, F64, f64_neg) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Ceil, F64, f64_ceil) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Floor, F64, f64_floor) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Trunc, F64, f64_trunc) |
| CASE_FLOAT_UNOP_WITH_CFALLBACK(F64NearestInt, F64, f64_nearest_int) |
| CASE_FLOAT_UNOP(F64Sqrt, F64, f64_sqrt) |
| CASE_TYPE_CONVERSION(I32ConvertI64, I32, I64, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I32SConvertF32, I32, F32, nullptr, kCanTrap) |
| CASE_TYPE_CONVERSION(I32UConvertF32, I32, F32, nullptr, kCanTrap) |
| CASE_TYPE_CONVERSION(I32SConvertF64, I32, F64, nullptr, kCanTrap) |
| CASE_TYPE_CONVERSION(I32UConvertF64, I32, F64, nullptr, kCanTrap) |
| CASE_TYPE_CONVERSION(I32ReinterpretF32, I32, F32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I64SConvertI32, I64, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I64UConvertI32, I64, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I64SConvertF32, I64, F32, |
| &ExternalReference::wasm_float32_to_int64, kCanTrap) |
| CASE_TYPE_CONVERSION(I64UConvertF32, I64, F32, |
| &ExternalReference::wasm_float32_to_uint64, kCanTrap) |
| CASE_TYPE_CONVERSION(I64SConvertF64, I64, F64, |
| &ExternalReference::wasm_float64_to_int64, kCanTrap) |
| CASE_TYPE_CONVERSION(I64UConvertF64, I64, F64, |
| &ExternalReference::wasm_float64_to_uint64, kCanTrap) |
| CASE_TYPE_CONVERSION(I64ReinterpretF64, I64, F64, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F32SConvertI32, F32, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F32UConvertI32, F32, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F32SConvertI64, F32, I64, |
| &ExternalReference::wasm_int64_to_float32, kNoTrap) |
| CASE_TYPE_CONVERSION(F32UConvertI64, F32, I64, |
| &ExternalReference::wasm_uint64_to_float32, kNoTrap) |
| CASE_TYPE_CONVERSION(F32ConvertF64, F32, F64, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F32ReinterpretI32, F32, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F64SConvertI32, F64, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F64UConvertI32, F64, I32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F64SConvertI64, F64, I64, |
| &ExternalReference::wasm_int64_to_float64, kNoTrap) |
| CASE_TYPE_CONVERSION(F64UConvertI64, F64, I64, |
| &ExternalReference::wasm_uint64_to_float64, kNoTrap) |
| CASE_TYPE_CONVERSION(F64ConvertF32, F64, F32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(F64ReinterpretI64, F64, I64, nullptr, kNoTrap) |
| CASE_I32_UNOP(I32SExtendI8, i32_signextend_i8) |
| CASE_I32_UNOP(I32SExtendI16, i32_signextend_i16) |
| CASE_I64_UNOP(I64SExtendI8, i64_signextend_i8) |
| CASE_I64_UNOP(I64SExtendI16, i64_signextend_i16) |
| CASE_I64_UNOP(I64SExtendI32, i64_signextend_i32) |
| CASE_I64_UNOP(I64Clz, i64_clz) |
| CASE_I64_UNOP(I64Ctz, i64_ctz) |
| CASE_TYPE_CONVERSION(I32SConvertSatF32, I32, F32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I32UConvertSatF32, I32, F32, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I32SConvertSatF64, I32, F64, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I32UConvertSatF64, I32, F64, nullptr, kNoTrap) |
| CASE_TYPE_CONVERSION(I64SConvertSatF32, I64, F32, |
| &ExternalReference::wasm_float32_to_int64_sat, |
| kNoTrap) |
| CASE_TYPE_CONVERSION(I64UConvertSatF32, I64, F32, |
| &ExternalReference::wasm_float32_to_uint64_sat, |
| kNoTrap) |
| CASE_TYPE_CONVERSION(I64SConvertSatF64, I64, F64, |
| &ExternalReference::wasm_float64_to_int64_sat, |
| kNoTrap) |
| CASE_TYPE_CONVERSION(I64UConvertSatF64, I64, F64, |
| &ExternalReference::wasm_float64_to_uint64_sat, |
| kNoTrap) |
| case kExprI32Eqz: |
| DCHECK(decoder->lookahead(0, kExprI32Eqz)); |
| if ((decoder->lookahead(1, kExprBrIf) || |
| decoder->lookahead(1, kExprIf)) && |
| !for_debugging_) { |
| DCHECK(!has_outstanding_op()); |
| outstanding_op_ = kExprI32Eqz; |
| break; |
| } |
| return EmitUnOp<kI32, kI32>(&LiftoffAssembler::emit_i32_eqz); |
| case kExprI64Eqz: |
| return EmitUnOp<kI64, kI32>(&LiftoffAssembler::emit_i64_eqz); |
| case kExprI32Popcnt: |
| return EmitUnOp<kI32, kI32>( |
| [this](LiftoffRegister dst, LiftoffRegister src) { |
| if (__ emit_i32_popcnt(dst.gp(), src.gp())) return; |
| auto sig = MakeSig::Returns(kI32).Params(kI32); |
| GenerateCCall(&dst, &sig, kVoid, &src, |
| ExternalReference::wasm_word32_popcnt()); |
| }); |
| case kExprI64Popcnt: |
| return EmitUnOp<kI64, kI64>( |
| [this](LiftoffRegister dst, LiftoffRegister src) { |
| if (__ emit_i64_popcnt(dst, src)) return; |
| // The c function returns i32. We will zero-extend later. |
| auto sig = MakeSig::Returns(kI32).Params(kI64); |
| LiftoffRegister c_call_dst = kNeedI64RegPair ? dst.low() : dst; |
| GenerateCCall(&c_call_dst, &sig, kVoid, &src, |
| ExternalReference::wasm_word64_popcnt()); |
| // Now zero-extend the result to i64. |
| __ emit_type_conversion(kExprI64UConvertI32, dst, c_call_dst, |
| nullptr); |
| }); |
| case kExprRefIsNull: |
| // We abuse ref.as_non_null, which isn't otherwise used in this switch, as |
| // a sentinel for the negation of ref.is_null. |
| case kExprRefAsNonNull: |
| return EmitIsNull(opcode, value.type); |
| case kExprExternInternalize: { |
| LiftoffAssembler::VarState input_state = |
| __ cache_state()->stack_state.back(); |
| CallRuntimeStub(WasmCode::kWasmExternInternalize, |
| MakeSig::Returns(kRefNull).Params(kRefNull), |
| {input_state}, decoder->position()); |
| __ DropValues(1); |
| __ PushRegister(kRef, LiftoffRegister(kReturnRegister0)); |
| return; |
| } |
| case kExprExternExternalize: { |
| LiftoffRegList pinned; |
| LiftoffRegister ref = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); |
| LoadNullValueForCompare(null.gp(), pinned, kWasmAnyRef); |
| Label label; |
| { |
| FREEZE_STATE(frozen); |
| __ emit_cond_jump(kNotEqual, &label, kRefNull, ref.gp(), null.gp(), |
| frozen); |
| LoadNullValue(ref.gp(), pinned, kWasmExternRef); |
| __ bind(&label); |
| } |
| __ PushRegister(kRefNull, ref); |
| return; |
| } |
| default: |
| UNREACHABLE(); |
| } |
| #undef CASE_I32_UNOP |
| #undef CASE_I64_UNOP |
| #undef CASE_FLOAT_UNOP |
| #undef CASE_FLOAT_UNOP_WITH_CFALLBACK |
| #undef CASE_TYPE_CONVERSION |
| } |
| |
| template <ValueKind src_kind, ValueKind result_kind, typename EmitFn, |
| typename EmitFnImm> |
| void EmitBinOpImm(EmitFn fn, EmitFnImm fnImm) { |
| static constexpr RegClass src_rc = reg_class_for(src_kind); |
| static constexpr RegClass result_rc = reg_class_for(result_kind); |
| |
| LiftoffAssembler::VarState rhs_slot = __ cache_state()->stack_state.back(); |
| // Check if the RHS is an immediate. |
| if (rhs_slot.is_const()) { |
| __ cache_state()->stack_state.pop_back(); |
| int32_t imm = rhs_slot.i32_const(); |
| |
| LiftoffRegister lhs = __ PopToRegister(); |
| // Either reuse {lhs} for {dst}, or choose a register (pair) which does |
| // not overlap, for easier code generation. |
| LiftoffRegList pinned{lhs}; |
| LiftoffRegister dst = src_rc == result_rc |
| ? __ GetUnusedRegister(result_rc, {lhs}, pinned) |
| : __ GetUnusedRegister(result_rc, pinned); |
| |
| CallEmitFn(fnImm, dst, lhs, imm); |
| static_assert(result_kind != kF32 && result_kind != kF64, |
| "Unhandled nondeterminism for fuzzing."); |
| __ PushRegister(result_kind, dst); |
| } else { |
| // The RHS was not an immediate. |
| EmitBinOp<src_kind, result_kind>(fn); |
| } |
| } |
| |
| template <ValueKind src_kind, ValueKind result_kind, |
| bool swap_lhs_rhs = false, ValueKind result_lane_kind = kVoid, |
| typename EmitFn> |
| void EmitBinOp(EmitFn fn) { |
| static constexpr RegClass src_rc = reg_class_for(src_kind); |
| static constexpr RegClass result_rc = reg_class_for(result_kind); |
| LiftoffRegister rhs = __ PopToRegister(); |
| LiftoffRegister lhs = __ PopToRegister(LiftoffRegList{rhs}); |
| LiftoffRegister dst = src_rc == result_rc |
| ? __ GetUnusedRegister(result_rc, {lhs, rhs}, {}) |
| : __ GetUnusedRegister(result_rc, {}); |
| |
| if (swap_lhs_rhs) std::swap(lhs, rhs); |
| |
| CallEmitFn(fn, dst, lhs, rhs); |
| if (V8_UNLIKELY(nondeterminism_)) { |
| LiftoffRegList pinned{dst}; |
| if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) { |
| CheckNan(dst, pinned, result_kind); |
| } else if (result_kind == ValueKind::kS128 && |
| (result_lane_kind == kF32 || result_lane_kind == kF64)) { |
| CheckS128Nan(dst, pinned, result_lane_kind); |
| } |
| } |
| __ PushRegister(result_kind, dst); |
| } |
| |
| void EmitDivOrRem64CCall(LiftoffRegister dst, LiftoffRegister lhs, |
| LiftoffRegister rhs, ExternalReference ext_ref, |
| Label* trap_by_zero, |
| Label* trap_unrepresentable = nullptr) { |
| // Cannot emit native instructions, build C call. |
| LiftoffRegister ret = __ GetUnusedRegister(kGpReg, LiftoffRegList{dst}); |
| LiftoffRegister tmp = |
| __ GetUnusedRegister(kGpReg, LiftoffRegList{dst, ret}); |
| LiftoffRegister arg_regs[] = {lhs, rhs}; |
| LiftoffRegister result_regs[] = {ret, dst}; |
| auto sig = MakeSig::Returns(kI32).Params(kI64, kI64); |
| GenerateCCall(result_regs, &sig, kI64, arg_regs, ext_ref); |
| FREEZE_STATE(trapping); |
| __ LoadConstant(tmp, WasmValue(int32_t{0})); |
| __ emit_cond_jump(kEqual, trap_by_zero, kI32, ret.gp(), tmp.gp(), trapping); |
| if (trap_unrepresentable) { |
| __ LoadConstant(tmp, WasmValue(int32_t{-1})); |
| __ emit_cond_jump(kEqual, trap_unrepresentable, kI32, ret.gp(), tmp.gp(), |
| trapping); |
| } |
| } |
| |
| template <WasmOpcode opcode> |
| void EmitI32CmpOp(FullDecoder* decoder) { |
| DCHECK(decoder->lookahead(0, opcode)); |
| if ((decoder->lookahead(1, kExprBrIf) || decoder->lookahead(1, kExprIf)) && |
| !for_debugging_) { |
| DCHECK(!has_outstanding_op()); |
| outstanding_op_ = opcode; |
| return; |
| } |
| return EmitBinOp<kI32, kI32>(BindFirst(&LiftoffAssembler::emit_i32_set_cond, |
| GetCompareCondition(opcode))); |
| } |
| |
| void BinOp(FullDecoder* decoder, WasmOpcode opcode, const Value& lhs, |
| const Value& rhs, Value* result) { |
| #define CASE_I64_SHIFTOP(opcode, fn) \ |
| case kExpr##opcode: \ |
| return EmitBinOpImm<kI64, kI64>( \ |
| [this](LiftoffRegister dst, LiftoffRegister src, \ |
| LiftoffRegister amount) { \ |
| __ emit_##fn(dst, src, \ |
| amount.is_gp_pair() ? amount.low_gp() : amount.gp()); \ |
| }, \ |
| &LiftoffAssembler::emit_##fn##i); |
| #define CASE_CCALL_BINOP(opcode, kind, ext_ref_fn) \ |
| case kExpr##opcode: \ |
| return EmitBinOp<k##kind, k##kind>([this](LiftoffRegister dst, \ |
| LiftoffRegister lhs, \ |
| LiftoffRegister rhs) { \ |
| LiftoffRegister args[] = {lhs, rhs}; \ |
| auto ext_ref = ExternalReference::ext_ref_fn(); \ |
| ValueKind sig_kinds[] = {k##kind, k##kind, k##kind}; \ |
| const bool out_via_stack = k##kind == kI64; \ |
| ValueKindSig sig(out_via_stack ? 0 : 1, 2, sig_kinds); \ |
| ValueKind out_arg_kind = out_via_stack ? kI64 : kVoid; \ |
| GenerateCCall(&dst, &sig, out_arg_kind, args, ext_ref); \ |
| }); |
| switch (opcode) { |
| case kExprI32Add: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_add, |
| &LiftoffAssembler::emit_i32_addi); |
| case kExprI32Sub: |
| return EmitBinOp<kI32, kI32>(&LiftoffAssembler::emit_i32_sub); |
| case kExprI32Mul: |
| return EmitBinOp<kI32, kI32>(&LiftoffAssembler::emit_i32_mul); |
| case kExprI32And: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_and, |
| &LiftoffAssembler::emit_i32_andi); |
| case kExprI32Ior: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_or, |
| &LiftoffAssembler::emit_i32_ori); |
| case kExprI32Xor: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_xor, |
| &LiftoffAssembler::emit_i32_xori); |
| case kExprI32Eq: |
| return EmitI32CmpOp<kExprI32Eq>(decoder); |
| case kExprI32Ne: |
| return EmitI32CmpOp<kExprI32Ne>(decoder); |
| case kExprI32LtS: |
| return EmitI32CmpOp<kExprI32LtS>(decoder); |
| case kExprI32LtU: |
| return EmitI32CmpOp<kExprI32LtU>(decoder); |
| case kExprI32GtS: |
| return EmitI32CmpOp<kExprI32GtS>(decoder); |
| case kExprI32GtU: |
| return EmitI32CmpOp<kExprI32GtU>(decoder); |
| case kExprI32LeS: |
| return EmitI32CmpOp<kExprI32LeS>(decoder); |
| case kExprI32LeU: |
| return EmitI32CmpOp<kExprI32LeU>(decoder); |
| case kExprI32GeS: |
| return EmitI32CmpOp<kExprI32GeS>(decoder); |
| case kExprI32GeU: |
| return EmitI32CmpOp<kExprI32GeU>(decoder); |
| case kExprI64Add: |
| return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_add, |
| &LiftoffAssembler::emit_i64_addi); |
| case kExprI64Sub: |
| return EmitBinOp<kI64, kI64>(&LiftoffAssembler::emit_i64_sub); |
| case kExprI64Mul: |
| return EmitBinOp<kI64, kI64>(&LiftoffAssembler::emit_i64_mul); |
| case kExprI64And: |
| return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_and, |
| &LiftoffAssembler::emit_i64_andi); |
| case kExprI64Ior: |
| return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_or, |
| &LiftoffAssembler::emit_i64_ori); |
| case kExprI64Xor: |
| return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_xor, |
| &LiftoffAssembler::emit_i64_xori); |
| case kExprI64Eq: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kEqual)); |
| case kExprI64Ne: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kNotEqual)); |
| case kExprI64LtS: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kLessThan)); |
| case kExprI64LtU: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kUnsignedLessThan)); |
| case kExprI64GtS: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kGreaterThan)); |
| case kExprI64GtU: |
| return EmitBinOp<kI64, kI32>(BindFirst( |
| &LiftoffAssembler::emit_i64_set_cond, kUnsignedGreaterThan)); |
| case kExprI64LeS: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kLessThanEqual)); |
| case kExprI64LeU: |
| return EmitBinOp<kI64, kI32>(BindFirst( |
| &LiftoffAssembler::emit_i64_set_cond, kUnsignedLessThanEqual)); |
| case kExprI64GeS: |
| return EmitBinOp<kI64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_i64_set_cond, kGreaterThanEqual)); |
| case kExprI64GeU: |
| return EmitBinOp<kI64, kI32>(BindFirst( |
| &LiftoffAssembler::emit_i64_set_cond, kUnsignedGreaterThanEqual)); |
| case kExprF32Eq: |
| return EmitBinOp<kF32, kI32>( |
| BindFirst(&LiftoffAssembler::emit_f32_set_cond, kEqual)); |
| case kExprF32Ne: |
| return EmitBinOp<kF32, kI32>( |
| BindFirst(&LiftoffAssembler::emit_f32_set_cond, kNotEqual)); |
| case kExprF32Lt: |
| return EmitBinOp<kF32, kI32>( |
| BindFirst(&LiftoffAssembler::emit_f32_set_cond, kUnsignedLessThan)); |
| case kExprF32Gt: |
| return EmitBinOp<kF32, kI32>(BindFirst( |
| &LiftoffAssembler::emit_f32_set_cond, kUnsignedGreaterThan)); |
| case kExprF32Le: |
| return EmitBinOp<kF32, kI32>(BindFirst( |
| &LiftoffAssembler::emit_f32_set_cond, kUnsignedLessThanEqual)); |
| case kExprF32Ge: |
| return EmitBinOp<kF32, kI32>(BindFirst( |
| &LiftoffAssembler::emit_f32_set_cond, kUnsignedGreaterThanEqual)); |
| case kExprF64Eq: |
| return EmitBinOp<kF64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_f64_set_cond, kEqual)); |
| case kExprF64Ne: |
| return EmitBinOp<kF64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_f64_set_cond, kNotEqual)); |
| case kExprF64Lt: |
| return EmitBinOp<kF64, kI32>( |
| BindFirst(&LiftoffAssembler::emit_f64_set_cond, kUnsignedLessThan)); |
| case kExprF64Gt: |
| return EmitBinOp<kF64, kI32>(BindFirst( |
| &LiftoffAssembler::emit_f64_set_cond, kUnsignedGreaterThan)); |
| case kExprF64Le: |
| return EmitBinOp<kF64, kI32>(BindFirst( |
| &LiftoffAssembler::emit_f64_set_cond, kUnsignedLessThanEqual)); |
| case kExprF64Ge: |
| return EmitBinOp<kF64, kI32>(BindFirst( |
| &LiftoffAssembler::emit_f64_set_cond, kUnsignedGreaterThanEqual)); |
| case kExprI32Shl: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_shl, |
| &LiftoffAssembler::emit_i32_shli); |
| case kExprI32ShrS: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_sar, |
| &LiftoffAssembler::emit_i32_sari); |
| case kExprI32ShrU: |
| return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_shr, |
| &LiftoffAssembler::emit_i32_shri); |
| CASE_CCALL_BINOP(I32Rol, I32, wasm_word32_rol) |
| CASE_CCALL_BINOP(I32Ror, I32, wasm_word32_ror) |
| CASE_I64_SHIFTOP(I64Shl, i64_shl) |
| CASE_I64_SHIFTOP(I64ShrS, i64_sar) |
| CASE_I64_SHIFTOP(I64ShrU, i64_shr) |
| CASE_CCALL_BINOP(I64Rol, I64, wasm_word64_rol) |
| CASE_CCALL_BINOP(I64Ror, I64, wasm_word64_ror) |
| case kExprF32Add: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_add); |
| case kExprF32Sub: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_sub); |
| case kExprF32Mul: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_mul); |
| case kExprF32Div: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_div); |
| case kExprF32Min: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_min); |
| case kExprF32Max: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_max); |
| case kExprF32CopySign: |
| return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_copysign); |
| case kExprF64Add: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_add); |
| case kExprF64Sub: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_sub); |
| case kExprF64Mul: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_mul); |
| case kExprF64Div: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_div); |
| case kExprF64Min: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_min); |
| case kExprF64Max: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_max); |
| case kExprF64CopySign: |
| return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_copysign); |
| case kExprI32DivS: |
| return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); |
| // Adding the second trap might invalidate the pointer returned for |
| // the first one, thus get both pointers afterwards. |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivUnrepresentable); |
| Label* div_by_zero = out_of_line_code_.end()[-2].label.get(); |
| Label* div_unrepresentable = out_of_line_code_.end()[-1].label.get(); |
| __ emit_i32_divs(dst.gp(), lhs.gp(), rhs.gp(), div_by_zero, |
| div_unrepresentable); |
| }); |
| case kExprI32DivU: |
| return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| Label* div_by_zero = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); |
| __ emit_i32_divu(dst.gp(), lhs.gp(), rhs.gp(), div_by_zero); |
| }); |
| case kExprI32RemS: |
| return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| Label* rem_by_zero = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); |
| __ emit_i32_rems(dst.gp(), lhs.gp(), rhs.gp(), rem_by_zero); |
| }); |
| case kExprI32RemU: |
| return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| Label* rem_by_zero = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); |
| __ emit_i32_remu(dst.gp(), lhs.gp(), rhs.gp(), rem_by_zero); |
| }); |
| case kExprI64DivS: |
| return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); |
| // Adding the second trap might invalidate the pointer returned for |
| // the first one, thus get both pointers afterwards. |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivUnrepresentable); |
| Label* div_by_zero = out_of_line_code_.end()[-2].label.get(); |
| Label* div_unrepresentable = out_of_line_code_.end()[-1].label.get(); |
| if (!__ emit_i64_divs(dst, lhs, rhs, div_by_zero, |
| div_unrepresentable)) { |
| ExternalReference ext_ref = ExternalReference::wasm_int64_div(); |
| EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, div_by_zero, |
| div_unrepresentable); |
| } |
| }); |
| case kExprI64DivU: |
| return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| Label* div_by_zero = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); |
| if (!__ emit_i64_divu(dst, lhs, rhs, div_by_zero)) { |
| ExternalReference ext_ref = ExternalReference::wasm_uint64_div(); |
| EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, div_by_zero); |
| } |
| }); |
| case kExprI64RemS: |
| return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| Label* rem_by_zero = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); |
| if (!__ emit_i64_rems(dst, lhs, rhs, rem_by_zero)) { |
| ExternalReference ext_ref = ExternalReference::wasm_int64_mod(); |
| EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, rem_by_zero); |
| } |
| }); |
| case kExprI64RemU: |
| return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, |
| LiftoffRegister lhs, |
| LiftoffRegister rhs) { |
| Label* rem_by_zero = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); |
| if (!__ emit_i64_remu(dst, lhs, rhs, rem_by_zero)) { |
| ExternalReference ext_ref = ExternalReference::wasm_uint64_mod(); |
| EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, rem_by_zero); |
| } |
| }); |
| case kExprRefEq: { |
| return EmitBinOp<kRefNull, kI32>( |
| BindFirst(&LiftoffAssembler::emit_ptrsize_set_cond, kEqual)); |
| } |
| |
| default: |
| UNREACHABLE(); |
| } |
| #undef CASE_I64_SHIFTOP |
| #undef CASE_CCALL_BINOP |
| } |
| |
| void TraceInstruction(FullDecoder* decoder, uint32_t markid) { |
| #if V8_TARGET_ARCH_X64 |
| __ emit_trace_instruction(markid); |
| #endif |
| } |
| |
| void I32Const(FullDecoder* decoder, Value* result, int32_t value) { |
| __ PushConstant(kI32, value); |
| } |
| |
| void I64Const(FullDecoder* decoder, Value* result, int64_t value) { |
| // The {VarState} stores constant values as int32_t, thus we only store |
| // 64-bit constants in this field if it fits in an int32_t. Larger values |
| // cannot be used as immediate value anyway, so we can also just put them in |
| // a register immediately. |
| int32_t value_i32 = static_cast<int32_t>(value); |
| if (value_i32 == value) { |
| __ PushConstant(kI64, value_i32); |
| } else { |
| LiftoffRegister reg = __ GetUnusedRegister(reg_class_for(kI64), {}); |
| __ LoadConstant(reg, WasmValue(value)); |
| __ PushRegister(kI64, reg); |
| } |
| } |
| |
| void F32Const(FullDecoder* decoder, Value* result, float value) { |
| LiftoffRegister reg = __ GetUnusedRegister(kFpReg, {}); |
| __ LoadConstant(reg, WasmValue(value)); |
| __ PushRegister(kF32, reg); |
| } |
| |
| void F64Const(FullDecoder* decoder, Value* result, double value) { |
| LiftoffRegister reg = __ GetUnusedRegister(kFpReg, {}); |
| __ LoadConstant(reg, WasmValue(value)); |
| __ PushRegister(kF64, reg); |
| } |
| |
| void RefNull(FullDecoder* decoder, ValueType type, Value*) { |
| LiftoffRegister null = __ GetUnusedRegister(kGpReg, {}); |
| LoadNullValue(null.gp(), {}, type); |
| __ PushRegister(type.kind(), null); |
| } |
| |
| void RefFunc(FullDecoder* decoder, uint32_t function_index, Value* result) { |
| LiftoffRegister func_index_reg = __ GetUnusedRegister(kGpReg, {}); |
| __ LoadConstant(func_index_reg, WasmValue(function_index)); |
| LiftoffAssembler::VarState func_index_var(kI32, func_index_reg, 0); |
| CallRuntimeStub(WasmCode::kWasmRefFunc, MakeSig::Returns(kRef).Params(kI32), |
| {func_index_var}, decoder->position()); |
| __ PushRegister(kRef, LiftoffRegister(kReturnRegister0)); |
| } |
| |
| void RefAsNonNull(FullDecoder* decoder, const Value& arg, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, obj.gp(), pinned, arg.type); |
| __ PushRegister(kRef, obj); |
| } |
| |
| void Drop(FullDecoder* decoder) { __ DropValues(1); } |
| |
| V8_NOINLINE V8_PRESERVE_MOST void TraceFunctionExit(FullDecoder* decoder) { |
| CODE_COMMENT("trace function exit"); |
| // Before making the runtime call, spill all cache registers. |
| __ SpillAllRegisters(); |
| |
| // Store the return value if there is exactly one. Multiple return values |
| // are not handled yet. |
| size_t num_returns = decoder->sig_->return_count(); |
| // Put the parameter in its place. |
| WasmTraceExitDescriptor descriptor; |
| DCHECK_EQ(0, descriptor.GetStackParameterCount()); |
| DCHECK_EQ(1, descriptor.GetRegisterParameterCount()); |
| Register param_reg = descriptor.GetRegisterParameter(0); |
| if (num_returns == 1) { |
| auto& return_slot = __ cache_state()->stack_state.back(); |
| if (return_slot.is_const()) { |
| __ Spill(&return_slot); |
| } |
| DCHECK(return_slot.is_stack()); |
| __ LoadSpillAddress(param_reg, return_slot.offset(), return_slot.kind()); |
| } else { |
| // Make sure to pass a "valid" parameter (Smi::zero()). |
| LoadSmi(LiftoffRegister{param_reg}, 0); |
| } |
| |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), false); |
| __ CallRuntimeStub(WasmCode::kWasmTraceExit); |
| DefineSafepoint(); |
| } |
| |
| void TierupCheckOnTailCall(FullDecoder* decoder) { |
| if (!dynamic_tiering()) return; |
| LiftoffRegList pinned; |
| Register tmp1 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| TierupCheck(decoder, decoder->position(), __ pc_offset(), tmp1, tmp2); |
| } |
| |
| void DoReturn(FullDecoder* decoder, uint32_t /* drop values */) { |
| Register tmp1 = no_reg; |
| Register tmp2 = no_reg; |
| if (dynamic_tiering()) { |
| LiftoffRegList pinned; |
| tmp1 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| } |
| ReturnImpl(decoder, tmp1, tmp2); |
| } |
| |
| void ReturnImpl(FullDecoder* decoder, Register tmp1, Register tmp2) { |
| if (V8_UNLIKELY(v8_flags.trace_wasm)) TraceFunctionExit(decoder); |
| if (dynamic_tiering()) { |
| TierupCheck(decoder, decoder->position(), __ pc_offset(), tmp1, tmp2); |
| } |
| size_t num_returns = decoder->sig_->return_count(); |
| if (num_returns > 0) __ MoveToReturnLocations(decoder->sig_, descriptor_); |
| __ LeaveFrame(StackFrame::WASM); |
| __ DropStackSlotsAndRet( |
| static_cast<uint32_t>(descriptor_->ParameterSlotCount())); |
| } |
| |
| void LocalGet(FullDecoder* decoder, Value* result, |
| const IndexImmediate& imm) { |
| auto local_slot = __ cache_state()->stack_state[imm.index]; |
| __ cache_state()->stack_state.emplace_back( |
| local_slot.kind(), __ NextSpillOffset(local_slot.kind())); |
| auto* slot = &__ cache_state()->stack_state.back(); |
| if (local_slot.is_reg()) { |
| __ cache_state()->inc_used(local_slot.reg()); |
| slot->MakeRegister(local_slot.reg()); |
| } else if (local_slot.is_const()) { |
| slot->MakeConstant(local_slot.i32_const()); |
| } else { |
| DCHECK(local_slot.is_stack()); |
| auto rc = reg_class_for(local_slot.kind()); |
| LiftoffRegister reg = __ GetUnusedRegister(rc, {}); |
| __ cache_state()->inc_used(reg); |
| slot->MakeRegister(reg); |
| __ Fill(reg, local_slot.offset(), local_slot.kind()); |
| } |
| } |
| |
| void LocalSetFromStackSlot(LiftoffAssembler::VarState* dst_slot, |
| uint32_t local_index) { |
| auto& state = *__ cache_state(); |
| auto& src_slot = state.stack_state.back(); |
| ValueKind kind = dst_slot->kind(); |
| if (dst_slot->is_reg()) { |
| LiftoffRegister slot_reg = dst_slot->reg(); |
| if (state.get_use_count(slot_reg) == 1) { |
| __ Fill(dst_slot->reg(), src_slot.offset(), kind); |
| return; |
| } |
| state.dec_used(slot_reg); |
| dst_slot->MakeStack(); |
| } |
| DCHECK(CompatibleStackSlotTypes(kind, __ local_kind(local_index))); |
| RegClass rc = reg_class_for(kind); |
| LiftoffRegister dst_reg = __ GetUnusedRegister(rc, {}); |
| __ Fill(dst_reg, src_slot.offset(), kind); |
| *dst_slot = LiftoffAssembler::VarState(kind, dst_reg, dst_slot->offset()); |
| __ cache_state()->inc_used(dst_reg); |
| } |
| |
| void LocalSet(uint32_t local_index, bool is_tee) { |
| auto& state = *__ cache_state(); |
| auto& source_slot = state.stack_state.back(); |
| auto& target_slot = state.stack_state[local_index]; |
| switch (source_slot.loc()) { |
| case kRegister: |
| if (target_slot.is_reg()) state.dec_used(target_slot.reg()); |
| target_slot.Copy(source_slot); |
| if (is_tee) state.inc_used(target_slot.reg()); |
| break; |
| case kIntConst: |
| if (target_slot.is_reg()) state.dec_used(target_slot.reg()); |
| target_slot.Copy(source_slot); |
| break; |
| case kStack: |
| LocalSetFromStackSlot(&target_slot, local_index); |
| break; |
| } |
| if (!is_tee) __ cache_state()->stack_state.pop_back(); |
| } |
| |
| void LocalSet(FullDecoder* decoder, const Value& value, |
| const IndexImmediate& imm) { |
| LocalSet(imm.index, false); |
| } |
| |
| void LocalTee(FullDecoder* decoder, const Value& value, Value* result, |
| const IndexImmediate& imm) { |
| LocalSet(imm.index, true); |
| } |
| |
| Register GetGlobalBaseAndOffset(const WasmGlobal* global, |
| LiftoffRegList* pinned, uint32_t* offset) { |
| Register addr = pinned->set(__ GetUnusedRegister(kGpReg, {})).gp(); |
| if (global->mutability && global->imported) { |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(addr, ImportedMutableGlobals, *pinned); |
| int field_offset = |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedAddressArray( |
| global->index); |
| __ Load(LiftoffRegister(addr), addr, no_reg, field_offset, |
| kPointerLoadType); |
| *offset = 0; |
| } else { |
| LOAD_INSTANCE_FIELD(addr, GlobalsStart, kSystemPointerSize, *pinned); |
| *offset = global->offset; |
| } |
| #ifdef V8_ENABLE_SANDBOX |
| __ DecodeSandboxedPointer(addr); |
| #endif |
| return addr; |
| } |
| |
| void GetBaseAndOffsetForImportedMutableExternRefGlobal( |
| const WasmGlobal* global, LiftoffRegList* pinned, Register* base, |
| Register* offset) { |
| Register globals_buffer = |
| pinned->set(__ GetUnusedRegister(kGpReg, *pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, |
| ImportedMutableGlobalsBuffers, *pinned); |
| *base = globals_buffer; |
| __ LoadTaggedPointer( |
| *base, globals_buffer, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(global->offset)); |
| |
| // For the offset we need the index of the global in the buffer, and |
| // then calculate the actual offset from the index. Load the index from |
| // the ImportedMutableGlobals array of the instance. |
| Register imported_mutable_globals = |
| pinned->set(__ GetUnusedRegister(kGpReg, *pinned)).gp(); |
| |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(imported_mutable_globals, |
| ImportedMutableGlobals, *pinned); |
| *offset = imported_mutable_globals; |
| int field_offset = |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedAddressArray( |
| global->index); |
| __ Load(LiftoffRegister(*offset), imported_mutable_globals, no_reg, |
| field_offset, LoadType::kI32Load); |
| __ emit_i32_shli(*offset, *offset, kTaggedSizeLog2); |
| __ emit_i32_addi(*offset, *offset, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(0)); |
| } |
| |
| void GlobalGet(FullDecoder* decoder, Value* result, |
| const GlobalIndexImmediate& imm) { |
| const auto* global = &env_->module->globals[imm.index]; |
| ValueKind kind = global->type.kind(); |
| if (!CheckSupportedType(decoder, kind, "global")) { |
| return; |
| } |
| |
| if (is_reference(kind)) { |
| if (global->mutability && global->imported) { |
| LiftoffRegList pinned; |
| Register base = no_reg; |
| Register offset = no_reg; |
| GetBaseAndOffsetForImportedMutableExternRefGlobal(global, &pinned, |
| &base, &offset); |
| __ LoadTaggedPointer(base, base, offset, 0); |
| __ PushRegister(kind, LiftoffRegister(base)); |
| return; |
| } |
| |
| LiftoffRegList pinned; |
| Register globals_buffer = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, TaggedGlobalsBuffer, |
| pinned); |
| Register value = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| __ LoadTaggedPointer(value, globals_buffer, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( |
| imm.global->offset)); |
| __ PushRegister(kind, LiftoffRegister(value)); |
| return; |
| } |
| LiftoffRegList pinned; |
| uint32_t offset = 0; |
| Register addr = GetGlobalBaseAndOffset(global, &pinned, &offset); |
| LiftoffRegister value = |
| pinned.set(__ GetUnusedRegister(reg_class_for(kind), pinned)); |
| LoadType type = LoadType::ForValueKind(kind); |
| __ Load(value, addr, no_reg, offset, type, nullptr, false); |
| __ PushRegister(kind, value); |
| } |
| |
| void GlobalSet(FullDecoder* decoder, const Value&, |
| const GlobalIndexImmediate& imm) { |
| auto* global = &env_->module->globals[imm.index]; |
| ValueKind kind = global->type.kind(); |
| if (!CheckSupportedType(decoder, kind, "global")) { |
| return; |
| } |
| |
| if (is_reference(kind)) { |
| if (global->mutability && global->imported) { |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| Register base = no_reg; |
| Register offset = no_reg; |
| GetBaseAndOffsetForImportedMutableExternRefGlobal(global, &pinned, |
| &base, &offset); |
| __ StoreTaggedPointer(base, offset, 0, value, pinned); |
| return; |
| } |
| |
| LiftoffRegList pinned; |
| Register globals_buffer = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, TaggedGlobalsBuffer, |
| pinned); |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| __ StoreTaggedPointer(globals_buffer, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( |
| imm.global->offset), |
| value, pinned); |
| return; |
| } |
| LiftoffRegList pinned; |
| uint32_t offset = 0; |
| Register addr = GetGlobalBaseAndOffset(global, &pinned, &offset); |
| LiftoffRegister reg = pinned.set(__ PopToRegister(pinned)); |
| StoreType type = StoreType::ForValueKind(kind); |
| __ Store(addr, no_reg, offset, reg, type, {}, nullptr, false); |
| } |
| |
| void TableGet(FullDecoder* decoder, const Value&, Value*, |
| const IndexImmediate& imm) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister table_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(table_index_reg, WasmValue(imm.index)); |
| LiftoffAssembler::VarState table_index{kI32, table_index_reg, 0}; |
| |
| LiftoffAssembler::VarState index = __ cache_state()->stack_state.back(); |
| |
| ValueType type = env_->module->tables[imm.index].type; |
| bool is_funcref = IsSubtypeOf(type, kWasmFuncRef, env_->module); |
| auto stub = |
| is_funcref ? WasmCode::kWasmTableGetFuncRef : WasmCode::kWasmTableGet; |
| |
| CallRuntimeStub(stub, MakeSig::Returns(type.kind()).Params(kI32, kI32), |
| {table_index, index}, decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ cache_state()->stack_state.pop_back(1); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| __ PushRegister(type.kind(), LiftoffRegister(kReturnRegister0)); |
| } |
| |
| void TableSet(FullDecoder* decoder, const Value&, const Value&, |
| const IndexImmediate& imm) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister table_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(table_index_reg, WasmValue(imm.index)); |
| LiftoffAssembler::VarState table_index{kI32, table_index_reg, 0}; |
| |
| LiftoffAssembler::VarState value = __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState index = __ cache_state()->stack_state.end()[-2]; |
| |
| ValueType type = env_->module->tables[imm.index].type; |
| bool is_funcref = IsSubtypeOf(type, kWasmFuncRef, env_->module); |
| auto stub = |
| is_funcref ? WasmCode::kWasmTableSetFuncRef : WasmCode::kWasmTableSet; |
| |
| CallRuntimeStub(stub, MakeSig::Params(kI32, kI32, kRefNull), |
| {table_index, index, value}, decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ cache_state()->stack_state.pop_back(2); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| } |
| |
| WasmCode::RuntimeStubId GetRuntimeStubIdForTrapReason(TrapReason reason) { |
| switch (reason) { |
| #define RUNTIME_STUB_FOR_TRAP(trap_reason) \ |
| case k##trap_reason: \ |
| return WasmCode::kThrowWasm##trap_reason; |
| |
| FOREACH_WASM_TRAPREASON(RUNTIME_STUB_FOR_TRAP) |
| #undef RUNTIME_STUB_FOR_TRAP |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void Trap(FullDecoder* decoder, TrapReason reason) { |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, GetRuntimeStubIdForTrapReason(reason)); |
| __ emit_jump(trap_label); |
| __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap); |
| } |
| |
| void AssertNullTypecheckImpl(FullDecoder* decoder, const Value& arg, |
| Value* result, Condition cond) { |
| LiftoffRegList pinned; |
| LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast); |
| LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); |
| LoadNullValueForCompare(null.gp(), pinned, arg.type); |
| { |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(cond, trap_label, kRefNull, obj.gp(), null.gp(), |
| trapping); |
| } |
| __ PushRegister(kRefNull, obj); |
| } |
| |
| void AssertNullTypecheck(FullDecoder* decoder, const Value& arg, |
| Value* result) { |
| AssertNullTypecheckImpl(decoder, arg, result, kNotEqual); |
| } |
| |
| void AssertNotNullTypecheck(FullDecoder* decoder, const Value& arg, |
| Value* result) { |
| AssertNullTypecheckImpl(decoder, arg, result, kEqual); |
| } |
| |
| void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) { |
| unsupported(decoder, kOtherReason, "testing opcode"); |
| } |
| |
| void Select(FullDecoder* decoder, const Value& cond, const Value& fval, |
| const Value& tval, Value* result) { |
| LiftoffRegList pinned; |
| Register condition = pinned.set(__ PopToRegister()).gp(); |
| ValueKind kind = __ cache_state()->stack_state.end()[-1].kind(); |
| DCHECK(CompatibleStackSlotTypes( |
| kind, __ cache_state()->stack_state.end()[-2].kind())); |
| LiftoffRegister false_value = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister true_value = __ PopToRegister(pinned); |
| LiftoffRegister dst = __ GetUnusedRegister(true_value.reg_class(), |
| {true_value, false_value}, {}); |
| if (!__ emit_select(dst, condition, true_value, false_value, kind)) { |
| FREEZE_STATE(frozen); |
| // Emit generic code (using branches) instead. |
| Label cont; |
| Label case_false; |
| __ emit_cond_jump(kEqual, &case_false, kI32, condition, no_reg, frozen); |
| if (dst != true_value) __ Move(dst, true_value, kind); |
| __ emit_jump(&cont); |
| |
| __ bind(&case_false); |
| if (dst != false_value) __ Move(dst, false_value, kind); |
| __ bind(&cont); |
| } |
| __ PushRegister(kind, dst); |
| } |
| |
| // {tmp1} and {tmp2} may be {no_reg} if it is guaranteed that {target} |
| // isn't a loop. |
| void BrImpl(FullDecoder* decoder, Control* target, Register tmp1, |
| Register tmp2) { |
| if (dynamic_tiering()) { |
| if (target->is_loop()) { |
| DCHECK(target->label.get()->is_bound()); |
| DCHECK_NE(tmp1, no_reg); |
| DCHECK_NE(tmp2, no_reg); |
| int jump_distance = __ pc_offset() - target->label.get()->pos(); |
| // For now we just add one as the cost for the tier up check. We might |
| // want to revisit this when tuning tiering budgets later. |
| const int kTierUpCheckCost = 1; |
| TierupCheck(decoder, decoder->position(), |
| jump_distance + kTierUpCheckCost, tmp1, tmp2); |
| } else { |
| // To estimate time spent in this function more accurately, we could |
| // increment the tiering budget on forward jumps. However, we don't |
| // know the jump distance yet; using a blanket value has been tried |
| // and found to not make a difference. |
| } |
| } |
| if (target->br_merge()->reached) { |
| __ MergeStackWith(target->label_state, target->br_merge()->arity, |
| target->is_loop() ? LiftoffAssembler::kBackwardJump |
| : LiftoffAssembler::kForwardJump); |
| } else { |
| target->label_state = |
| __ MergeIntoNewState(__ num_locals(), target->br_merge()->arity, |
| target->stack_depth + target->num_exceptions); |
| } |
| __ jmp(target->label.get()); |
| } |
| |
| bool NeedsTierupCheck(FullDecoder* decoder, uint32_t br_depth) { |
| if (!dynamic_tiering()) return false; |
| return br_depth == decoder->control_depth() - 1 || |
| decoder->control_at(br_depth)->is_loop(); |
| } |
| |
| struct TierupTempRegisters { |
| Register tmp1 = no_reg; |
| Register tmp2 = no_reg; |
| }; |
| void AllocateTempRegisters(TierupTempRegisters& temps) { |
| LiftoffRegList pinned; |
| temps.tmp1 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| temps.tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| } |
| |
| void BrOrRet(FullDecoder* decoder, uint32_t depth, |
| uint32_t /* drop_values */) { |
| TierupTempRegisters temps; |
| if (NeedsTierupCheck(decoder, depth)) AllocateTempRegisters(temps); |
| BrOrRetImpl(decoder, depth, temps.tmp1, temps.tmp2); |
| } |
| |
| void BrOrRetImpl(FullDecoder* decoder, uint32_t depth, Register tmp1, |
| Register tmp2) { |
| if (depth == decoder->control_depth() - 1) { |
| ReturnImpl(decoder, tmp1, tmp2); |
| } else { |
| BrImpl(decoder, decoder->control_at(depth), tmp1, tmp2); |
| } |
| } |
| |
| void BrIf(FullDecoder* decoder, const Value& /* cond */, uint32_t depth) { |
| // Avoid having sequences of branches do duplicate work. |
| if (depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {}); |
| } |
| |
| Label cont_false; |
| TierupTempRegisters temps; |
| if (NeedsTierupCheck(decoder, depth)) AllocateTempRegisters(temps); |
| |
| // Test the condition on the value stack, jump to {cont_false} if zero. |
| base::Optional<FreezeCacheState> frozen; |
| JumpIfFalse(decoder, &cont_false, frozen); |
| |
| BrOrRetImpl(decoder, depth, temps.tmp1, temps.tmp2); |
| |
| __ bind(&cont_false); |
| } |
| |
| // Generate a branch table case, potentially reusing previously generated |
| // stack transfer code. |
| void GenerateBrCase(FullDecoder* decoder, uint32_t br_depth, |
| ZoneMap<uint32_t, MovableLabel>* br_targets, |
| Register tmp1, Register tmp2) { |
| auto [iterator, is_new_target] = br_targets->emplace(br_depth, zone_); |
| Label* label = iterator->second.get(); |
| DCHECK_EQ(is_new_target, !label->is_bound()); |
| if (is_new_target) { |
| __ bind(label); |
| BrOrRetImpl(decoder, br_depth, tmp1, tmp2); |
| } else { |
| __ jmp(label); |
| } |
| } |
| |
| // Generate a branch table for input in [min, max). |
| // TODO(wasm): Generate a real branch table (like TF TableSwitch). |
| void GenerateBrTable(FullDecoder* decoder, LiftoffRegister tmp, |
| LiftoffRegister value, uint32_t min, uint32_t max, |
| BranchTableIterator<ValidationTag>* table_iterator, |
| ZoneMap<uint32_t, MovableLabel>* br_targets, |
| Register tmp1, Register tmp2, |
| const FreezeCacheState& frozen) { |
| DCHECK_LT(min, max); |
| // Check base case. |
| if (max == min + 1) { |
| DCHECK_EQ(min, table_iterator->cur_index()); |
| GenerateBrCase(decoder, table_iterator->next(), br_targets, tmp1, tmp2); |
| return; |
| } |
| |
| uint32_t split = min + (max - min) / 2; |
| Label upper_half; |
| __ LoadConstant(tmp, WasmValue(split)); |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, &upper_half, kI32, value.gp(), |
| tmp.gp(), frozen); |
| // Emit br table for lower half: |
| GenerateBrTable(decoder, tmp, value, min, split, table_iterator, br_targets, |
| tmp1, tmp2, frozen); |
| __ bind(&upper_half); |
| // table_iterator will trigger a DCHECK if we don't stop decoding now. |
| if (did_bailout()) return; |
| // Emit br table for upper half: |
| GenerateBrTable(decoder, tmp, value, split, max, table_iterator, br_targets, |
| tmp1, tmp2, frozen); |
| } |
| |
| void BrTable(FullDecoder* decoder, const BranchTableImmediate& imm, |
| const Value& key) { |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister()); |
| |
| // Reserve temp registers if any of the table entries will do a tierup |
| // check (function exit, or loop back edge). |
| Register tmp1 = no_reg; |
| Register tmp2 = no_reg; |
| if (dynamic_tiering()) { |
| bool need_temps = false; |
| BranchTableIterator<ValidationTag> table_iterator(decoder, imm); |
| while (table_iterator.has_next()) { |
| uint32_t depth = table_iterator.next(); |
| if (depth == decoder->control_depth() - 1 || |
| decoder->control_at(depth)->is_loop()) { |
| need_temps = true; |
| break; |
| } |
| } |
| if (need_temps) { |
| tmp1 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| } |
| } |
| |
| { |
| // All targets must have the same arity (checked by validation), so |
| // we can just sample any of them to find that arity. |
| auto [sample_depth, unused_length] = |
| decoder->read_u32v<Decoder::NoValidationTag>(imm.table, |
| "first depth"); |
| __ PrepareForBranch(decoder->control_at(sample_depth)->br_merge()->arity, |
| pinned); |
| } |
| |
| BranchTableIterator<ValidationTag> table_iterator{decoder, imm}; |
| ZoneMap<uint32_t, MovableLabel> br_targets{zone_}; |
| |
| if (imm.table_count > 0) { |
| LiftoffRegister tmp = __ GetUnusedRegister(kGpReg, pinned); |
| __ LoadConstant(tmp, WasmValue(uint32_t{imm.table_count})); |
| FREEZE_STATE(frozen); |
| Label case_default; |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, &case_default, kI32, |
| value.gp(), tmp.gp(), frozen); |
| |
| GenerateBrTable(decoder, tmp, value, 0, imm.table_count, &table_iterator, |
| &br_targets, tmp1, tmp2, frozen); |
| |
| __ bind(&case_default); |
| // table_iterator will trigger a DCHECK if we don't stop decoding now. |
| if (did_bailout()) return; |
| } |
| |
| // Generate the default case. |
| GenerateBrCase(decoder, table_iterator.next(), &br_targets, tmp1, tmp2); |
| DCHECK(!table_iterator.has_next()); |
| } |
| |
| void Else(FullDecoder* decoder, Control* c) { |
| if (c->reachable()) { |
| if (c->end_merge.reached) { |
| __ MergeFullStackWith(c->label_state); |
| } else { |
| c->label_state = |
| __ MergeIntoNewState(__ num_locals(), c->end_merge.arity, |
| c->stack_depth + c->num_exceptions); |
| } |
| __ emit_jump(c->label.get()); |
| } |
| __ bind(c->else_state->label.get()); |
| __ cache_state()->Steal(c->else_state->state); |
| } |
| |
| SpilledRegistersForInspection* GetSpilledRegistersForInspection() { |
| DCHECK(for_debugging_); |
| // If we are generating debugging code, we really need to spill all |
| // registers to make them inspectable when stopping at the trap. |
| auto* spilled = zone_->New<SpilledRegistersForInspection>(zone_); |
| for (uint32_t i = 0, e = __ cache_state()->stack_height(); i < e; ++i) { |
| auto& slot = __ cache_state()->stack_state[i]; |
| if (!slot.is_reg()) continue; |
| spilled->entries.push_back(SpilledRegistersForInspection::Entry{ |
| slot.offset(), slot.reg(), slot.kind()}); |
| __ RecordUsedSpillOffset(slot.offset()); |
| } |
| return spilled; |
| } |
| |
| Label* AddOutOfLineTrap(FullDecoder* decoder, WasmCode::RuntimeStubId stub, |
| uint32_t pc = 0) { |
| // Only memory OOB traps need a {pc}. |
| DCHECK_IMPLIES(stub != WasmCode::kThrowWasmTrapMemOutOfBounds, pc == 0); |
| DCHECK(v8_flags.wasm_bounds_checks); |
| OutOfLineSafepointInfo* safepoint_info = nullptr; |
| if (V8_UNLIKELY(for_debugging_)) { |
| // Execution does not return after a trap. Therefore we don't have to |
| // define a safepoint for traps that would preserve references on the |
| // stack. However, if this is debug code, then we have to preserve the |
| // references so that they can be inspected. |
| safepoint_info = zone_->New<OutOfLineSafepointInfo>(zone_); |
| __ cache_state()->GetTaggedSlotsForOOLCode( |
| &safepoint_info->slots, &safepoint_info->spills, |
| LiftoffAssembler::CacheState::SpillLocation::kStackSlots); |
| } |
| out_of_line_code_.push_back(OutOfLineCode::Trap( |
| zone_, stub, decoder->position(), |
| V8_UNLIKELY(for_debugging_) ? GetSpilledRegistersForInspection() |
| : nullptr, |
| safepoint_info, pc, RegisterOOLDebugSideTableEntry(decoder))); |
| return out_of_line_code_.back().label.get(); |
| } |
| |
| enum ForceCheck : bool { kDoForceCheck = true, kDontForceCheck = false }; |
| |
| // Returns {no_reg} if the memory access is statically known to be out of |
| // bounds (a jump to the trap was generated then); return the GP {index} |
| // register otherwise (holding the ptrsized index). |
| Register BoundsCheckMem(FullDecoder* decoder, uint32_t access_size, |
| uint64_t offset, LiftoffRegister index, |
| LiftoffRegList pinned, ForceCheck force_check) { |
| // This is ensured by the decoder. |
| DCHECK(base::IsInBounds<uintptr_t>(offset, access_size, |
| env_->module->max_memory_size)); |
| |
| // After bounds checking, we know that the index must be ptrsize, hence only |
| // look at the lower word on 32-bit systems (the high word is bounds-checked |
| // further down). |
| Register index_ptrsize = |
| kNeedI64RegPair && index.is_gp_pair() ? index.low_gp() : index.gp(); |
| |
| // Without bounds checks (testing only), just return the ptrsize index. |
| if (V8_UNLIKELY(env_->bounds_checks == kNoBoundsChecks)) { |
| return index_ptrsize; |
| } |
| |
| // Early return for trap handler. |
| DCHECK_IMPLIES(env_->module->is_memory64, |
| env_->bounds_checks == kExplicitBoundsChecks); |
| if (!force_check && env_->bounds_checks == kTrapHandler) { |
| // With trap handlers we should not have a register pair as input (we |
| // would only return the lower half). |
| DCHECK(index.is_gp()); |
| return index_ptrsize; |
| } |
| |
| CODE_COMMENT("bounds check memory"); |
| |
| // Set {pc} of the OOL code to {0} to avoid generation of protected |
| // instruction information (see {GenerateOutOfLineCode}. |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 0); |
| |
| // Convert the index to ptrsize, bounds-checking the high word on 32-bit |
| // systems for memory64. |
| if (!env_->module->is_memory64) { |
| __ emit_u32_to_uintptr(index_ptrsize, index_ptrsize); |
| } else if (kSystemPointerSize == kInt32Size) { |
| DCHECK_GE(kMaxUInt32, env_->module->max_memory_size); |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kNotZero, trap_label, kI32, index.high_gp(), no_reg, |
| trapping); |
| } |
| |
| uintptr_t end_offset = offset + access_size - 1u; |
| |
| pinned.set(index_ptrsize); |
| LiftoffRegister end_offset_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LiftoffRegister mem_size = __ GetUnusedRegister(kGpReg, pinned); |
| LOAD_INSTANCE_FIELD(mem_size.gp(), MemorySize, kSystemPointerSize, pinned); |
| |
| __ LoadConstant(end_offset_reg, WasmValue::ForUintPtr(end_offset)); |
| |
| FREEZE_STATE(trapping); |
| // If the end offset is larger than the smallest memory, dynamically check |
| // the end offset against the actual memory size, which is not known at |
| // compile time. Otherwise, only one check is required (see below). |
| if (end_offset > env_->module->min_memory_size) { |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, trap_label, kIntPtrKind, |
| end_offset_reg.gp(), mem_size.gp(), trapping); |
| } |
| |
| // Just reuse the end_offset register for computing the effective size |
| // (which is >= 0 because of the check above). |
| LiftoffRegister effective_size_reg = end_offset_reg; |
| __ emit_ptrsize_sub(effective_size_reg.gp(), mem_size.gp(), |
| end_offset_reg.gp()); |
| |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, trap_label, kIntPtrKind, |
| index_ptrsize, effective_size_reg.gp(), trapping); |
| return index_ptrsize; |
| } |
| |
| void AlignmentCheckMem(FullDecoder* decoder, uint32_t access_size, |
| uintptr_t offset, Register index, |
| LiftoffRegList pinned) { |
| CODE_COMMENT("alignment check"); |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapUnalignedAccess, 0); |
| Register address = __ GetUnusedRegister(kGpReg, pinned).gp(); |
| |
| FREEZE_STATE(trapping); |
| const uint32_t align_mask = access_size - 1; |
| if ((offset & align_mask) == 0) { |
| // If {offset} is aligned, we can produce faster code. |
| |
| // TODO(ahaas): On Intel, the "test" instruction implicitly computes the |
| // AND of two operands. We could introduce a new variant of |
| // {emit_cond_jump} to use the "test" instruction without the "and" here. |
| // Then we can also avoid using the temp register here. |
| __ emit_i32_andi(address, index, align_mask); |
| __ emit_cond_jump(kNotEqual, trap_label, kI32, address, no_reg, trapping); |
| } else { |
| // For alignment checks we only look at the lower 32-bits in {offset}. |
| __ emit_i32_addi(address, index, static_cast<uint32_t>(offset)); |
| __ emit_i32_andi(address, address, align_mask); |
| __ emit_cond_jump(kNotEqual, trap_label, kI32, address, no_reg, trapping); |
| } |
| } |
| |
| void TraceMemoryOperation(bool is_store, MachineRepresentation rep, |
| Register index, uintptr_t offset, |
| WasmCodePosition position) { |
| // Before making the runtime call, spill all cache registers. |
| __ SpillAllRegisters(); |
| |
| LiftoffRegList pinned; |
| if (index != no_reg) pinned.set(index); |
| // Get one register for computing the effective offset (offset + index). |
| LiftoffRegister effective_offset = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| bool is_memory64 = env_->module->is_memory64; |
| if (is_memory64 && !kNeedI64RegPair) { |
| __ LoadConstant(effective_offset, |
| WasmValue(static_cast<uint64_t>(offset))); |
| if (index != no_reg) { |
| __ emit_i64_add(effective_offset, effective_offset, |
| LiftoffRegister(index)); |
| } |
| } else { |
| // The offset is actually a 32-bits number when 'kNeedI64RegPair' |
| // is true, so we just do 32-bits operations on it under memory64. |
| DCHECK_GE(kMaxUInt32, offset); |
| __ LoadConstant(effective_offset, |
| WasmValue(static_cast<uint32_t>(offset))); |
| if (index != no_reg) { |
| __ emit_i32_add(effective_offset.gp(), effective_offset.gp(), index); |
| } |
| } |
| |
| // Get a register to hold the stack slot for MemoryTracingInfo. |
| LiftoffRegister info = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| // Allocate stack slot for MemoryTracingInfo. |
| __ AllocateStackSlot(info.gp(), sizeof(MemoryTracingInfo)); |
| |
| // Reuse the {effective_offset} register for all information to be stored in |
| // the MemoryTracingInfo struct. |
| LiftoffRegister data = effective_offset; |
| |
| // Now store all information into the MemoryTracingInfo struct. |
| if (kSystemPointerSize == 8 && !is_memory64) { |
| // Zero-extend the effective offset to u64. |
| CHECK(__ emit_type_conversion(kExprI64UConvertI32, data, effective_offset, |
| nullptr)); |
| } |
| __ Store( |
| info.gp(), no_reg, offsetof(MemoryTracingInfo, offset), data, |
| kSystemPointerSize == 8 ? StoreType::kI64Store : StoreType::kI32Store, |
| pinned); |
| __ LoadConstant(data, WasmValue(is_store ? 1 : 0)); |
| __ Store(info.gp(), no_reg, offsetof(MemoryTracingInfo, is_store), data, |
| StoreType::kI32Store8, pinned); |
| __ LoadConstant(data, WasmValue(static_cast<int>(rep))); |
| __ Store(info.gp(), no_reg, offsetof(MemoryTracingInfo, mem_rep), data, |
| StoreType::kI32Store8, pinned); |
| |
| WasmTraceMemoryDescriptor descriptor; |
| DCHECK_EQ(0, descriptor.GetStackParameterCount()); |
| DCHECK_EQ(1, descriptor.GetRegisterParameterCount()); |
| Register param_reg = descriptor.GetRegisterParameter(0); |
| if (info.gp() != param_reg) { |
| __ Move(param_reg, info.gp(), kIntPtrKind); |
| } |
| |
| source_position_table_builder_.AddPosition(__ pc_offset(), |
| SourcePosition(position), false); |
| __ CallRuntimeStub(WasmCode::kWasmTraceMemory); |
| DefineSafepoint(); |
| |
| __ DeallocateStackSlot(sizeof(MemoryTracingInfo)); |
| } |
| |
| bool IndexStaticallyInBounds(const LiftoffAssembler::VarState& index_slot, |
| int access_size, uintptr_t* offset) { |
| if (!index_slot.is_const()) return false; |
| |
| // Potentially zero extend index (which is a 32-bit constant). |
| const uintptr_t index = static_cast<uint32_t>(index_slot.i32_const()); |
| const uintptr_t effective_offset = index + *offset; |
| |
| if (effective_offset < index // overflow |
| || !base::IsInBounds<uintptr_t>(effective_offset, access_size, |
| env_->module->min_memory_size)) { |
| return false; |
| } |
| |
| *offset = effective_offset; |
| return true; |
| } |
| |
| V8_INLINE Register GetMemoryStart(LiftoffRegList pinned) { |
| Register memory_start = __ cache_state()->cached_mem_start; |
| if (V8_UNLIKELY(memory_start == no_reg)) { |
| memory_start = GetMemoryStart_Slow(pinned); |
| } |
| return memory_start; |
| } |
| |
| V8_NOINLINE V8_PRESERVE_MOST Register |
| GetMemoryStart_Slow(LiftoffRegList pinned) { |
| DCHECK_EQ(no_reg, __ cache_state()->cached_mem_start); |
| SCOPED_CODE_COMMENT("load memory start"); |
| Register memory_start = __ GetUnusedRegister(kGpReg, pinned).gp(); |
| LOAD_INSTANCE_FIELD(memory_start, MemoryStart, kSystemPointerSize, pinned); |
| #ifdef V8_ENABLE_SANDBOX |
| __ DecodeSandboxedPointer(memory_start); |
| #endif |
| __ cache_state()->SetMemStartCacheRegister(memory_start); |
| return memory_start; |
| } |
| |
| void LoadMem(FullDecoder* decoder, LoadType type, |
| const MemoryAccessImmediate& imm, const Value& index_val, |
| Value* result) { |
| ValueKind kind = type.value_type().kind(); |
| DCHECK_EQ(kind, result->type.kind()); |
| if (!CheckSupportedType(decoder, kind, "load")) return; |
| |
| uintptr_t offset = imm.offset; |
| Register index = no_reg; |
| RegClass rc = reg_class_for(kind); |
| |
| // Only look at the slot, do not pop it yet (will happen in PopToRegister |
| // below, if this is not a statically-in-bounds index). |
| auto& index_slot = __ cache_state()->stack_state.back(); |
| DCHECK_EQ(index_val.type.kind(), index_slot.kind()); |
| DCHECK(index_slot.kind() == kI32 || index_slot.kind() == kI64); |
| bool i64_offset = index_slot.kind() == kI64; |
| if (IndexStaticallyInBounds(index_slot, type.size(), &offset)) { |
| __ cache_state()->stack_state.pop_back(); |
| SCOPED_CODE_COMMENT("load from memory (constant offset)"); |
| LiftoffRegList pinned; |
| Register mem = pinned.set(GetMemoryStart(pinned)); |
| LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); |
| __ Load(value, mem, no_reg, offset, type, nullptr, true, i64_offset); |
| __ PushRegister(kind, value); |
| } else { |
| LiftoffRegister full_index = __ PopToRegister(); |
| index = BoundsCheckMem(decoder, type.size(), offset, full_index, {}, |
| kDontForceCheck); |
| |
| SCOPED_CODE_COMMENT("load from memory"); |
| LiftoffRegList pinned{index}; |
| |
| // Load the memory start address only now to reduce register pressure |
| // (important on ia32). |
| Register mem = pinned.set(GetMemoryStart(pinned)); |
| LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); |
| |
| uint32_t protected_load_pc = 0; |
| __ Load(value, mem, index, offset, type, &protected_load_pc, true, |
| i64_offset); |
| if (env_->bounds_checks == kTrapHandler) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, |
| protected_load_pc); |
| } |
| __ PushRegister(kind, value); |
| } |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| TraceMemoryOperation(false, type.mem_type().representation(), index, |
| offset, decoder->position()); |
| } |
| } |
| |
| void LoadTransform(FullDecoder* decoder, LoadType type, |
| LoadTransformationKind transform, |
| const MemoryAccessImmediate& imm, const Value& index_val, |
| Value* result) { |
| // LoadTransform requires SIMD support, so check for it here. If |
| // unsupported, bailout and let TurboFan lower the code. |
| if (!CheckSupportedType(decoder, kS128, "LoadTransform")) { |
| return; |
| } |
| |
| LiftoffRegister full_index = __ PopToRegister(); |
| // For load splats and load zero, LoadType is the size of the load, and for |
| // load extends, LoadType is the size of the lane, and it always loads 8 |
| // bytes. |
| uint32_t access_size = |
| transform == LoadTransformationKind::kExtend ? 8 : type.size(); |
| Register index = BoundsCheckMem(decoder, access_size, imm.offset, |
| full_index, {}, kDontForceCheck); |
| |
| uintptr_t offset = imm.offset; |
| LiftoffRegList pinned{index}; |
| CODE_COMMENT("load with transformation"); |
| Register addr = GetMemoryStart(pinned); |
| LiftoffRegister value = __ GetUnusedRegister(reg_class_for(kS128), {}); |
| uint32_t protected_load_pc = 0; |
| __ LoadTransform(value, addr, index, offset, type, transform, |
| &protected_load_pc); |
| |
| if (env_->bounds_checks == kTrapHandler) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, |
| protected_load_pc); |
| } |
| __ PushRegister(kS128, value); |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| // Again load extend is different. |
| MachineRepresentation mem_rep = |
| transform == LoadTransformationKind::kExtend |
| ? MachineRepresentation::kWord64 |
| : type.mem_type().representation(); |
| TraceMemoryOperation(false, mem_rep, index, offset, decoder->position()); |
| } |
| } |
| |
| void LoadLane(FullDecoder* decoder, LoadType type, const Value& _value, |
| const Value& _index, const MemoryAccessImmediate& imm, |
| const uint8_t laneidx, Value* _result) { |
| if (!CheckSupportedType(decoder, kS128, "LoadLane")) { |
| return; |
| } |
| |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister()); |
| LiftoffRegister full_index = __ PopToRegister(); |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, pinned, kDontForceCheck); |
| |
| DCHECK(_index.type.kind() == kI32 || _index.type.kind() == kI64); |
| bool i64_offset = _index.type.kind() == kI64; |
| |
| uintptr_t offset = imm.offset; |
| pinned.set(index); |
| CODE_COMMENT("load lane"); |
| Register addr = GetMemoryStart(pinned); |
| LiftoffRegister result = __ GetUnusedRegister(reg_class_for(kS128), {}); |
| uint32_t protected_load_pc = 0; |
| |
| __ LoadLane(result, value, addr, index, offset, type, laneidx, |
| &protected_load_pc, i64_offset); |
| if (env_->bounds_checks == kTrapHandler) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, |
| protected_load_pc); |
| } |
| |
| __ PushRegister(kS128, result); |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| TraceMemoryOperation(false, type.mem_type().representation(), index, |
| offset, decoder->position()); |
| } |
| } |
| |
| void StoreMem(FullDecoder* decoder, StoreType type, |
| const MemoryAccessImmediate& imm, const Value& index_val, |
| const Value& value_val) { |
| ValueKind kind = type.value_type().kind(); |
| DCHECK_EQ(kind, value_val.type.kind()); |
| if (!CheckSupportedType(decoder, kind, "store")) return; |
| |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister()); |
| |
| uintptr_t offset = imm.offset; |
| Register index = no_reg; |
| |
| auto& index_slot = __ cache_state()->stack_state.back(); |
| DCHECK_EQ(index_val.type.kind(), index_slot.kind()); |
| DCHECK(index_slot.kind() == kI32 || index_slot.kind() == kI64); |
| bool i64_offset = index_slot.kind() == kI64; |
| if (IndexStaticallyInBounds(index_slot, type.size(), &offset)) { |
| __ cache_state()->stack_state.pop_back(); |
| SCOPED_CODE_COMMENT("store to memory (constant offset)"); |
| Register mem = pinned.set(GetMemoryStart(pinned)); |
| __ Store(mem, no_reg, offset, value, type, pinned, nullptr, true, |
| i64_offset); |
| } else { |
| LiftoffRegister full_index = __ PopToRegister(pinned); |
| ForceCheck force_check = |
| kPartialOOBWritesAreNoops ? kDontForceCheck : kDoForceCheck; |
| index = BoundsCheckMem(decoder, type.size(), imm.offset, full_index, |
| pinned, force_check); |
| |
| pinned.set(index); |
| SCOPED_CODE_COMMENT("store to memory"); |
| uint32_t protected_store_pc = 0; |
| // Load the memory start address only now to reduce register pressure |
| // (important on ia32). |
| Register mem = pinned.set(GetMemoryStart(pinned)); |
| LiftoffRegList outer_pinned; |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) outer_pinned.set(index); |
| __ Store(mem, index, offset, value, type, outer_pinned, |
| &protected_store_pc, true, i64_offset); |
| if (env_->bounds_checks == kTrapHandler) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, |
| protected_store_pc); |
| } |
| } |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| TraceMemoryOperation(true, type.mem_rep(), index, offset, |
| decoder->position()); |
| } |
| } |
| |
| void StoreLane(FullDecoder* decoder, StoreType type, |
| const MemoryAccessImmediate& imm, const Value& _index, |
| const Value& _value, const uint8_t lane) { |
| if (!CheckSupportedType(decoder, kS128, "StoreLane")) return; |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister()); |
| LiftoffRegister full_index = __ PopToRegister(pinned); |
| ForceCheck force_check = |
| kPartialOOBWritesAreNoops ? kDontForceCheck : kDoForceCheck; |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, pinned, force_check); |
| |
| DCHECK(_index.type.kind() == kI32 || _index.type.kind() == kI64); |
| bool i64_offset = _index.type.kind() == kI64; |
| |
| uintptr_t offset = imm.offset; |
| pinned.set(index); |
| CODE_COMMENT("store lane to memory"); |
| Register addr = pinned.set(GetMemoryStart(pinned)); |
| uint32_t protected_store_pc = 0; |
| __ StoreLane(addr, index, offset, value, type, lane, &protected_store_pc, |
| i64_offset); |
| if (env_->bounds_checks == kTrapHandler) { |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, |
| protected_store_pc); |
| } |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| TraceMemoryOperation(true, type.mem_rep(), index, offset, |
| decoder->position()); |
| } |
| } |
| |
| void CurrentMemoryPages(FullDecoder* /* decoder */, Value* /* result */) { |
| Register mem_size = __ GetUnusedRegister(kGpReg, {}).gp(); |
| LOAD_INSTANCE_FIELD(mem_size, MemorySize, kSystemPointerSize, {}); |
| __ emit_ptrsize_shri(mem_size, mem_size, kWasmPageSizeLog2); |
| LiftoffRegister result{mem_size}; |
| if (env_->module->is_memory64 && kNeedI64RegPair) { |
| LiftoffRegister high_word = |
| __ GetUnusedRegister(kGpReg, LiftoffRegList{mem_size}); |
| // The high word is always 0 on 32-bit systems. |
| __ LoadConstant(high_word, WasmValue{uint32_t{0}}); |
| result = LiftoffRegister::ForPair(mem_size, high_word.gp()); |
| } |
| __ PushRegister(env_->module->is_memory64 ? kI64 : kI32, result); |
| } |
| |
| void MemoryGrow(FullDecoder* decoder, const Value& value, Value* result_val) { |
| // Pop the input, then spill all cache registers to make the runtime call. |
| LiftoffRegList pinned; |
| LiftoffRegister input = pinned.set(__ PopToRegister()); |
| __ SpillAllRegisters(); |
| |
| LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| |
| Label done; |
| |
| if (env_->module->is_memory64) { |
| // If the high word is not 0, this will always fail (would grow by |
| // >=256TB). The int32_t value will be sign-extended below. |
| __ LoadConstant(result, WasmValue(int32_t{-1})); |
| if (kNeedI64RegPair) { |
| FREEZE_STATE(all_spilled_anyway); |
| __ emit_cond_jump(kNotEqual, &done, kI32, input.high_gp(), no_reg, |
| all_spilled_anyway); |
| input = input.low(); |
| } else { |
| LiftoffRegister high_word = __ GetUnusedRegister(kGpReg, pinned); |
| __ emit_i64_shri(high_word, input, 32); |
| FREEZE_STATE(all_spilled_anyway); |
| __ emit_cond_jump(kNotEqual, &done, kI32, high_word.gp(), no_reg, |
| all_spilled_anyway); |
| } |
| } |
| |
| WasmMemoryGrowDescriptor descriptor; |
| DCHECK_EQ(0, descriptor.GetStackParameterCount()); |
| DCHECK_EQ(1, descriptor.GetRegisterParameterCount()); |
| DCHECK_EQ(machine_type(kI32), descriptor.GetParameterType(0)); |
| |
| Register param_reg = descriptor.GetRegisterParameter(0); |
| if (input.gp() != param_reg) __ Move(param_reg, input.gp(), kI32); |
| |
| __ CallRuntimeStub(WasmCode::kWasmMemoryGrow); |
| DefineSafepoint(); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| if (kReturnRegister0 != result.gp()) { |
| __ Move(result.gp(), kReturnRegister0, kI32); |
| } |
| |
| __ bind(&done); |
| |
| if (env_->module->is_memory64) { |
| LiftoffRegister result64 = result; |
| if (kNeedI64RegPair) result64 = __ GetUnusedRegister(kGpRegPair, pinned); |
| __ emit_type_conversion(kExprI64SConvertI32, result64, result, nullptr); |
| __ PushRegister(kI64, result64); |
| } else { |
| __ PushRegister(kI32, result); |
| } |
| } |
| |
| base::OwnedVector<DebugSideTable::Entry::Value> |
| GetCurrentDebugSideTableEntries( |
| FullDecoder* decoder, |
| DebugSideTableBuilder::AssumeSpilling assume_spilling) { |
| auto& stack_state = __ cache_state()->stack_state; |
| auto values = |
| base::OwnedVector<DebugSideTable::Entry::Value>::NewForOverwrite( |
| stack_state.size()); |
| |
| // For function calls, the decoder still has the arguments on the stack, but |
| // Liftoff already popped them. Hence {decoder->stack_size()} can be bigger |
| // than expected. Just ignore that and use the lower part only. |
| DCHECK_LE(stack_state.size() - num_exceptions_, |
| decoder->num_locals() + decoder->stack_size()); |
| int index = 0; |
| int decoder_stack_index = decoder->stack_size(); |
| // Iterate the operand stack control block by control block, so that we can |
| // handle the implicit exception value for try blocks. |
| for (int j = decoder->control_depth() - 1; j >= 0; j--) { |
| Control* control = decoder->control_at(j); |
| Control* next_control = j > 0 ? decoder->control_at(j - 1) : nullptr; |
| int end_index = next_control |
| ? next_control->stack_depth + __ num_locals() + |
| next_control->num_exceptions |
| : __ cache_state()->stack_height(); |
| bool exception = control->is_try_catch() || control->is_try_catchall(); |
| for (; index < end_index; ++index) { |
| auto& slot = stack_state[index]; |
| auto& value = values[index]; |
| value.index = index; |
| ValueType type = |
| index < static_cast<int>(__ num_locals()) |
| ? decoder->local_type(index) |
| : exception ? ValueType::Ref(HeapType::kAny) |
| : decoder->stack_value(decoder_stack_index--)->type; |
| DCHECK(CompatibleStackSlotTypes(slot.kind(), type.kind())); |
| value.type = type; |
| switch (slot.loc()) { |
| case kIntConst: |
| value.storage = DebugSideTable::Entry::kConstant; |
| value.i32_const = slot.i32_const(); |
| break; |
| case kRegister: |
| DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling); |
| if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) { |
| value.storage = DebugSideTable::Entry::kRegister; |
| value.reg_code = slot.reg().liftoff_code(); |
| break; |
| } |
| DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling); |
| V8_FALLTHROUGH; |
| case kStack: |
| value.storage = DebugSideTable::Entry::kStack; |
| value.stack_offset = slot.offset(); |
| break; |
| } |
| exception = false; |
| } |
| } |
| DCHECK_EQ(values.size(), index); |
| return values; |
| } |
| |
| // Call this after emitting a runtime call that can show up in a stack trace |
| // (e.g. because it can trap). |
| void RegisterDebugSideTableEntry( |
| FullDecoder* decoder, |
| DebugSideTableBuilder::AssumeSpilling assume_spilling) { |
| if (V8_LIKELY(!debug_sidetable_builder_)) return; |
| debug_sidetable_builder_->NewEntry( |
| __ pc_offset(), |
| GetCurrentDebugSideTableEntries(decoder, assume_spilling).as_vector()); |
| } |
| |
| DebugSideTableBuilder::EntryBuilder* RegisterOOLDebugSideTableEntry( |
| FullDecoder* decoder) { |
| if (V8_LIKELY(!debug_sidetable_builder_)) return nullptr; |
| return debug_sidetable_builder_->NewOOLEntry( |
| GetCurrentDebugSideTableEntries(decoder, |
| DebugSideTableBuilder::kAssumeSpilling) |
| .as_vector()); |
| } |
| |
| enum TailCall : bool { kTailCall = true, kNoTailCall = false }; |
| |
| void CallDirect(FullDecoder* decoder, const CallFunctionImmediate& imm, |
| const Value args[], Value[]) { |
| CallDirect(decoder, imm, args, nullptr, kNoTailCall); |
| } |
| |
| void CallIndirect(FullDecoder* decoder, const Value& index_val, |
| const CallIndirectImmediate& imm, const Value args[], |
| Value returns[]) { |
| CallIndirect(decoder, index_val, imm, kNoTailCall); |
| } |
| |
| void CallRef(FullDecoder* decoder, const Value& func_ref, |
| const FunctionSig* sig, uint32_t sig_index, const Value args[], |
| Value returns[]) { |
| CallRef(decoder, func_ref.type, sig, kNoTailCall); |
| } |
| |
| void ReturnCall(FullDecoder* decoder, const CallFunctionImmediate& imm, |
| const Value args[]) { |
| TierupCheckOnTailCall(decoder); |
| CallDirect(decoder, imm, args, nullptr, kTailCall); |
| } |
| |
| void ReturnCallIndirect(FullDecoder* decoder, const Value& index_val, |
| const CallIndirectImmediate& imm, |
| const Value args[]) { |
| TierupCheckOnTailCall(decoder); |
| CallIndirect(decoder, index_val, imm, kTailCall); |
| } |
| |
| void ReturnCallRef(FullDecoder* decoder, const Value& func_ref, |
| const FunctionSig* sig, uint32_t sig_index, |
| const Value args[]) { |
| TierupCheckOnTailCall(decoder); |
| CallRef(decoder, func_ref.type, sig, kTailCall); |
| } |
| |
| void BrOnNull(FullDecoder* decoder, const Value& ref_object, uint32_t depth, |
| bool pass_null_along_branch, |
| Value* /* result_on_fallthrough */) { |
| // Avoid having sequences of branches do duplicate work. |
| if (depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {}); |
| } |
| |
| Label cont_false; |
| LiftoffRegList pinned; |
| LiftoffRegister ref = |
| pinned.set(pass_null_along_branch ? __ PeekToRegister(0, pinned) |
| : __ PopToRegister(pinned)); |
| Register null = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register tmp = NeedsTierupCheck(decoder, depth) |
| ? pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp() |
| : no_reg; |
| LoadNullValueForCompare(null, pinned, ref_object.type); |
| { |
| FREEZE_STATE(frozen); |
| __ emit_cond_jump(kNotEqual, &cont_false, ref_object.type.kind(), |
| ref.gp(), null, frozen); |
| BrOrRetImpl(decoder, depth, null, tmp); |
| } |
| __ bind(&cont_false); |
| if (!pass_null_along_branch) { |
| // We popped the value earlier, must push it back now. |
| __ PushRegister(kRef, ref); |
| } |
| } |
| |
| void BrOnNonNull(FullDecoder* decoder, const Value& ref_object, |
| Value* /* result */, uint32_t depth, |
| bool drop_null_on_fallthrough) { |
| // Avoid having sequences of branches do duplicate work. |
| if (depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {}); |
| } |
| |
| Label cont_false; |
| LiftoffRegList pinned; |
| LiftoffRegister ref = pinned.set(__ PeekToRegister(0, pinned)); |
| |
| Register null = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register tmp = NeedsTierupCheck(decoder, depth) |
| ? pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp() |
| : no_reg; |
| LoadNullValueForCompare(null, pinned, ref_object.type); |
| { |
| FREEZE_STATE(frozen); |
| __ emit_cond_jump(kEqual, &cont_false, ref_object.type.kind(), ref.gp(), |
| null, frozen); |
| |
| BrOrRetImpl(decoder, depth, null, tmp); |
| } |
| // Drop the reference if we are not branching. |
| if (drop_null_on_fallthrough) __ DropValues(1); |
| __ bind(&cont_false); |
| } |
| |
| template <ValueKind src_kind, ValueKind result_kind, |
| ValueKind result_lane_kind = kVoid, typename EmitFn> |
| void EmitTerOp(EmitFn fn, LiftoffRegister dst, LiftoffRegister src1, |
| LiftoffRegister src2, LiftoffRegister src3) { |
| CallEmitFn(fn, dst, src1, src2, src3); |
| if (V8_UNLIKELY(nondeterminism_)) { |
| LiftoffRegList pinned{dst}; |
| if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) { |
| CheckNan(dst, pinned, result_kind); |
| } else if (result_kind == ValueKind::kS128 && |
| (result_lane_kind == kF32 || result_lane_kind == kF64)) { |
| CheckS128Nan(dst, LiftoffRegList{src1, src2, src3, dst}, |
| result_lane_kind); |
| } |
| } |
| __ PushRegister(result_kind, dst); |
| } |
| |
| template <ValueKind src_kind, ValueKind result_kind, |
| ValueKind result_lane_kind = kVoid, typename EmitFn> |
| void EmitTerOp(EmitFn fn) { |
| LiftoffRegister src3 = __ PopToRegister(); |
| LiftoffRegister src2 = __ PopToRegister(LiftoffRegList{src3}); |
| LiftoffRegister src1 = __ PopToRegister(LiftoffRegList{src3, src2}); |
| static constexpr RegClass src_rc = reg_class_for(src_kind); |
| static constexpr RegClass result_rc = reg_class_for(result_kind); |
| // Reusing src1 and src2 will complicate codegen for select for some |
| // backend, so we allow only reusing src3 (the mask), and pin src1 and src2. |
| LiftoffRegister dst = src_rc == result_rc |
| ? __ GetUnusedRegister(result_rc, {src3}, |
| LiftoffRegList{src1, src2}) |
| : __ GetUnusedRegister(result_rc, {}); |
| EmitTerOp<src_kind, result_kind, result_lane_kind, EmitFn>(fn, dst, src1, |
| src2, src3); |
| } |
| |
| void EmitRelaxedLaneSelect() { |
| #if defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64) |
| if (!CpuFeatures::IsSupported(AVX)) { |
| LiftoffRegister mask(xmm0); |
| __ PopToFixedRegister(mask); |
| LiftoffRegister src2 = __ PopToModifiableRegister(LiftoffRegList{mask}); |
| LiftoffRegister src1 = __ PopToRegister(LiftoffRegList{src2, mask}); |
| EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_relaxed_laneselect, |
| src2, src1, src2, mask); |
| return; |
| } |
| #endif |
| LiftoffRegList pinned; |
| LiftoffRegister mask = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister src2 = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister src1 = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister dst = |
| __ GetUnusedRegister(reg_class_for(kS128), {}, pinned); |
| EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_relaxed_laneselect, |
| dst, src1, src2, mask); |
| } |
| |
| template <typename EmitFn, typename EmitFnImm> |
| void EmitSimdShiftOp(EmitFn fn, EmitFnImm fnImm) { |
| static constexpr RegClass result_rc = reg_class_for(kS128); |
| |
| LiftoffAssembler::VarState rhs_slot = __ cache_state()->stack_state.back(); |
| // Check if the RHS is an immediate. |
| if (rhs_slot.is_const()) { |
| __ cache_state()->stack_state.pop_back(); |
| int32_t imm = rhs_slot.i32_const(); |
| |
| LiftoffRegister operand = __ PopToRegister(); |
| LiftoffRegister dst = __ GetUnusedRegister(result_rc, {operand}, {}); |
| |
| CallEmitFn(fnImm, dst, operand, imm); |
| __ PushRegister(kS128, dst); |
| } else { |
| LiftoffRegister count = __ PopToRegister(); |
| LiftoffRegister operand = __ PopToRegister(); |
| LiftoffRegister dst = __ GetUnusedRegister(result_rc, {operand}, {}); |
| |
| CallEmitFn(fn, dst, operand, count); |
| __ PushRegister(kS128, dst); |
| } |
| } |
| |
| template <ValueKind result_lane_kind> |
| void EmitSimdFloatRoundingOpWithCFallback( |
| bool (LiftoffAssembler::*emit_fn)(LiftoffRegister, LiftoffRegister), |
| ExternalReference (*ext_ref)()) { |
| static constexpr RegClass rc = reg_class_for(kS128); |
| LiftoffRegister src = __ PopToRegister(); |
| LiftoffRegister dst = __ GetUnusedRegister(rc, {src}, {}); |
| if (!(asm_.*emit_fn)(dst, src)) { |
| // Return v128 via stack for ARM. |
| auto sig_v_s = MakeSig::Params(kS128); |
| GenerateCCall(&dst, &sig_v_s, kS128, &src, ext_ref()); |
| } |
| if (V8_UNLIKELY(nondeterminism_)) { |
| LiftoffRegList pinned{dst}; |
| CheckS128Nan(dst, pinned, result_lane_kind); |
| } |
| __ PushRegister(kS128, dst); |
| } |
| |
| template <typename EmitFn> |
| void EmitSimdFmaOp(EmitFn emit_fn) { |
| LiftoffRegList pinned; |
| LiftoffRegister src3 = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister src2 = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister src1 = pinned.set(__ PopToRegister(pinned)); |
| RegClass dst_rc = reg_class_for(kS128); |
| LiftoffRegister dst = __ GetUnusedRegister(dst_rc, {}); |
| (asm_.*emit_fn)(dst, src1, src2, src3); |
| __ PushRegister(kS128, dst); |
| return; |
| } |
| |
| void SimdOp(FullDecoder* decoder, WasmOpcode opcode, base::Vector<Value> args, |
| Value* result) { |
| if (!CpuFeatures::SupportsWasmSimd128()) { |
| return unsupported(decoder, kSimd, "simd"); |
| } |
| switch (opcode) { |
| case wasm::kExprI8x16Swizzle: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_swizzle); |
| case wasm::kExprI8x16RelaxedSwizzle: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i8x16_relaxed_swizzle); |
| case wasm::kExprI8x16Popcnt: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_popcnt); |
| case wasm::kExprI8x16Splat: |
| return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i8x16_splat); |
| case wasm::kExprI16x8Splat: |
| return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i16x8_splat); |
| case wasm::kExprI32x4Splat: |
| return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i32x4_splat); |
| case wasm::kExprI64x2Splat: |
| return EmitUnOp<kI64, kS128>(&LiftoffAssembler::emit_i64x2_splat); |
| case wasm::kExprF32x4Splat: |
| return EmitUnOp<kF32, kS128, kF32>(&LiftoffAssembler::emit_f32x4_splat); |
| case wasm::kExprF64x2Splat: |
| return EmitUnOp<kF64, kS128, kF64>(&LiftoffAssembler::emit_f64x2_splat); |
| case wasm::kExprI8x16Eq: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_eq); |
| case wasm::kExprI8x16Ne: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ne); |
| case wasm::kExprI8x16LtS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i8x16_gt_s); |
| case wasm::kExprI8x16LtU: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i8x16_gt_u); |
| case wasm::kExprI8x16GtS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_gt_s); |
| case wasm::kExprI8x16GtU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_gt_u); |
| case wasm::kExprI8x16LeS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i8x16_ge_s); |
| case wasm::kExprI8x16LeU: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i8x16_ge_u); |
| case wasm::kExprI8x16GeS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ge_s); |
| case wasm::kExprI8x16GeU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ge_u); |
| case wasm::kExprI16x8Eq: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_eq); |
| case wasm::kExprI16x8Ne: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ne); |
| case wasm::kExprI16x8LtS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i16x8_gt_s); |
| case wasm::kExprI16x8LtU: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i16x8_gt_u); |
| case wasm::kExprI16x8GtS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_gt_s); |
| case wasm::kExprI16x8GtU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_gt_u); |
| case wasm::kExprI16x8LeS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i16x8_ge_s); |
| case wasm::kExprI16x8LeU: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i16x8_ge_u); |
| case wasm::kExprI16x8GeS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ge_s); |
| case wasm::kExprI16x8GeU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ge_u); |
| case wasm::kExprI32x4Eq: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_eq); |
| case wasm::kExprI32x4Ne: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ne); |
| case wasm::kExprI32x4LtS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i32x4_gt_s); |
| case wasm::kExprI32x4LtU: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i32x4_gt_u); |
| case wasm::kExprI32x4GtS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_gt_s); |
| case wasm::kExprI32x4GtU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_gt_u); |
| case wasm::kExprI32x4LeS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i32x4_ge_s); |
| case wasm::kExprI32x4LeU: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i32x4_ge_u); |
| case wasm::kExprI32x4GeS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ge_s); |
| case wasm::kExprI32x4GeU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ge_u); |
| case wasm::kExprI64x2Eq: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_eq); |
| case wasm::kExprI64x2Ne: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_ne); |
| case wasm::kExprI64x2LtS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i64x2_gt_s); |
| case wasm::kExprI64x2GtS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_gt_s); |
| case wasm::kExprI64x2LeS: |
| return EmitBinOp<kS128, kS128, true>( |
| &LiftoffAssembler::emit_i64x2_ge_s); |
| case wasm::kExprI64x2GeS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_ge_s); |
| case wasm::kExprF32x4Eq: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_eq); |
| case wasm::kExprF32x4Ne: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_ne); |
| case wasm::kExprF32x4Lt: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_lt); |
| case wasm::kExprF32x4Gt: |
| return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f32x4_lt); |
| case wasm::kExprF32x4Le: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_le); |
| case wasm::kExprF32x4Ge: |
| return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f32x4_le); |
| case wasm::kExprF64x2Eq: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_eq); |
| case wasm::kExprF64x2Ne: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_ne); |
| case wasm::kExprF64x2Lt: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_lt); |
| case wasm::kExprF64x2Gt: |
| return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f64x2_lt); |
| case wasm::kExprF64x2Le: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_le); |
| case wasm::kExprF64x2Ge: |
| return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f64x2_le); |
| case wasm::kExprS128Not: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_s128_not); |
| case wasm::kExprS128And: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_and); |
| case wasm::kExprS128Or: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_or); |
| case wasm::kExprS128Xor: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_xor); |
| case wasm::kExprS128Select: |
| return EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_select); |
| case wasm::kExprI8x16Neg: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_neg); |
| case wasm::kExprV128AnyTrue: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_v128_anytrue); |
| case wasm::kExprI8x16AllTrue: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i8x16_alltrue); |
| case wasm::kExprI8x16BitMask: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i8x16_bitmask); |
| case wasm::kExprI8x16Shl: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i8x16_shl, |
| &LiftoffAssembler::emit_i8x16_shli); |
| case wasm::kExprI8x16ShrS: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i8x16_shr_s, |
| &LiftoffAssembler::emit_i8x16_shri_s); |
| case wasm::kExprI8x16ShrU: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i8x16_shr_u, |
| &LiftoffAssembler::emit_i8x16_shri_u); |
| case wasm::kExprI8x16Add: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add); |
| case wasm::kExprI8x16AddSatS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add_sat_s); |
| case wasm::kExprI8x16AddSatU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add_sat_u); |
| case wasm::kExprI8x16Sub: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub); |
| case wasm::kExprI8x16SubSatS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub_sat_s); |
| case wasm::kExprI8x16SubSatU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub_sat_u); |
| case wasm::kExprI8x16MinS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_min_s); |
| case wasm::kExprI8x16MinU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_min_u); |
| case wasm::kExprI8x16MaxS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_max_s); |
| case wasm::kExprI8x16MaxU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_max_u); |
| case wasm::kExprI16x8Neg: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_neg); |
| case wasm::kExprI16x8AllTrue: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i16x8_alltrue); |
| case wasm::kExprI16x8BitMask: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i16x8_bitmask); |
| case wasm::kExprI16x8Shl: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i16x8_shl, |
| &LiftoffAssembler::emit_i16x8_shli); |
| case wasm::kExprI16x8ShrS: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i16x8_shr_s, |
| &LiftoffAssembler::emit_i16x8_shri_s); |
| case wasm::kExprI16x8ShrU: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i16x8_shr_u, |
| &LiftoffAssembler::emit_i16x8_shri_u); |
| case wasm::kExprI16x8Add: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add); |
| case wasm::kExprI16x8AddSatS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add_sat_s); |
| case wasm::kExprI16x8AddSatU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add_sat_u); |
| case wasm::kExprI16x8Sub: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub); |
| case wasm::kExprI16x8SubSatS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub_sat_s); |
| case wasm::kExprI16x8SubSatU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub_sat_u); |
| case wasm::kExprI16x8Mul: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_mul); |
| case wasm::kExprI16x8MinS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_min_s); |
| case wasm::kExprI16x8MinU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_min_u); |
| case wasm::kExprI16x8MaxS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_max_s); |
| case wasm::kExprI16x8MaxU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_max_u); |
| case wasm::kExprI16x8ExtAddPairwiseI8x16S: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_extadd_pairwise_i8x16_s); |
| case wasm::kExprI16x8ExtAddPairwiseI8x16U: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_extadd_pairwise_i8x16_u); |
| case wasm::kExprI16x8ExtMulLowI8x16S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_extmul_low_i8x16_s); |
| case wasm::kExprI16x8ExtMulLowI8x16U: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_extmul_low_i8x16_u); |
| case wasm::kExprI16x8ExtMulHighI8x16S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_extmul_high_i8x16_s); |
| case wasm::kExprI16x8ExtMulHighI8x16U: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_extmul_high_i8x16_u); |
| case wasm::kExprI16x8Q15MulRSatS: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_q15mulr_sat_s); |
| case wasm::kExprI32x4Neg: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_neg); |
| case wasm::kExprI32x4AllTrue: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i32x4_alltrue); |
| case wasm::kExprI32x4BitMask: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i32x4_bitmask); |
| case wasm::kExprI32x4Shl: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i32x4_shl, |
| &LiftoffAssembler::emit_i32x4_shli); |
| case wasm::kExprI32x4ShrS: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i32x4_shr_s, |
| &LiftoffAssembler::emit_i32x4_shri_s); |
| case wasm::kExprI32x4ShrU: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i32x4_shr_u, |
| &LiftoffAssembler::emit_i32x4_shri_u); |
| case wasm::kExprI32x4Add: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_add); |
| case wasm::kExprI32x4Sub: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_sub); |
| case wasm::kExprI32x4Mul: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_mul); |
| case wasm::kExprI32x4MinS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_min_s); |
| case wasm::kExprI32x4MinU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_min_u); |
| case wasm::kExprI32x4MaxS: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_max_s); |
| case wasm::kExprI32x4MaxU: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_max_u); |
| case wasm::kExprI32x4DotI16x8S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_dot_i16x8_s); |
| case wasm::kExprI32x4ExtAddPairwiseI16x8S: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_extadd_pairwise_i16x8_s); |
| case wasm::kExprI32x4ExtAddPairwiseI16x8U: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_extadd_pairwise_i16x8_u); |
| case wasm::kExprI32x4ExtMulLowI16x8S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_extmul_low_i16x8_s); |
| case wasm::kExprI32x4ExtMulLowI16x8U: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_extmul_low_i16x8_u); |
| case wasm::kExprI32x4ExtMulHighI16x8S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_extmul_high_i16x8_s); |
| case wasm::kExprI32x4ExtMulHighI16x8U: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_extmul_high_i16x8_u); |
| case wasm::kExprI64x2Neg: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_neg); |
| case wasm::kExprI64x2AllTrue: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i64x2_alltrue); |
| case wasm::kExprI64x2Shl: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i64x2_shl, |
| &LiftoffAssembler::emit_i64x2_shli); |
| case wasm::kExprI64x2ShrS: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i64x2_shr_s, |
| &LiftoffAssembler::emit_i64x2_shri_s); |
| case wasm::kExprI64x2ShrU: |
| return EmitSimdShiftOp(&LiftoffAssembler::emit_i64x2_shr_u, |
| &LiftoffAssembler::emit_i64x2_shri_u); |
| case wasm::kExprI64x2Add: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_add); |
| case wasm::kExprI64x2Sub: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_sub); |
| case wasm::kExprI64x2Mul: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_mul); |
| case wasm::kExprI64x2ExtMulLowI32x4S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_extmul_low_i32x4_s); |
| case wasm::kExprI64x2ExtMulLowI32x4U: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_extmul_low_i32x4_u); |
| case wasm::kExprI64x2ExtMulHighI32x4S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_extmul_high_i32x4_s); |
| case wasm::kExprI64x2ExtMulHighI32x4U: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_extmul_high_i32x4_u); |
| case wasm::kExprI64x2BitMask: |
| return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i64x2_bitmask); |
| case wasm::kExprI64x2SConvertI32x4Low: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_sconvert_i32x4_low); |
| case wasm::kExprI64x2SConvertI32x4High: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_sconvert_i32x4_high); |
| case wasm::kExprI64x2UConvertI32x4Low: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_uconvert_i32x4_low); |
| case wasm::kExprI64x2UConvertI32x4High: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i64x2_uconvert_i32x4_high); |
| case wasm::kExprF32x4Abs: |
| return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_abs); |
| case wasm::kExprF32x4Neg: |
| return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_neg); |
| case wasm::kExprF32x4Sqrt: |
| return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_sqrt); |
| case wasm::kExprF32x4Ceil: |
| return EmitSimdFloatRoundingOpWithCFallback<kF32>( |
| &LiftoffAssembler::emit_f32x4_ceil, |
| &ExternalReference::wasm_f32x4_ceil); |
| case wasm::kExprF32x4Floor: |
| return EmitSimdFloatRoundingOpWithCFallback<kF32>( |
| &LiftoffAssembler::emit_f32x4_floor, |
| ExternalReference::wasm_f32x4_floor); |
| case wasm::kExprF32x4Trunc: |
| return EmitSimdFloatRoundingOpWithCFallback<kF32>( |
| &LiftoffAssembler::emit_f32x4_trunc, |
| ExternalReference::wasm_f32x4_trunc); |
| case wasm::kExprF32x4NearestInt: |
| return EmitSimdFloatRoundingOpWithCFallback<kF32>( |
| &LiftoffAssembler::emit_f32x4_nearest_int, |
| ExternalReference::wasm_f32x4_nearest_int); |
| case wasm::kExprF32x4Add: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_add); |
| case wasm::kExprF32x4Sub: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_sub); |
| case wasm::kExprF32x4Mul: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_mul); |
| case wasm::kExprF32x4Div: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_div); |
| case wasm::kExprF32x4Min: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_min); |
| case wasm::kExprF32x4Max: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_max); |
| case wasm::kExprF32x4Pmin: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_pmin); |
| case wasm::kExprF32x4Pmax: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_pmax); |
| case wasm::kExprF64x2Abs: |
| return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_abs); |
| case wasm::kExprF64x2Neg: |
| return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_neg); |
| case wasm::kExprF64x2Sqrt: |
| return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_sqrt); |
| case wasm::kExprF64x2Ceil: |
| return EmitSimdFloatRoundingOpWithCFallback<kF64>( |
| &LiftoffAssembler::emit_f64x2_ceil, |
| &ExternalReference::wasm_f64x2_ceil); |
| case wasm::kExprF64x2Floor: |
| return EmitSimdFloatRoundingOpWithCFallback<kF64>( |
| &LiftoffAssembler::emit_f64x2_floor, |
| ExternalReference::wasm_f64x2_floor); |
| case wasm::kExprF64x2Trunc: |
| return EmitSimdFloatRoundingOpWithCFallback<kF64>( |
| &LiftoffAssembler::emit_f64x2_trunc, |
| ExternalReference::wasm_f64x2_trunc); |
| case wasm::kExprF64x2NearestInt: |
| return EmitSimdFloatRoundingOpWithCFallback<kF64>( |
| &LiftoffAssembler::emit_f64x2_nearest_int, |
| ExternalReference::wasm_f64x2_nearest_int); |
| case wasm::kExprF64x2Add: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_add); |
| case wasm::kExprF64x2Sub: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_sub); |
| case wasm::kExprF64x2Mul: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_mul); |
| case wasm::kExprF64x2Div: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_div); |
| case wasm::kExprF64x2Min: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_min); |
| case wasm::kExprF64x2Max: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_max); |
| case wasm::kExprF64x2Pmin: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_pmin); |
| case wasm::kExprF64x2Pmax: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_pmax); |
| case wasm::kExprI32x4SConvertF32x4: |
| return EmitUnOp<kS128, kS128, kF32>( |
| &LiftoffAssembler::emit_i32x4_sconvert_f32x4); |
| case wasm::kExprI32x4UConvertF32x4: |
| return EmitUnOp<kS128, kS128, kF32>( |
| &LiftoffAssembler::emit_i32x4_uconvert_f32x4); |
| case wasm::kExprF32x4SConvertI32x4: |
| return EmitUnOp<kS128, kS128, kF32>( |
| &LiftoffAssembler::emit_f32x4_sconvert_i32x4); |
| case wasm::kExprF32x4UConvertI32x4: |
| return EmitUnOp<kS128, kS128, kF32>( |
| &LiftoffAssembler::emit_f32x4_uconvert_i32x4); |
| case wasm::kExprI8x16SConvertI16x8: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i8x16_sconvert_i16x8); |
| case wasm::kExprI8x16UConvertI16x8: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i8x16_uconvert_i16x8); |
| case wasm::kExprI16x8SConvertI32x4: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_sconvert_i32x4); |
| case wasm::kExprI16x8UConvertI32x4: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_uconvert_i32x4); |
| case wasm::kExprI16x8SConvertI8x16Low: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_sconvert_i8x16_low); |
| case wasm::kExprI16x8SConvertI8x16High: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_sconvert_i8x16_high); |
| case wasm::kExprI16x8UConvertI8x16Low: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_uconvert_i8x16_low); |
| case wasm::kExprI16x8UConvertI8x16High: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_uconvert_i8x16_high); |
| case wasm::kExprI32x4SConvertI16x8Low: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_sconvert_i16x8_low); |
| case wasm::kExprI32x4SConvertI16x8High: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_sconvert_i16x8_high); |
| case wasm::kExprI32x4UConvertI16x8Low: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_uconvert_i16x8_low); |
| case wasm::kExprI32x4UConvertI16x8High: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_uconvert_i16x8_high); |
| case wasm::kExprS128AndNot: |
| return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_and_not); |
| case wasm::kExprI8x16RoundingAverageU: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i8x16_rounding_average_u); |
| case wasm::kExprI16x8RoundingAverageU: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_rounding_average_u); |
| case wasm::kExprI8x16Abs: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_abs); |
| case wasm::kExprI16x8Abs: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_abs); |
| case wasm::kExprI32x4Abs: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_abs); |
| case wasm::kExprI64x2Abs: |
| return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_abs); |
| case wasm::kExprF64x2ConvertLowI32x4S: |
| return EmitUnOp<kS128, kS128, kF64>( |
| &LiftoffAssembler::emit_f64x2_convert_low_i32x4_s); |
| case wasm::kExprF64x2ConvertLowI32x4U: |
| return EmitUnOp<kS128, kS128, kF64>( |
| &LiftoffAssembler::emit_f64x2_convert_low_i32x4_u); |
| case wasm::kExprF64x2PromoteLowF32x4: |
| return EmitUnOp<kS128, kS128, kF64>( |
| &LiftoffAssembler::emit_f64x2_promote_low_f32x4); |
| case wasm::kExprF32x4DemoteF64x2Zero: |
| return EmitUnOp<kS128, kS128, kF32>( |
| &LiftoffAssembler::emit_f32x4_demote_f64x2_zero); |
| case wasm::kExprI32x4TruncSatF64x2SZero: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_trunc_sat_f64x2_s_zero); |
| case wasm::kExprI32x4TruncSatF64x2UZero: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_trunc_sat_f64x2_u_zero); |
| case wasm::kExprF32x4Qfma: |
| return EmitSimdFmaOp(&LiftoffAssembler::emit_f32x4_qfma); |
| case wasm::kExprF32x4Qfms: |
| return EmitSimdFmaOp(&LiftoffAssembler::emit_f32x4_qfms); |
| case wasm::kExprF64x2Qfma: |
| return EmitSimdFmaOp(&LiftoffAssembler::emit_f64x2_qfma); |
| case wasm::kExprF64x2Qfms: |
| return EmitSimdFmaOp(&LiftoffAssembler::emit_f64x2_qfms); |
| case wasm::kExprI16x8RelaxedLaneSelect: |
| case wasm::kExprI8x16RelaxedLaneSelect: |
| case wasm::kExprI32x4RelaxedLaneSelect: |
| case wasm::kExprI64x2RelaxedLaneSelect: |
| return EmitRelaxedLaneSelect(); |
| case wasm::kExprF32x4RelaxedMin: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_relaxed_min); |
| case wasm::kExprF32x4RelaxedMax: |
| return EmitBinOp<kS128, kS128, false, kF32>( |
| &LiftoffAssembler::emit_f32x4_relaxed_max); |
| case wasm::kExprF64x2RelaxedMin: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_relaxed_min); |
| case wasm::kExprF64x2RelaxedMax: |
| return EmitBinOp<kS128, kS128, false, kF64>( |
| &LiftoffAssembler::emit_f64x2_relaxed_max); |
| case wasm::kExprI16x8RelaxedQ15MulRS: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_relaxed_q15mulr_s); |
| case wasm::kExprI32x4RelaxedTruncF32x4S: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_relaxed_trunc_f32x4_s); |
| case wasm::kExprI32x4RelaxedTruncF32x4U: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_relaxed_trunc_f32x4_u); |
| case wasm::kExprI32x4RelaxedTruncF64x2SZero: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_relaxed_trunc_f64x2_s_zero); |
| case wasm::kExprI32x4RelaxedTruncF64x2UZero: |
| return EmitUnOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i32x4_relaxed_trunc_f64x2_u_zero); |
| case wasm::kExprI16x8DotI8x16I7x16S: |
| return EmitBinOp<kS128, kS128>( |
| &LiftoffAssembler::emit_i16x8_dot_i8x16_i7x16_s); |
| case wasm::kExprI32x4DotI8x16I7x16AddS: { |
| // There is no helper for an instruction with 3 SIMD operands |
| // and we do not expect to add any more, so inlining it here. |
| static constexpr RegClass res_rc = reg_class_for(kS128); |
| LiftoffRegList pinned; |
| LiftoffRegister acc = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister rhs = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister lhs = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister dst = __ GetUnusedRegister(res_rc, {lhs, rhs, acc}, {}); |
| |
| __ emit_i32x4_dot_i8x16_i7x16_add_s(dst, lhs, rhs, acc); |
| __ PushRegister(kS128, dst); |
| return; |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| template <ValueKind src_kind, ValueKind result_kind, typename EmitFn> |
| void EmitSimdExtractLaneOp(EmitFn fn, const SimdLaneImmediate& imm) { |
| static constexpr RegClass src_rc = reg_class_for(src_kind); |
| static constexpr RegClass result_rc = reg_class_for(result_kind); |
| LiftoffRegister lhs = __ PopToRegister(); |
| LiftoffRegister dst = src_rc == result_rc |
| ? __ GetUnusedRegister(result_rc, {lhs}, {}) |
| : __ GetUnusedRegister(result_rc, {}); |
| fn(dst, lhs, imm.lane); |
| __ PushRegister(result_kind, dst); |
| } |
| |
| template <ValueKind src2_kind, typename EmitFn> |
| void EmitSimdReplaceLaneOp(EmitFn fn, const SimdLaneImmediate& imm) { |
| static constexpr RegClass src1_rc = reg_class_for(kS128); |
| static constexpr RegClass src2_rc = reg_class_for(src2_kind); |
| static constexpr RegClass result_rc = reg_class_for(kS128); |
| // On backends which need fp pair, src1_rc and result_rc end up being |
| // kFpRegPair, which is != kFpReg, but we still want to pin src2 when it is |
| // kFpReg, since it can overlap with those pairs. |
| static constexpr bool pin_src2 = kNeedS128RegPair && src2_rc == kFpReg; |
| |
| // Does not work for arm |
| LiftoffRegister src2 = __ PopToRegister(); |
| LiftoffRegister src1 = (src1_rc == src2_rc || pin_src2) |
| ? __ PopToRegister(LiftoffRegList{src2}) |
| : __ |
| PopToRegister(); |
| LiftoffRegister dst = |
| (src2_rc == result_rc || pin_src2) |
| ? __ GetUnusedRegister(result_rc, {src1}, LiftoffRegList{src2}) |
| : __ GetUnusedRegister(result_rc, {src1}, {}); |
| fn(dst, src1, src2, imm.lane); |
| __ PushRegister(kS128, dst); |
| } |
| |
| void SimdLaneOp(FullDecoder* decoder, WasmOpcode opcode, |
| const SimdLaneImmediate& imm, |
| const base::Vector<Value> inputs, Value* result) { |
| if (!CpuFeatures::SupportsWasmSimd128()) { |
| return unsupported(decoder, kSimd, "simd"); |
| } |
| switch (opcode) { |
| #define CASE_SIMD_EXTRACT_LANE_OP(opcode, kind, fn) \ |
| case wasm::kExpr##opcode: \ |
| EmitSimdExtractLaneOp<kS128, k##kind>( \ |
| [this](LiftoffRegister dst, LiftoffRegister lhs, \ |
| uint8_t imm_lane_idx) { \ |
| __ emit_##fn(dst, lhs, imm_lane_idx); \ |
| }, \ |
| imm); \ |
| break; |
| CASE_SIMD_EXTRACT_LANE_OP(I8x16ExtractLaneS, I32, i8x16_extract_lane_s) |
| CASE_SIMD_EXTRACT_LANE_OP(I8x16ExtractLaneU, I32, i8x16_extract_lane_u) |
| CASE_SIMD_EXTRACT_LANE_OP(I16x8ExtractLaneS, I32, i16x8_extract_lane_s) |
| CASE_SIMD_EXTRACT_LANE_OP(I16x8ExtractLaneU, I32, i16x8_extract_lane_u) |
| CASE_SIMD_EXTRACT_LANE_OP(I32x4ExtractLane, I32, i32x4_extract_lane) |
| CASE_SIMD_EXTRACT_LANE_OP(I64x2ExtractLane, I64, i64x2_extract_lane) |
| CASE_SIMD_EXTRACT_LANE_OP(F32x4ExtractLane, F32, f32x4_extract_lane) |
| CASE_SIMD_EXTRACT_LANE_OP(F64x2ExtractLane, F64, f64x2_extract_lane) |
| #undef CASE_SIMD_EXTRACT_LANE_OP |
| #define CASE_SIMD_REPLACE_LANE_OP(opcode, kind, fn) \ |
| case wasm::kExpr##opcode: \ |
| EmitSimdReplaceLaneOp<k##kind>( \ |
| [this](LiftoffRegister dst, LiftoffRegister src1, \ |
| LiftoffRegister src2, uint8_t imm_lane_idx) { \ |
| __ emit_##fn(dst, src1, src2, imm_lane_idx); \ |
| }, \ |
| imm); \ |
| break; |
| CASE_SIMD_REPLACE_LANE_OP(I8x16ReplaceLane, I32, i8x16_replace_lane) |
| CASE_SIMD_REPLACE_LANE_OP(I16x8ReplaceLane, I32, i16x8_replace_lane) |
| CASE_SIMD_REPLACE_LANE_OP(I32x4ReplaceLane, I32, i32x4_replace_lane) |
| CASE_SIMD_REPLACE_LANE_OP(I64x2ReplaceLane, I64, i64x2_replace_lane) |
| CASE_SIMD_REPLACE_LANE_OP(F32x4ReplaceLane, F32, f32x4_replace_lane) |
| CASE_SIMD_REPLACE_LANE_OP(F64x2ReplaceLane, F64, f64x2_replace_lane) |
| #undef CASE_SIMD_REPLACE_LANE_OP |
| default: |
| unsupported(decoder, kSimd, "simd"); |
| } |
| } |
| |
| void S128Const(FullDecoder* decoder, const Simd128Immediate& imm, |
| Value* result) { |
| if (!CpuFeatures::SupportsWasmSimd128()) { |
| return unsupported(decoder, kSimd, "simd"); |
| } |
| constexpr RegClass result_rc = reg_class_for(kS128); |
| LiftoffRegister dst = __ GetUnusedRegister(result_rc, {}); |
| bool all_zeroes = std::all_of(std::begin(imm.value), std::end(imm.value), |
| [](uint8_t v) { return v == 0; }); |
| bool all_ones = std::all_of(std::begin(imm.value), std::end(imm.value), |
| [](uint8_t v) { return v == 0xff; }); |
| if (all_zeroes) { |
| __ LiftoffAssembler::emit_s128_xor(dst, dst, dst); |
| } else if (all_ones) { |
| // Any SIMD eq will work, i32x4 is efficient on all archs. |
| __ LiftoffAssembler::emit_i32x4_eq(dst, dst, dst); |
| } else { |
| __ LiftoffAssembler::emit_s128_const(dst, imm.value); |
| } |
| __ PushRegister(kS128, dst); |
| } |
| |
| void Simd8x16ShuffleOp(FullDecoder* decoder, const Simd128Immediate& imm, |
| const Value& input0, const Value& input1, |
| Value* result) { |
| if (!CpuFeatures::SupportsWasmSimd128()) { |
| return unsupported(decoder, kSimd, "simd"); |
| } |
| static constexpr RegClass result_rc = reg_class_for(kS128); |
| LiftoffRegList pinned; |
| LiftoffRegister rhs = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister lhs = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister dst = __ GetUnusedRegister(result_rc, {lhs, rhs}, {}); |
| |
| uint8_t shuffle[kSimd128Size]; |
| memcpy(shuffle, imm.value, sizeof(shuffle)); |
| bool is_swizzle; |
| bool needs_swap; |
| wasm::SimdShuffle::CanonicalizeShuffle(lhs == rhs, shuffle, &needs_swap, |
| &is_swizzle); |
| if (needs_swap) { |
| std::swap(lhs, rhs); |
| } |
| __ LiftoffAssembler::emit_i8x16_shuffle(dst, lhs, rhs, shuffle, is_swizzle); |
| __ PushRegister(kS128, dst); |
| } |
| |
| void ToSmi(Register reg) { |
| if (COMPRESS_POINTERS_BOOL || kSystemPointerSize == 4) { |
| __ emit_i32_shli(reg, reg, kSmiShiftSize + kSmiTagSize); |
| } else { |
| __ emit_i64_shli(LiftoffRegister{reg}, LiftoffRegister{reg}, |
| kSmiShiftSize + kSmiTagSize); |
| } |
| } |
| |
| void Store32BitExceptionValue(Register values_array, int* index_in_array, |
| Register value, LiftoffRegList pinned) { |
| LiftoffRegister tmp_reg = __ GetUnusedRegister(kGpReg, pinned); |
| // Get the lower half word into tmp_reg and extend to a Smi. |
| --*index_in_array; |
| __ emit_i32_andi(tmp_reg.gp(), value, 0xffff); |
| ToSmi(tmp_reg.gp()); |
| __ StoreTaggedPointer( |
| values_array, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index_in_array), |
| tmp_reg, pinned, LiftoffAssembler::kSkipWriteBarrier); |
| |
| // Get the upper half word into tmp_reg and extend to a Smi. |
| --*index_in_array; |
| __ emit_i32_shri(tmp_reg.gp(), value, 16); |
| ToSmi(tmp_reg.gp()); |
| __ StoreTaggedPointer( |
| values_array, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index_in_array), |
| tmp_reg, pinned, LiftoffAssembler::kSkipWriteBarrier); |
| } |
| |
| void Store64BitExceptionValue(Register values_array, int* index_in_array, |
| LiftoffRegister value, LiftoffRegList pinned) { |
| if (kNeedI64RegPair) { |
| Store32BitExceptionValue(values_array, index_in_array, value.low_gp(), |
| pinned); |
| Store32BitExceptionValue(values_array, index_in_array, value.high_gp(), |
| pinned); |
| } else { |
| Store32BitExceptionValue(values_array, index_in_array, value.gp(), |
| pinned); |
| __ emit_i64_shri(value, value, 32); |
| Store32BitExceptionValue(values_array, index_in_array, value.gp(), |
| pinned); |
| } |
| } |
| |
| void Load16BitExceptionValue(LiftoffRegister dst, |
| LiftoffRegister values_array, uint32_t* index, |
| LiftoffRegList pinned) { |
| __ LoadSmiAsInt32( |
| dst, values_array.gp(), |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index)); |
| (*index)++; |
| } |
| |
| void Load32BitExceptionValue(Register dst, LiftoffRegister values_array, |
| uint32_t* index, LiftoffRegList pinned) { |
| LiftoffRegister upper = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| Load16BitExceptionValue(upper, values_array, index, pinned); |
| __ emit_i32_shli(upper.gp(), upper.gp(), 16); |
| Load16BitExceptionValue(LiftoffRegister(dst), values_array, index, pinned); |
| __ emit_i32_or(dst, upper.gp(), dst); |
| } |
| |
| void Load64BitExceptionValue(LiftoffRegister dst, |
| LiftoffRegister values_array, uint32_t* index, |
| LiftoffRegList pinned) { |
| if (kNeedI64RegPair) { |
| Load32BitExceptionValue(dst.high_gp(), values_array, index, pinned); |
| Load32BitExceptionValue(dst.low_gp(), values_array, index, pinned); |
| } else { |
| Load16BitExceptionValue(dst, values_array, index, pinned); |
| __ emit_i64_shli(dst, dst, 48); |
| LiftoffRegister tmp_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| Load16BitExceptionValue(tmp_reg, values_array, index, pinned); |
| __ emit_i64_shli(tmp_reg, tmp_reg, 32); |
| __ emit_i64_or(dst, tmp_reg, dst); |
| Load16BitExceptionValue(tmp_reg, values_array, index, pinned); |
| __ emit_i64_shli(tmp_reg, tmp_reg, 16); |
| __ emit_i64_or(dst, tmp_reg, dst); |
| Load16BitExceptionValue(tmp_reg, values_array, index, pinned); |
| __ emit_i64_or(dst, tmp_reg, dst); |
| } |
| } |
| |
| void StoreExceptionValue(ValueType type, Register values_array, |
| int* index_in_array, LiftoffRegList pinned) { |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| switch (type.kind()) { |
| case kI32: |
| Store32BitExceptionValue(values_array, index_in_array, value.gp(), |
| pinned); |
| break; |
| case kF32: { |
| LiftoffRegister gp_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ emit_type_conversion(kExprI32ReinterpretF32, gp_reg, value, nullptr); |
| Store32BitExceptionValue(values_array, index_in_array, gp_reg.gp(), |
| pinned); |
| break; |
| } |
| case kI64: |
| Store64BitExceptionValue(values_array, index_in_array, value, pinned); |
| break; |
| case kF64: { |
| LiftoffRegister tmp_reg = |
| pinned.set(__ GetUnusedRegister(reg_class_for(kI64), pinned)); |
| __ emit_type_conversion(kExprI64ReinterpretF64, tmp_reg, value, |
| nullptr); |
| Store64BitExceptionValue(values_array, index_in_array, tmp_reg, pinned); |
| break; |
| } |
| case kS128: { |
| LiftoffRegister tmp_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| for (int i : {3, 2, 1, 0}) { |
| __ emit_i32x4_extract_lane(tmp_reg, value, i); |
| Store32BitExceptionValue(values_array, index_in_array, tmp_reg.gp(), |
| pinned); |
| } |
| break; |
| } |
| case wasm::kRef: |
| case wasm::kRefNull: |
| case wasm::kRtt: { |
| --(*index_in_array); |
| __ StoreTaggedPointer( |
| values_array, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( |
| *index_in_array), |
| value, pinned); |
| break; |
| } |
| case wasm::kI8: |
| case wasm::kI16: |
| case wasm::kVoid: |
| case wasm::kBottom: |
| UNREACHABLE(); |
| } |
| } |
| |
| void LoadExceptionValue(ValueKind kind, LiftoffRegister values_array, |
| uint32_t* index, LiftoffRegList pinned) { |
| RegClass rc = reg_class_for(kind); |
| LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); |
| switch (kind) { |
| case kI32: |
| Load32BitExceptionValue(value.gp(), values_array, index, pinned); |
| break; |
| case kF32: { |
| LiftoffRegister tmp_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned); |
| __ emit_type_conversion(kExprF32ReinterpretI32, value, tmp_reg, |
| nullptr); |
| break; |
| } |
| case kI64: |
| Load64BitExceptionValue(value, values_array, index, pinned); |
| break; |
| case kF64: { |
| RegClass rc_i64 = reg_class_for(kI64); |
| LiftoffRegister tmp_reg = |
| pinned.set(__ GetUnusedRegister(rc_i64, pinned)); |
| Load64BitExceptionValue(tmp_reg, values_array, index, pinned); |
| __ emit_type_conversion(kExprF64ReinterpretI64, value, tmp_reg, |
| nullptr); |
| break; |
| } |
| case kS128: { |
| LiftoffRegister tmp_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned); |
| __ emit_i32x4_splat(value, tmp_reg); |
| for (int lane : {1, 2, 3}) { |
| Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned); |
| __ emit_i32x4_replace_lane(value, value, tmp_reg, lane); |
| } |
| break; |
| } |
| case wasm::kRef: |
| case wasm::kRefNull: |
| case wasm::kRtt: { |
| __ LoadTaggedPointer( |
| value.gp(), values_array.gp(), no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index)); |
| (*index)++; |
| break; |
| } |
| case wasm::kI8: |
| case wasm::kI16: |
| case wasm::kVoid: |
| case wasm::kBottom: |
| UNREACHABLE(); |
| } |
| __ PushRegister(kind, value); |
| } |
| |
| void GetExceptionValues(FullDecoder* decoder, |
| const LiftoffAssembler::VarState& exception_var, |
| const WasmTag* tag) { |
| LiftoffRegList pinned; |
| CODE_COMMENT("get exception values"); |
| LiftoffRegister values_array = GetExceptionProperty( |
| exception_var, RootIndex::kwasm_exception_values_symbol); |
| pinned.set(values_array); |
| uint32_t index = 0; |
| const WasmTagSig* sig = tag->sig; |
| for (ValueType param : sig->parameters()) { |
| LoadExceptionValue(param.kind(), values_array, &index, pinned); |
| } |
| DCHECK_EQ(index, WasmExceptionPackage::GetEncodedSize(tag)); |
| } |
| |
| void EmitLandingPad(FullDecoder* decoder, int handler_offset) { |
| if (decoder->current_catch() == -1) return; |
| MovableLabel handler{zone_}; |
| |
| // If we return from the throwing code normally, just skip over the handler. |
| Label skip_handler; |
| __ emit_jump(&skip_handler); |
| |
| // Handler: merge into the catch state, and jump to the catch body. |
| CODE_COMMENT("-- landing pad --"); |
| __ bind(handler.get()); |
| __ ExceptionHandler(); |
| __ PushException(); |
| handlers_.push_back({std::move(handler), handler_offset}); |
| Control* current_try = |
| decoder->control_at(decoder->control_depth_of_current_catch()); |
| DCHECK_NOT_NULL(current_try->try_info); |
| if (current_try->try_info->catch_reached) { |
| __ MergeStackWith(current_try->try_info->catch_state, 1, |
| LiftoffAssembler::kForwardJump); |
| } else { |
| current_try->try_info->catch_state = __ MergeIntoNewState( |
| __ num_locals(), 1, |
| current_try->stack_depth + current_try->num_exceptions); |
| current_try->try_info->catch_reached = true; |
| } |
| __ emit_jump(¤t_try->try_info->catch_label); |
| |
| __ bind(&skip_handler); |
| // Drop the exception. |
| __ DropValues(1); |
| } |
| |
| void Throw(FullDecoder* decoder, const TagIndexImmediate& imm, |
| const base::Vector<Value>& /* args */) { |
| LiftoffRegList pinned; |
| |
| // Load the encoded size in a register for the builtin call. |
| int encoded_size = WasmExceptionPackage::GetEncodedSize(imm.tag); |
| LiftoffRegister encoded_size_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(encoded_size_reg, WasmValue::ForUintPtr(encoded_size)); |
| |
| // Call the WasmAllocateFixedArray builtin to create the values array. |
| CallRuntimeStub(WasmCode::kWasmAllocateFixedArray, |
| MakeSig::Returns(kIntPtrKind).Params(kIntPtrKind), |
| {LiftoffAssembler::VarState{ |
| kIntPtrKind, LiftoffRegister{encoded_size_reg}, 0}}, |
| decoder->position()); |
| MaybeOSR(); |
| |
| // The FixedArray for the exception values is now in the first gp return |
| // register. |
| LiftoffRegister values_array{kReturnRegister0}; |
| pinned.set(values_array); |
| |
| // Now store the exception values in the FixedArray. Do this from last to |
| // first value, such that we can just pop them from the value stack. |
| CODE_COMMENT("fill values array"); |
| int index = encoded_size; |
| auto* sig = imm.tag->sig; |
| for (size_t param_idx = sig->parameter_count(); param_idx > 0; |
| --param_idx) { |
| ValueType type = sig->GetParam(param_idx - 1); |
| StoreExceptionValue(type, values_array.gp(), &index, pinned); |
| } |
| DCHECK_EQ(0, index); |
| |
| // Load the exception tag. |
| CODE_COMMENT("load exception tag"); |
| LiftoffRegister exception_tag = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(exception_tag.gp(), TagsTable, pinned); |
| __ LoadTaggedPointer( |
| exception_tag.gp(), exception_tag.gp(), no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index)); |
| |
| // Finally, call WasmThrow. |
| CallRuntimeStub(WasmCode::kWasmThrow, |
| MakeSig::Params(kIntPtrKind, kIntPtrKind), |
| {LiftoffAssembler::VarState{kIntPtrKind, exception_tag, 0}, |
| LiftoffAssembler::VarState{kIntPtrKind, values_array, 0}}, |
| decoder->position()); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| int pc_offset = __ pc_offset(); |
| MaybeOSR(); |
| EmitLandingPad(decoder, pc_offset); |
| } |
| |
| void AtomicStoreMem(FullDecoder* decoder, StoreType type, |
| const MemoryAccessImmediate& imm) { |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister()); |
| bool i64_offset = __ cache_state()->stack_state.back().kind() == kI64; |
| LiftoffRegister full_index = __ PopToRegister(pinned); |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, pinned, kDoForceCheck); |
| |
| pinned.set(index); |
| AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); |
| uintptr_t offset = imm.offset; |
| CODE_COMMENT("atomic store to memory"); |
| Register addr = pinned.set(GetMemoryStart(pinned)); |
| LiftoffRegList outer_pinned; |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) outer_pinned.set(index); |
| __ AtomicStore(addr, index, offset, value, type, outer_pinned, i64_offset); |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| TraceMemoryOperation(true, type.mem_rep(), index, offset, |
| decoder->position()); |
| } |
| } |
| |
| void AtomicLoadMem(FullDecoder* decoder, LoadType type, |
| const MemoryAccessImmediate& imm) { |
| ValueKind kind = type.value_type().kind(); |
| bool i64_offset = __ cache_state()->stack_state.back().kind() == kI64; |
| LiftoffRegister full_index = __ PopToRegister(); |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, {}, kDoForceCheck); |
| |
| LiftoffRegList pinned{index}; |
| AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); |
| uintptr_t offset = imm.offset; |
| CODE_COMMENT("atomic load from memory"); |
| Register addr = pinned.set(GetMemoryStart(pinned)); |
| RegClass rc = reg_class_for(kind); |
| LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); |
| __ AtomicLoad(value, addr, index, offset, type, pinned, i64_offset); |
| __ PushRegister(kind, value); |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) { |
| TraceMemoryOperation(false, type.mem_type().representation(), index, |
| offset, decoder->position()); |
| } |
| } |
| |
| void AtomicBinop(FullDecoder* decoder, StoreType type, |
| const MemoryAccessImmediate& imm, |
| void (LiftoffAssembler::*emit_fn)(Register, Register, |
| uintptr_t, LiftoffRegister, |
| LiftoffRegister, StoreType, |
| bool)) { |
| ValueKind result_kind = type.value_type().kind(); |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister()); |
| #ifdef V8_TARGET_ARCH_IA32 |
| // We have to reuse the value register as the result register so that we |
| // don't run out of registers on ia32. For this we use the value register as |
| // the result register if it has no other uses. Otherwise we allocate a new |
| // register and let go of the value register to get spilled. |
| LiftoffRegister result = value; |
| if (__ cache_state()->is_used(value)) { |
| result = pinned.set(__ GetUnusedRegister(value.reg_class(), pinned)); |
| __ Move(result, value, result_kind); |
| pinned.clear(value); |
| value = result; |
| } |
| #else |
| LiftoffRegister result = |
| pinned.set(__ GetUnusedRegister(value.reg_class(), pinned)); |
| #endif |
| bool i64_offset = __ cache_state()->stack_state.back().kind() == kI64; |
| LiftoffRegister full_index = __ PopToRegister(pinned); |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, pinned, kDoForceCheck); |
| |
| pinned.set(index); |
| AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); |
| |
| CODE_COMMENT("atomic binop"); |
| uintptr_t offset = imm.offset; |
| Register addr = pinned.set(GetMemoryStart(pinned)); |
| |
| (asm_.*emit_fn)(addr, index, offset, value, result, type, i64_offset); |
| __ PushRegister(result_kind, result); |
| } |
| |
| void AtomicCompareExchange(FullDecoder* decoder, StoreType type, |
| const MemoryAccessImmediate& imm) { |
| #ifdef V8_TARGET_ARCH_IA32 |
| // On ia32 we don't have enough registers to first pop all the values off |
| // the stack and then start with the code generation. Instead we do the |
| // complete address calculation first, so that the address only needs a |
| // single register. Afterwards we load all remaining values into the |
| // other registers. |
| LiftoffRegister full_index = __ PeekToRegister(2, {}); |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, {}, kDoForceCheck); |
| LiftoffRegList pinned{index}; |
| AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); |
| |
| uintptr_t offset = imm.offset; |
| Register addr = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_INSTANCE_FIELD(addr, MemoryStart, kSystemPointerSize, pinned); |
| #ifdef V8_ENABLE_SANDBOX |
| __ DecodeSandboxedPointer(addr); |
| #endif |
| __ emit_i32_add(addr, addr, index); |
| pinned.clear(LiftoffRegister(index)); |
| LiftoffRegister new_value = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister expected = pinned.set(__ PopToRegister(pinned)); |
| |
| // Pop the index from the stack. |
| bool i64_offset = __ cache_state()->stack_state.back().kind() == kI64; |
| __ DropValues(1); |
| |
| LiftoffRegister result = expected; |
| if (__ cache_state()->is_used(result)) __ SpillRegister(result); |
| |
| // We already added the index to addr, so we can just pass no_reg to the |
| // assembler now. |
| __ AtomicCompareExchange(addr, no_reg, offset, expected, new_value, result, |
| type, i64_offset); |
| __ PushRegister(type.value_type().kind(), result); |
| return; |
| #else |
| ValueKind result_kind = type.value_type().kind(); |
| LiftoffRegList pinned; |
| LiftoffRegister new_value = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister expected = pinned.set(__ PopToRegister(pinned)); |
| bool i64_offset = __ cache_state()->stack_state.back().kind() == kI64; |
| LiftoffRegister full_index = __ PopToRegister(pinned); |
| Register index = BoundsCheckMem(decoder, type.size(), imm.offset, |
| full_index, pinned, kDoForceCheck); |
| pinned.set(index); |
| AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); |
| |
| uintptr_t offset = imm.offset; |
| Register addr = pinned.set(GetMemoryStart(pinned)); |
| LiftoffRegister result = |
| pinned.set(__ GetUnusedRegister(reg_class_for(result_kind), pinned)); |
| |
| __ AtomicCompareExchange(addr, index, offset, expected, new_value, result, |
| type, i64_offset); |
| __ PushRegister(result_kind, result); |
| #endif |
| } |
| |
| void CallRuntimeStub(WasmCode::RuntimeStubId stub_id, const ValueKindSig& sig, |
| std::initializer_list<LiftoffAssembler::VarState> params, |
| int position) { |
| CODE_COMMENT( |
| (std::string{"call builtin: "} + GetRuntimeStubName(stub_id)).c_str()); |
| auto interface_descriptor = Builtins::CallInterfaceDescriptorFor( |
| RuntimeStubIdToBuiltinName(stub_id)); |
| auto* call_descriptor = compiler::Linkage::GetStubCallDescriptor( |
| zone_, // zone |
| interface_descriptor, // descriptor |
| interface_descriptor.GetStackParameterCount(), // stack parameter count |
| compiler::CallDescriptor::kNoFlags, // flags |
| compiler::Operator::kNoProperties, // properties |
| StubCallMode::kCallWasmRuntimeStub); // stub call mode |
| |
| __ PrepareBuiltinCall(&sig, call_descriptor, params); |
| if (position != kNoSourcePosition) { |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(position), true); |
| } |
| __ CallRuntimeStub(stub_id); |
| DefineSafepoint(); |
| } |
| |
| void AtomicWait(FullDecoder* decoder, ValueKind kind, |
| const MemoryAccessImmediate& imm) { |
| { |
| LiftoffRegList pinned; |
| LiftoffRegister full_index = __ PeekToRegister(2, pinned); |
| Register index_reg = |
| BoundsCheckMem(decoder, value_kind_size(kind), imm.offset, full_index, |
| pinned, kDoForceCheck); |
| pinned.set(index_reg); |
| AlignmentCheckMem(decoder, value_kind_size(kind), imm.offset, index_reg, |
| pinned); |
| |
| uintptr_t offset = imm.offset; |
| Register index_plus_offset = index_reg; |
| |
| if (__ cache_state()->is_used(LiftoffRegister(index_reg))) { |
| index_plus_offset = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| __ Move(index_plus_offset, index_reg, kIntPtrKind); |
| } |
| if (offset) { |
| __ emit_ptrsize_addi(index_plus_offset, index_plus_offset, offset); |
| } |
| |
| LiftoffAssembler::VarState& index = |
| __ cache_state()->stack_state.end()[-3]; |
| |
| // We replace the index on the value stack with the `index_plus_offset` |
| // calculated above. Thereby the BigInt allocation below does not |
| // overwrite the calculated value by accident. |
| __ cache_state()->inc_used(LiftoffRegister(index_plus_offset)); |
| if (index.is_reg()) __ cache_state()->dec_used(index.reg()); |
| |
| index = LiftoffAssembler::VarState{ |
| kIntPtrKind, LiftoffRegister{index_plus_offset}, index.offset()}; |
| } |
| { |
| // Convert the top value of the stack (the timeout) from I64 to a BigInt, |
| // which we can then pass to the atomic.wait builtin. |
| LiftoffAssembler::VarState i64_timeout = |
| __ cache_state()->stack_state.back(); |
| CallRuntimeStub( |
| kNeedI64RegPair ? WasmCode::kI32PairToBigInt : WasmCode::kI64ToBigInt, |
| MakeSig::Returns(kRef).Params(kI64), {i64_timeout}, |
| decoder->position()); |
| __ DropValues(1); |
| // We put the result on the value stack so that it gets preserved across |
| // a potential GC that may get triggered by the BigInt allocation below. |
| __ PushRegister(kRef, LiftoffRegister(kReturnRegister0)); |
| } |
| |
| Register expected = no_reg; |
| if (kind == kI32) { |
| expected = __ PeekToRegister(1, {}).gp(); |
| } else { |
| LiftoffAssembler::VarState i64_expected = |
| __ cache_state()->stack_state.end()[-2]; |
| CallRuntimeStub( |
| kNeedI64RegPair ? WasmCode::kI32PairToBigInt : WasmCode::kI64ToBigInt, |
| MakeSig::Returns(kRef).Params(kI64), {i64_expected}, |
| decoder->position()); |
| expected = kReturnRegister0; |
| } |
| ValueKind expected_kind = kind == kI32 ? kI32 : kRef; |
| |
| LiftoffAssembler::VarState timeout = |
| __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState expected_value{expected_kind, |
| LiftoffRegister{expected}, 0}; |
| LiftoffAssembler::VarState index = __ cache_state()->stack_state.end()[-3]; |
| |
| auto target = kind == kI32 ? WasmCode::kWasmI32AtomicWait |
| : WasmCode::kWasmI64AtomicWait; |
| |
| CallRuntimeStub(target, MakeSig::Params(kIntPtrKind, expected_kind, kRef), |
| {index, expected_value, timeout}, decoder->position()); |
| // Pop parameters from the value stack. |
| __ DropValues(3); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| __ PushRegister(kI32, LiftoffRegister(kReturnRegister0)); |
| } |
| |
| void AtomicNotify(FullDecoder* decoder, const MemoryAccessImmediate& imm) { |
| LiftoffRegister full_index = __ PeekToRegister(1, {}); |
| Register index_reg = BoundsCheckMem(decoder, kInt32Size, imm.offset, |
| full_index, {}, kDoForceCheck); |
| LiftoffRegList pinned{index_reg}; |
| AlignmentCheckMem(decoder, kInt32Size, imm.offset, index_reg, pinned); |
| |
| uintptr_t offset = imm.offset; |
| Register index_plus_offset = index_reg; |
| if (__ cache_state()->is_used(LiftoffRegister(index_reg))) { |
| index_plus_offset = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| __ Move(index_plus_offset, index_reg, kIntPtrKind); |
| } |
| if (offset) { |
| __ emit_ptrsize_addi(index_plus_offset, index_plus_offset, offset); |
| } |
| |
| LiftoffAssembler::VarState count = __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState index_plus_offset_input{ |
| kIntPtrKind, LiftoffRegister{index_plus_offset}, 0}; |
| |
| CallRuntimeStub(WasmCode::kWasmAtomicNotify, |
| MakeSig::Returns(kI32).Params(kIntPtrKind, kI32), |
| {index_plus_offset_input, count}, decoder->position()); |
| // Pop parameters from the value stack. |
| __ DropValues(2); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| __ PushRegister(kI32, LiftoffRegister(kReturnRegister0)); |
| } |
| |
| #define ATOMIC_STORE_LIST(V) \ |
| V(I32AtomicStore, kI32Store) \ |
| V(I64AtomicStore, kI64Store) \ |
| V(I32AtomicStore8U, kI32Store8) \ |
| V(I32AtomicStore16U, kI32Store16) \ |
| V(I64AtomicStore8U, kI64Store8) \ |
| V(I64AtomicStore16U, kI64Store16) \ |
| V(I64AtomicStore32U, kI64Store32) |
| |
| #define ATOMIC_LOAD_LIST(V) \ |
| V(I32AtomicLoad, kI32Load) \ |
| V(I64AtomicLoad, kI64Load) \ |
| V(I32AtomicLoad8U, kI32Load8U) \ |
| V(I32AtomicLoad16U, kI32Load16U) \ |
| V(I64AtomicLoad8U, kI64Load8U) \ |
| V(I64AtomicLoad16U, kI64Load16U) \ |
| V(I64AtomicLoad32U, kI64Load32U) |
| |
| #define ATOMIC_BINOP_INSTRUCTION_LIST(V) \ |
| V(Add, I32AtomicAdd, kI32Store) \ |
| V(Add, I64AtomicAdd, kI64Store) \ |
| V(Add, I32AtomicAdd8U, kI32Store8) \ |
| V(Add, I32AtomicAdd16U, kI32Store16) \ |
| V(Add, I64AtomicAdd8U, kI64Store8) \ |
| V(Add, I64AtomicAdd16U, kI64Store16) \ |
| V(Add, I64AtomicAdd32U, kI64Store32) \ |
| V(Sub, I32AtomicSub, kI32Store) \ |
| V(Sub, I64AtomicSub, kI64Store) \ |
| V(Sub, I32AtomicSub8U, kI32Store8) \ |
| V(Sub, I32AtomicSub16U, kI32Store16) \ |
| V(Sub, I64AtomicSub8U, kI64Store8) \ |
| V(Sub, I64AtomicSub16U, kI64Store16) \ |
| V(Sub, I64AtomicSub32U, kI64Store32) \ |
| V(And, I32AtomicAnd, kI32Store) \ |
| V(And, I64AtomicAnd, kI64Store) \ |
| V(And, I32AtomicAnd8U, kI32Store8) \ |
| V(And, I32AtomicAnd16U, kI32Store16) \ |
| V(And, I64AtomicAnd8U, kI64Store8) \ |
| V(And, I64AtomicAnd16U, kI64Store16) \ |
| V(And, I64AtomicAnd32U, kI64Store32) \ |
| V(Or, I32AtomicOr, kI32Store) \ |
| V(Or, I64AtomicOr, kI64Store) \ |
| V(Or, I32AtomicOr8U, kI32Store8) \ |
| V(Or, I32AtomicOr16U, kI32Store16) \ |
| V(Or, I64AtomicOr8U, kI64Store8) \ |
| V(Or, I64AtomicOr16U, kI64Store16) \ |
| V(Or, I64AtomicOr32U, kI64Store32) \ |
| V(Xor, I32AtomicXor, kI32Store) \ |
| V(Xor, I64AtomicXor, kI64Store) \ |
| V(Xor, I32AtomicXor8U, kI32Store8) \ |
| V(Xor, I32AtomicXor16U, kI32Store16) \ |
| V(Xor, I64AtomicXor8U, kI64Store8) \ |
| V(Xor, I64AtomicXor16U, kI64Store16) \ |
| V(Xor, I64AtomicXor32U, kI64Store32) \ |
| V(Exchange, I32AtomicExchange, kI32Store) \ |
| V(Exchange, I64AtomicExchange, kI64Store) \ |
| V(Exchange, I32AtomicExchange8U, kI32Store8) \ |
| V(Exchange, I32AtomicExchange16U, kI32Store16) \ |
| V(Exchange, I64AtomicExchange8U, kI64Store8) \ |
| V(Exchange, I64AtomicExchange16U, kI64Store16) \ |
| V(Exchange, I64AtomicExchange32U, kI64Store32) |
| |
| #define ATOMIC_COMPARE_EXCHANGE_LIST(V) \ |
| V(I32AtomicCompareExchange, kI32Store) \ |
| V(I64AtomicCompareExchange, kI64Store) \ |
| V(I32AtomicCompareExchange8U, kI32Store8) \ |
| V(I32AtomicCompareExchange16U, kI32Store16) \ |
| V(I64AtomicCompareExchange8U, kI64Store8) \ |
| V(I64AtomicCompareExchange16U, kI64Store16) \ |
| V(I64AtomicCompareExchange32U, kI64Store32) |
| |
| void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, |
| base::Vector<Value> args, const MemoryAccessImmediate& imm, |
| Value* result) { |
| switch (opcode) { |
| #define ATOMIC_STORE_OP(name, type) \ |
| case wasm::kExpr##name: \ |
| AtomicStoreMem(decoder, StoreType::type, imm); \ |
| break; |
| |
| ATOMIC_STORE_LIST(ATOMIC_STORE_OP) |
| #undef ATOMIC_STORE_OP |
| |
| #define ATOMIC_LOAD_OP(name, type) \ |
| case wasm::kExpr##name: \ |
| AtomicLoadMem(decoder, LoadType::type, imm); \ |
| break; |
| |
| ATOMIC_LOAD_LIST(ATOMIC_LOAD_OP) |
| #undef ATOMIC_LOAD_OP |
| |
| #define ATOMIC_BINOP_OP(op, name, type) \ |
| case wasm::kExpr##name: \ |
| AtomicBinop(decoder, StoreType::type, imm, &LiftoffAssembler::Atomic##op); \ |
| break; |
| |
| ATOMIC_BINOP_INSTRUCTION_LIST(ATOMIC_BINOP_OP) |
| #undef ATOMIC_BINOP_OP |
| |
| #define ATOMIC_COMPARE_EXCHANGE_OP(name, type) \ |
| case wasm::kExpr##name: \ |
| AtomicCompareExchange(decoder, StoreType::type, imm); \ |
| break; |
| |
| ATOMIC_COMPARE_EXCHANGE_LIST(ATOMIC_COMPARE_EXCHANGE_OP) |
| #undef ATOMIC_COMPARE_EXCHANGE_OP |
| |
| case kExprI32AtomicWait: |
| AtomicWait(decoder, kI32, imm); |
| break; |
| case kExprI64AtomicWait: |
| AtomicWait(decoder, kI64, imm); |
| break; |
| case kExprAtomicNotify: |
| AtomicNotify(decoder, imm); |
| break; |
| default: |
| unsupported(decoder, kAtomics, "atomicop"); |
| } |
| } |
| |
| #undef ATOMIC_STORE_LIST |
| #undef ATOMIC_LOAD_LIST |
| #undef ATOMIC_BINOP_INSTRUCTION_LIST |
| #undef ATOMIC_COMPARE_EXCHANGE_LIST |
| |
| void AtomicFence(FullDecoder* decoder) { __ AtomicFence(); } |
| |
| // Pop a memtype (i32 or i64 depending on {WasmModule::is_memory64}) to a |
| // register, updating {*high_word} to contain the ORed combination of all |
| // popped high words. Returns the ptrsized register holding the popped value. |
| LiftoffRegister PopMemTypeToRegister(FullDecoder* decoder, |
| Register* high_word, |
| LiftoffRegList* pinned) { |
| LiftoffRegister reg = __ PopToRegister(*pinned); |
| LiftoffRegister intptr_reg = reg; |
| // For memory32 on 64-bit hosts, zero-extend. |
| if (kSystemPointerSize == kInt64Size && !env_->module->is_memory64) { |
| // Only overwrite {reg} if it's not used otherwise. |
| if (pinned->has(reg) || __ cache_state()->is_used(reg)) { |
| intptr_reg = __ GetUnusedRegister(kGpReg, *pinned); |
| } |
| __ emit_u32_to_uintptr(intptr_reg.gp(), reg.gp()); |
| } |
| // For memory32 or memory64 on 64-bit, we are done here. |
| if (kSystemPointerSize == kInt64Size || !env_->module->is_memory64) { |
| pinned->set(intptr_reg); |
| return intptr_reg; |
| } |
| |
| // For memory64 on 32-bit systems, combine all high words for a zero-check |
| // and only use the low words afterwards. This keeps the register pressure |
| // managable. |
| DCHECK_GE(kMaxUInt32, env_->module->max_memory_size); |
| pinned->set(reg.low()); |
| if (*high_word == no_reg) { |
| // Choose a register to hold the (combination of) high word(s). It cannot |
| // be one of the pinned registers, and it cannot be used in the value |
| // stack. |
| *high_word = |
| pinned->has(reg.high()) |
| ? __ GetUnusedRegister(kGpReg, *pinned).gp() |
| : __ GetUnusedRegister(kGpReg, {reg.high()}, *pinned).gp(); |
| pinned->set(*high_word); |
| if (*high_word != reg.high_gp()) { |
| __ Move(*high_word, reg.high_gp(), kI32); |
| } |
| } else if (*high_word != reg.high_gp()) { |
| // Combine the new high word into existing high words. |
| __ emit_i32_or(*high_word, *high_word, reg.high_gp()); |
| } |
| return reg.low(); |
| } |
| |
| void MemoryInit(FullDecoder* decoder, const MemoryInitImmediate& imm, |
| const Value&, const Value&, const Value&) { |
| Register mem_offsets_high_word = no_reg; |
| LiftoffRegList pinned; |
| LiftoffRegister size = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister src = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister dst = |
| PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned); |
| |
| Register instance = __ cache_state()->cached_instance; |
| if (instance == no_reg) { |
| instance = __ GetUnusedRegister(kGpReg, pinned).gp(); |
| __ LoadInstanceFromFrame(instance); |
| } |
| pinned.set(instance); |
| |
| // Only allocate the OOB code now, so the state of the stack is reflected |
| // correctly. |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds); |
| if (mem_offsets_high_word != no_reg) { |
| // If any high word has bits set, jump to the OOB trap. |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kNotZero, trap_label, kI32, mem_offsets_high_word, |
| no_reg, trapping); |
| pinned.clear(mem_offsets_high_word); |
| } |
| |
| LiftoffRegister segment_index = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(segment_index, WasmValue(imm.data_segment.index)); |
| |
| auto sig = MakeSig::Returns(kI32).Params(kIntPtrKind, kIntPtrKind, kI32, |
| kI32, kI32); |
| LiftoffRegister args[] = {LiftoffRegister(instance), dst, src, |
| segment_index, size}; |
| // We don't need the instance anymore after the call. We can use the |
| // register for the result. |
| LiftoffRegister result(instance); |
| GenerateCCall(&result, &sig, kVoid, args, |
| ExternalReference::wasm_memory_init()); |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kEqual, trap_label, kI32, result.gp(), no_reg, trapping); |
| } |
| |
| void DataDrop(FullDecoder* decoder, const IndexImmediate& imm) { |
| LiftoffRegList pinned; |
| |
| Register seg_size_array = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(seg_size_array, DataSegmentSizes, pinned); |
| |
| LiftoffRegister seg_index = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| // Scale the seg_index for the array access. |
| __ LoadConstant( |
| seg_index, |
| WasmValue(wasm::ObjectAccess::ElementOffsetInTaggedFixedUInt32Array( |
| imm.index))); |
| |
| // Set the length of the segment to '0' to drop it. |
| LiftoffRegister null_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(null_reg, WasmValue(0)); |
| __ Store(seg_size_array, seg_index.gp(), 0, null_reg, StoreType::kI32Store, |
| pinned); |
| } |
| |
| void MemoryCopy(FullDecoder* decoder, const MemoryCopyImmediate& imm, |
| const Value&, const Value&, const Value&) { |
| Register mem_offsets_high_word = no_reg; |
| LiftoffRegList pinned; |
| LiftoffRegister size = pinned.set( |
| PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); |
| LiftoffRegister src = pinned.set( |
| PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); |
| LiftoffRegister dst = pinned.set( |
| PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); |
| |
| Register instance = __ cache_state()->cached_instance; |
| if (instance == no_reg) { |
| instance = __ GetUnusedRegister(kGpReg, pinned).gp(); |
| __ LoadInstanceFromFrame(instance); |
| } |
| |
| // Only allocate the OOB code now, so the state of the stack is reflected |
| // correctly. |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds); |
| if (mem_offsets_high_word != no_reg) { |
| // If any high word has bits set, jump to the OOB trap. |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kNotZero, trap_label, kI32, mem_offsets_high_word, |
| no_reg, trapping); |
| } |
| |
| auto sig = MakeSig::Returns(kI32).Params(kIntPtrKind, kIntPtrKind, |
| kIntPtrKind, kIntPtrKind); |
| LiftoffRegister args[] = {LiftoffRegister(instance), dst, src, size}; |
| // We don't need the instance anymore after the call. We can use the |
| // register for the result. |
| LiftoffRegister result(instance); |
| GenerateCCall(&result, &sig, kVoid, args, |
| ExternalReference::wasm_memory_copy()); |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kEqual, trap_label, kI32, result.gp(), no_reg, trapping); |
| } |
| |
| void MemoryFill(FullDecoder* decoder, const MemoryIndexImmediate& imm, |
| const Value&, const Value&, const Value&) { |
| Register mem_offsets_high_word = no_reg; |
| LiftoffRegList pinned; |
| LiftoffRegister size = pinned.set( |
| PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister dst = pinned.set( |
| PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); |
| |
| Register instance = __ cache_state()->cached_instance; |
| if (instance == no_reg) { |
| instance = __ GetUnusedRegister(kGpReg, pinned).gp(); |
| __ LoadInstanceFromFrame(instance); |
| } |
| |
| // Only allocate the OOB code now, so the state of the stack is reflected |
| // correctly. |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds); |
| if (mem_offsets_high_word != no_reg) { |
| // If any high word has bits set, jump to the OOB trap. |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kNotZero, trap_label, kI32, mem_offsets_high_word, |
| no_reg, trapping); |
| } |
| |
| auto sig = MakeSig::Returns(kI32).Params(kIntPtrKind, kIntPtrKind, kI32, |
| kIntPtrKind); |
| LiftoffRegister args[] = {LiftoffRegister(instance), dst, value, size}; |
| // We don't need the instance anymore after the call. We can use the |
| // register for the result. |
| LiftoffRegister result(instance); |
| GenerateCCall(&result, &sig, kVoid, args, |
| ExternalReference::wasm_memory_fill()); |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kEqual, trap_label, kI32, result.gp(), no_reg, trapping); |
| } |
| |
| void LoadSmi(LiftoffRegister reg, int value) { |
| Address smi_value = Smi::FromInt(value).ptr(); |
| using smi_type = std::conditional_t<kSmiKind == kI32, int32_t, int64_t>; |
| __ LoadConstant(reg, WasmValue{static_cast<smi_type>(smi_value)}); |
| } |
| |
| void TableInit(FullDecoder* decoder, const TableInitImmediate& imm, |
| base::Vector<Value> args) { |
| LiftoffRegList pinned; |
| LiftoffRegister table_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| |
| LoadSmi(table_index_reg, imm.table.index); |
| LiftoffAssembler::VarState table_index{kSmiKind, table_index_reg, 0}; |
| |
| LiftoffRegister segment_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(segment_index_reg, imm.element_segment.index); |
| LiftoffAssembler::VarState segment_index{kSmiKind, segment_index_reg, 0}; |
| |
| LiftoffAssembler::VarState size = __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState src = __ cache_state()->stack_state.end()[-2]; |
| LiftoffAssembler::VarState dst = __ cache_state()->stack_state.end()[-3]; |
| |
| CallRuntimeStub(WasmCode::kWasmTableInit, |
| MakeSig::Params(kI32, kI32, kI32, kSmiKind, kSmiKind), |
| {dst, src, size, table_index, segment_index}, |
| decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ cache_state()->stack_state.pop_back(3); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| } |
| |
| void ElemDrop(FullDecoder* decoder, const IndexImmediate& imm) { |
| LiftoffRegList pinned; |
| Register element_segments = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(element_segments, ElementSegments, pinned); |
| |
| LiftoffRegister seg_index = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant( |
| seg_index, |
| WasmValue( |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index))); |
| |
| // Mark the segment as dropped by setting it to the empty fixed array. |
| LiftoffRegister empty_fixed_array = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadFullPointer( |
| empty_fixed_array.gp(), kRootRegister, |
| IsolateData::root_slot_offset(RootIndex::kEmptyFixedArray)); |
| |
| __ StoreTaggedPointer(element_segments, seg_index.gp(), 0, |
| empty_fixed_array, pinned); |
| } |
| |
| void TableCopy(FullDecoder* decoder, const TableCopyImmediate& imm, |
| base::Vector<Value> args) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister table_dst_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(table_dst_index_reg, imm.table_dst.index); |
| LiftoffAssembler::VarState table_dst_index{kSmiKind, table_dst_index_reg, |
| 0}; |
| |
| LiftoffRegister table_src_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(table_src_index_reg, imm.table_src.index); |
| LiftoffAssembler::VarState table_src_index{kSmiKind, table_src_index_reg, |
| 0}; |
| |
| LiftoffAssembler::VarState size = __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState src = __ cache_state()->stack_state.end()[-2]; |
| LiftoffAssembler::VarState dst = __ cache_state()->stack_state.end()[-3]; |
| |
| CallRuntimeStub(WasmCode::kWasmTableCopy, |
| MakeSig::Params(kI32, kI32, kI32, kSmiKind, kSmiKind), |
| {dst, src, size, table_dst_index, table_src_index}, |
| decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ cache_state()->stack_state.pop_back(3); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| } |
| |
| void TableGrow(FullDecoder* decoder, const IndexImmediate& imm, const Value&, |
| const Value&, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister table_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(table_index_reg, imm.index); |
| LiftoffAssembler::VarState table_index(kSmiKind, table_index_reg, 0); |
| |
| LiftoffAssembler::VarState delta = __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState value = __ cache_state()->stack_state.end()[-2]; |
| |
| CallRuntimeStub(WasmCode::kWasmTableGrow, |
| MakeSig::Returns(kSmiKind).Params(kSmiKind, kI32, kRefNull), |
| {table_index, delta, value}, decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ cache_state()->stack_state.pop_back(2); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| __ SmiToInt32(kReturnRegister0); |
| __ PushRegister(kI32, LiftoffRegister(kReturnRegister0)); |
| } |
| |
| void TableSize(FullDecoder* decoder, const IndexImmediate& imm, Value*) { |
| // We have to look up instance->tables[table_index].length. |
| |
| LiftoffRegList pinned; |
| // Get the number of calls array address. |
| Register tables = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(tables, Tables, pinned); |
| |
| Register table = tables; |
| __ LoadTaggedPointer( |
| table, tables, no_reg, |
| ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index)); |
| |
| int length_field_size = WasmTableObject::kCurrentLengthOffsetEnd - |
| WasmTableObject::kCurrentLengthOffset + 1; |
| |
| Register result = table; |
| __ Load(LiftoffRegister(result), table, no_reg, |
| wasm::ObjectAccess::ToTagged(WasmTableObject::kCurrentLengthOffset), |
| length_field_size == 4 ? LoadType::kI32Load : LoadType::kI64Load); |
| |
| __ SmiUntag(result); |
| __ PushRegister(kI32, LiftoffRegister(result)); |
| } |
| |
| void TableFill(FullDecoder* decoder, const IndexImmediate& imm, const Value&, |
| const Value&, const Value&) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister table_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(table_index_reg, imm.index); |
| LiftoffAssembler::VarState table_index(kSmiKind, table_index_reg, 0); |
| |
| LiftoffAssembler::VarState count = __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState value = __ cache_state()->stack_state.end()[-2]; |
| LiftoffAssembler::VarState start = __ cache_state()->stack_state.end()[-3]; |
| |
| CallRuntimeStub(WasmCode::kWasmTableFill, |
| MakeSig::Params(kSmiKind, kI32, kI32, kRefNull), |
| {table_index, start, count, value}, decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ cache_state()->stack_state.pop_back(3); |
| |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| } |
| |
| void StructNew(FullDecoder* decoder, const StructIndexImmediate& imm, |
| const Value& rtt, bool initial_values_on_stack) { |
| LiftoffRegList pinned; |
| LiftoffRegister instance_size = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LiftoffAssembler::VarState instance_size_state(kI32, instance_size, 0); |
| LiftoffAssembler::VarState rtt_value = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| __ LoadConstant( |
| instance_size, |
| WasmValue(static_cast<int32_t>(WasmStruct::Size(imm.struct_type)))); |
| CallRuntimeStub(WasmCode::kWasmAllocateStructWithRtt, |
| MakeSig::Returns(kRef).Params(rtt.type.kind(), kI32), |
| {rtt_value, instance_size_state}, decoder->position()); |
| // Drop the RTT. |
| __ cache_state()->stack_state.pop_back(1); |
| |
| LiftoffRegister obj(kReturnRegister0); |
| pinned.set(obj); |
| |
| for (uint32_t i = imm.struct_type->field_count(); i > 0;) { |
| i--; |
| int offset = StructFieldOffset(imm.struct_type, i); |
| ValueType field_type = imm.struct_type->field(i); |
| LiftoffRegister value = pinned.set( |
| initial_values_on_stack |
| ? __ PopToRegister(pinned) |
| : __ GetUnusedRegister(reg_class_for(field_type.kind()), pinned)); |
| if (!initial_values_on_stack) { |
| if (!CheckSupportedType(decoder, field_type.kind(), "default value")) { |
| return; |
| } |
| SetDefaultValue(value, field_type, pinned); |
| } |
| // Skipping the write barrier is safe as long as: |
| // (1) {obj} is freshly allocated, and |
| // (2) {obj} is in new-space (not pretenured). |
| StoreObjectField(obj.gp(), no_reg, offset, value, pinned, |
| field_type.kind(), LiftoffAssembler::kSkipWriteBarrier); |
| pinned.clear(value); |
| } |
| // If this assert fails then initialization of padding field might be |
| // necessary. |
| static_assert(Heap::kMinObjectSizeInTaggedWords == 2 && |
| WasmStruct::kHeaderSize == 2 * kTaggedSize, |
| "empty struct might require initialization of padding field"); |
| __ PushRegister(kRef, obj); |
| } |
| |
| void StructNew(FullDecoder* decoder, const StructIndexImmediate& imm, |
| const Value& rtt, const Value args[], Value* result) { |
| StructNew(decoder, imm, rtt, true); |
| } |
| |
| void StructNewDefault(FullDecoder* decoder, const StructIndexImmediate& imm, |
| const Value& rtt, Value* result) { |
| StructNew(decoder, imm, rtt, false); |
| } |
| |
| void StructGet(FullDecoder* decoder, const Value& struct_obj, |
| const FieldImmediate& field, bool is_signed, Value* result) { |
| const StructType* struct_type = field.struct_imm.struct_type; |
| ValueKind field_kind = struct_type->field(field.field_imm.index).kind(); |
| if (!CheckSupportedType(decoder, field_kind, "field load")) return; |
| int offset = StructFieldOffset(struct_type, field.field_imm.index); |
| LiftoffRegList pinned; |
| LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, obj.gp(), pinned, struct_obj.type); |
| LiftoffRegister value = |
| __ GetUnusedRegister(reg_class_for(field_kind), pinned); |
| LoadObjectField(value, obj.gp(), no_reg, offset, field_kind, is_signed, |
| pinned); |
| __ PushRegister(unpacked(field_kind), value); |
| } |
| |
| void StructSet(FullDecoder* decoder, const Value& struct_obj, |
| const FieldImmediate& field, const Value& field_value) { |
| const StructType* struct_type = field.struct_imm.struct_type; |
| ValueKind field_kind = struct_type->field(field.field_imm.index).kind(); |
| int offset = StructFieldOffset(struct_type, field.field_imm.index); |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, obj.gp(), pinned, struct_obj.type); |
| StoreObjectField(obj.gp(), no_reg, offset, value, pinned, field_kind); |
| } |
| |
| void ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate& imm, |
| ValueKind rtt_kind, bool initial_value_on_stack) { |
| // Max length check. |
| { |
| LiftoffRegister length = |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], {}); |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayTooLarge); |
| FREEZE_STATE(trapping); |
| __ emit_i32_cond_jumpi(kUnsignedGreaterThan, trap_label, length.gp(), |
| WasmArray::MaxLength(imm.array_type), trapping); |
| } |
| ValueType elem_type = imm.array_type->element_type(); |
| ValueKind elem_kind = elem_type.kind(); |
| int elem_size = value_kind_size(elem_kind); |
| // Allocate the array. |
| { |
| LiftoffRegister elem_size_reg = __ GetUnusedRegister(kGpReg, {}); |
| LiftoffAssembler::VarState rtt_var = |
| __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState length_var = |
| __ cache_state()->stack_state.end()[-2]; |
| __ LoadConstant(elem_size_reg, WasmValue(elem_size)); |
| LiftoffAssembler::VarState elem_size_var(kI32, elem_size_reg, 0); |
| CallRuntimeStub(WasmCode::kWasmAllocateArray_Uninitialized, |
| MakeSig::Returns(kRef).Params(rtt_kind, kI32, kI32), |
| {rtt_var, length_var, elem_size_var}, |
| decoder->position()); |
| // Drop the RTT. |
| __ cache_state()->stack_state.pop_back(1); |
| } |
| |
| LiftoffRegister obj(kReturnRegister0); |
| LiftoffRegList pinned{obj}; |
| LiftoffRegister length = pinned.set(__ PopToModifiableRegister(pinned)); |
| LiftoffRegister value = |
| pinned.set(__ GetUnusedRegister(reg_class_for(elem_kind), pinned)); |
| if (initial_value_on_stack) { |
| __ PopToFixedRegister(value); |
| } else { |
| if (!CheckSupportedType(decoder, elem_kind, "default value")) return; |
| SetDefaultValue(value, elem_type, pinned); |
| } |
| |
| LiftoffRegister index = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(index, WasmValue(int32_t{0})); |
| |
| // Initialize the array's elements. |
| // Skipping the write barrier is safe as long as: |
| // (1) {obj} is freshly allocated, and |
| // (2) {obj} is in new-space (not pretenured). |
| ArrayFillImpl(pinned, obj, index, value, length, elem_kind, |
| LiftoffAssembler::kSkipWriteBarrier); |
| |
| __ PushRegister(kRef, obj); |
| } |
| |
| void ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate& imm, |
| const Value& length_value, const Value& initial_value, |
| const Value& rtt, Value* result) { |
| ArrayNew(decoder, imm, rtt.type.kind(), true); |
| } |
| |
| void ArrayNewDefault(FullDecoder* decoder, const ArrayIndexImmediate& imm, |
| const Value& length, const Value& rtt, Value* result) { |
| ArrayNew(decoder, imm, rtt.type.kind(), false); |
| } |
| |
| void ArrayFill(FullDecoder* decoder, ArrayIndexImmediate& imm, |
| const Value& array, const Value& /* index */, |
| const Value& /* value */, const Value& /* length */) { |
| { |
| // Null check. |
| LiftoffRegList pinned; |
| LiftoffRegister array_reg = pinned.set(__ PeekToRegister(3, pinned)); |
| MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type); |
| |
| // Bounds checks. |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayOutOfBounds); |
| LiftoffRegister array_length = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadObjectField(array_length, array_reg.gp(), no_reg, |
| ObjectAccess::ToTagged(WasmArray::kLengthOffset), kI32, |
| false, pinned); |
| LiftoffRegister index = pinned.set(__ PeekToRegister(2, pinned)); |
| LiftoffRegister length = pinned.set(__ PeekToRegister(0, pinned)); |
| LiftoffRegister index_plus_length = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| DCHECK(index_plus_length != array_length); |
| __ emit_i32_add(index_plus_length.gp(), length.gp(), index.gp()); |
| FREEZE_STATE(frozen); |
| __ emit_cond_jump(kUnsignedGreaterThan, trap_label, kI32, |
| index_plus_length.gp(), array_length.gp(), frozen); |
| // Guard against overflow. |
| __ emit_cond_jump(kUnsignedGreaterThan, trap_label, kI32, index.gp(), |
| index_plus_length.gp(), frozen); |
| } |
| |
| LiftoffRegList pinned; |
| LiftoffRegister length = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister index = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); |
| |
| ArrayFillImpl(pinned, obj, index, value, length, |
| imm.array_type->element_type().kind(), |
| LiftoffAssembler::kNoSkipWriteBarrier); |
| } |
| |
| void ArrayGet(FullDecoder* decoder, const Value& array_obj, |
| const ArrayIndexImmediate& imm, const Value& index_val, |
| bool is_signed, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned)); |
| LiftoffRegister array = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, array.gp(), pinned, array_obj.type); |
| BoundsCheckArray(decoder, array, index, pinned); |
| ValueKind elem_kind = imm.array_type->element_type().kind(); |
| if (!CheckSupportedType(decoder, elem_kind, "array load")) return; |
| int elem_size_shift = value_kind_size_log2(elem_kind); |
| if (elem_size_shift != 0) { |
| __ emit_i32_shli(index.gp(), index.gp(), elem_size_shift); |
| } |
| LiftoffRegister value = |
| __ GetUnusedRegister(reg_class_for(elem_kind), pinned); |
| LoadObjectField(value, array.gp(), index.gp(), |
| wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize), |
| elem_kind, is_signed, pinned); |
| __ PushRegister(unpacked(elem_kind), value); |
| } |
| |
| void ArraySet(FullDecoder* decoder, const Value& array_obj, |
| const ArrayIndexImmediate& imm, const Value& index_val, |
| const Value& value_val) { |
| LiftoffRegList pinned; |
| LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); |
| DCHECK_EQ(reg_class_for(imm.array_type->element_type().kind()), |
| value.reg_class()); |
| LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned)); |
| LiftoffRegister array = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, array.gp(), pinned, array_obj.type); |
| BoundsCheckArray(decoder, array, index, pinned); |
| ValueKind elem_kind = imm.array_type->element_type().kind(); |
| int elem_size_shift = value_kind_size_log2(elem_kind); |
| if (elem_size_shift != 0) { |
| __ emit_i32_shli(index.gp(), index.gp(), elem_size_shift); |
| } |
| StoreObjectField(array.gp(), index.gp(), |
| wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize), |
| value, pinned, elem_kind); |
| } |
| |
| void ArrayLen(FullDecoder* decoder, const Value& array_obj, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, obj.gp(), pinned, array_obj.type); |
| LiftoffRegister len = __ GetUnusedRegister(kGpReg, pinned); |
| int kLengthOffset = wasm::ObjectAccess::ToTagged(WasmArray::kLengthOffset); |
| LoadObjectField(len, obj.gp(), no_reg, kLengthOffset, kI32, false, pinned); |
| __ PushRegister(kI32, len); |
| } |
| |
| void ArrayCopy(FullDecoder* decoder, const Value& dst, const Value& dst_index, |
| const Value& src, const Value& src_index, |
| const Value& length) { |
| // TODO(7748): Unify implementation with TF: Implement this with |
| // GenerateCCall. Remove runtime function and builtin in wasm.tq. |
| CallRuntimeStub(v8_flags.experimental_wasm_skip_bounds_checks |
| ? WasmCode::kWasmArrayCopy |
| : WasmCode::kWasmArrayCopyWithChecks, |
| MakeSig::Params(kI32, kI32, kI32, kRefNull, kRefNull), |
| // Builtin parameter order: |
| // [dst_index, src_index, length, dst, src]. |
| {__ cache_state()->stack_state.end()[-4], |
| __ cache_state()->stack_state.end()[-2], |
| __ cache_state()->stack_state.end()[-1], |
| __ cache_state()->stack_state.end()[-5], |
| __ cache_state()->stack_state.end()[-3]}, |
| decoder->position()); |
| __ DropValues(5); |
| } |
| |
| void ArrayNewFixed(FullDecoder* decoder, const ArrayIndexImmediate& imm, |
| const base::Vector<Value>& elements, const Value& rtt, |
| Value* result) { |
| ValueKind rtt_kind = rtt.type.kind(); |
| ValueKind elem_kind = imm.array_type->element_type().kind(); |
| // Allocate the array. |
| { |
| LiftoffRegList pinned; |
| LiftoffRegister elem_size_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| |
| __ LoadConstant(elem_size_reg, WasmValue(value_kind_size(elem_kind))); |
| LiftoffAssembler::VarState elem_size_var(kI32, elem_size_reg, 0); |
| |
| LiftoffRegister length_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(length_reg, |
| WasmValue(static_cast<int32_t>(elements.size()))); |
| LiftoffAssembler::VarState length_var(kI32, length_reg, 0); |
| |
| LiftoffAssembler::VarState rtt_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| CallRuntimeStub(WasmCode::kWasmAllocateArray_Uninitialized, |
| MakeSig::Returns(kRef).Params(rtt_kind, kI32, kI32), |
| {rtt_var, length_var, elem_size_var}, |
| decoder->position()); |
| // Drop the RTT. |
| __ DropValues(1); |
| } |
| |
| // Initialize the array with stack arguments. |
| LiftoffRegister array(kReturnRegister0); |
| if (!CheckSupportedType(decoder, elem_kind, "array.new_fixed")) return; |
| for (int i = static_cast<int>(elements.size()) - 1; i >= 0; i--) { |
| LiftoffRegList pinned{array}; |
| LiftoffRegister element = pinned.set(__ PopToRegister(pinned)); |
| int offset = |
| WasmArray::kHeaderSize + (i << value_kind_size_log2(elem_kind)); |
| // Skipping the write barrier is safe as long as: |
| // (1) {array} is freshly allocated, and |
| // (2) {array} is in new-space (not pretenured). |
| StoreObjectField(array.gp(), no_reg, wasm::ObjectAccess::ToTagged(offset), |
| element, pinned, elem_kind, |
| LiftoffAssembler::kSkipWriteBarrier); |
| } |
| |
| // Push the array onto the stack. |
| __ PushRegister(kRef, array); |
| } |
| |
| void ArrayNewSegment(FullDecoder* decoder, |
| const ArrayIndexImmediate& array_imm, |
| const IndexImmediate& segment_imm, |
| const Value& /* offset */, const Value& /* length */, |
| const Value& /* rtt */, Value* /* result */) { |
| LiftoffRegList pinned; |
| LiftoffRegister segment_index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(segment_index_reg, |
| WasmValue(static_cast<int32_t>(segment_imm.index))); |
| LiftoffAssembler::VarState segment_index_var(kI32, segment_index_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmArrayNewSegment, |
| MakeSig::Returns(kRef).Params(kI32, kI32, kI32, kRtt), |
| { |
| segment_index_var, |
| __ cache_state()->stack_state.end()[-3], // offset |
| __ cache_state()->stack_state.end()[-2], // length |
| __ cache_state()->stack_state.end()[-1] // rtt |
| }, |
| decoder->position()); |
| |
| // Pop parameters from the value stack. |
| __ DropValues(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result(kReturnRegister0); |
| __ PushRegister(kRef, result); |
| } |
| |
| void ArrayInitSegment(FullDecoder* decoder, |
| const ArrayIndexImmediate& /* array_imm */, |
| const IndexImmediate& segment_imm, |
| const Value& /* array */, |
| const Value& /* array_index */, |
| const Value& /* segment_offset*/, |
| const Value& /* length */) { |
| LiftoffRegister segment_index_reg = __ GetUnusedRegister(kGpReg, {}); |
| LoadSmi(segment_index_reg, static_cast<int32_t>(segment_imm.index)); |
| LiftoffAssembler::VarState segment_index_var(kSmiKind, segment_index_reg, |
| 0); |
| |
| // Builtin parameter order: segment_index, array_index, segment_offset, |
| // length, array. |
| CallRuntimeStub(WasmCode::kWasmArrayInitSegment, |
| MakeSig::Params(kI32, kI32, kI32, kSmiKind, kRefNull), |
| {__ cache_state()->stack_state.end()[-3], |
| __ cache_state()->stack_state.end()[-2], |
| __ cache_state()->stack_state.end()[-1], segment_index_var, |
| __ cache_state()->stack_state.end()[-4]}, |
| decoder->position()); |
| __ DropValues(4); |
| } |
| |
| void I31New(FullDecoder* decoder, const Value& input, Value* result) { |
| LiftoffRegister src = __ PopToRegister(); |
| LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {}); |
| if constexpr (SmiValuesAre31Bits()) { |
| static_assert(kSmiTag == 0); |
| __ emit_i32_shli(dst.gp(), src.gp(), kSmiTagSize); |
| } else { |
| DCHECK(SmiValuesAre32Bits()); |
| // Set the topmost bit to sign-extend the second bit. This way, |
| // interpretation in JS (if this value escapes there) will be the same as |
| // i31.get_s. |
| __ emit_i64_shli(dst, src, kSmiTagSize + kSmiShiftSize + 1); |
| __ emit_i64_sari(dst, dst, 1); |
| } |
| __ PushRegister(kRef, dst); |
| } |
| |
| void I31GetS(FullDecoder* decoder, const Value& input, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister src = pinned.set(__ PopToRegister()); |
| MaybeEmitNullCheck(decoder, src.gp(), pinned, input.type); |
| LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {}); |
| if constexpr (SmiValuesAre31Bits()) { |
| __ emit_i32_sari(dst.gp(), src.gp(), kSmiTagSize); |
| } else { |
| DCHECK(SmiValuesAre32Bits()); |
| // Topmost bit is already sign-extended. |
| __ emit_i64_sari(dst, src, kSmiTagSize + kSmiShiftSize); |
| } |
| __ PushRegister(kI32, dst); |
| } |
| |
| void I31GetU(FullDecoder* decoder, const Value& input, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister src = pinned.set(__ PopToRegister()); |
| MaybeEmitNullCheck(decoder, src.gp(), pinned, input.type); |
| LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {}); |
| if constexpr (SmiValuesAre31Bits()) { |
| __ emit_i32_shri(dst.gp(), src.gp(), kSmiTagSize); |
| } else { |
| DCHECK(SmiValuesAre32Bits()); |
| // Remove topmost bit. |
| __ emit_i64_shli(dst, src, 1); |
| __ emit_i64_shri(dst, dst, kSmiTagSize + kSmiShiftSize + 1); |
| } |
| __ PushRegister(kI32, dst); |
| } |
| |
| void RttCanon(FullDecoder* decoder, uint32_t type_index, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister rtt = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(rtt.gp(), ManagedObjectMaps, pinned); |
| __ LoadTaggedPointer( |
| rtt.gp(), rtt.gp(), no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(type_index)); |
| __ PushRegister(kRtt, rtt); |
| } |
| |
| enum NullSucceeds : bool { // -- |
| kNullSucceeds = true, |
| kNullFails = false |
| }; |
| |
| // Falls through on match (=successful type check). |
| // Returns the register containing the object. |
| void SubtypeCheck(const WasmModule* module, Register obj_reg, |
| ValueType obj_type, Register rtt_reg, ValueType rtt_type, |
| Register scratch_null, Register scratch2, Label* no_match, |
| NullSucceeds null_succeeds, |
| const FreezeCacheState& frozen) { |
| Label match; |
| bool is_cast_from_any = obj_type.is_reference_to(HeapType::kAny); |
| |
| // Skip the null check if casting from any and not {null_succeeds}. |
| // In that case the instance type check will identify null as not being a |
| // wasm object and fail. |
| if (obj_type.is_nullable() && (!is_cast_from_any || null_succeeds)) { |
| __ emit_cond_jump(kEqual, null_succeeds ? &match : no_match, |
| obj_type.kind(), obj_reg, scratch_null, frozen); |
| } |
| Register tmp1 = scratch_null; // Done with null checks. |
| |
| // Add Smi check if the source type may store a Smi (i31ref or JS Smi). |
| ValueType i31ref = ValueType::Ref(HeapType::kI31); |
| // Ref.extern can also contain Smis, however there isn't any type that |
| // could downcast to ref.extern. |
| DCHECK(!rtt_type.is_reference_to(HeapType::kExtern)); |
| // Ref.i31 check has its own implementation. |
| DCHECK(!rtt_type.is_reference_to(HeapType::kI31)); |
| if (IsSubtypeOf(i31ref, obj_type, module)) { |
| Label* i31_target = |
| IsSubtypeOf(i31ref, rtt_type, module) ? &match : no_match; |
| __ emit_smi_check(obj_reg, i31_target, LiftoffAssembler::kJumpOnSmi, |
| frozen); |
| } |
| |
| __ LoadMap(tmp1, obj_reg); |
| // {tmp1} now holds the object's map. |
| |
| if (module->types[rtt_type.ref_index()].is_final) { |
| // In this case, simply check for map equality. |
| __ emit_cond_jump(kNotEqual, no_match, rtt_type.kind(), tmp1, rtt_reg, |
| frozen); |
| } else { |
| // Check for rtt equality, and if not, check if the rtt is a struct/array |
| // rtt. |
| __ emit_cond_jump(kEqual, &match, rtt_type.kind(), tmp1, rtt_reg, frozen); |
| |
| if (is_cast_from_any) { |
| // Check for map being a map for a wasm object (struct, array, func). |
| __ Load(LiftoffRegister(scratch2), tmp1, no_reg, |
| wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset), |
| LoadType::kI32Load16U); |
| __ emit_i32_subi(scratch2, scratch2, FIRST_WASM_OBJECT_TYPE); |
| __ emit_i32_cond_jumpi(kUnsignedGreaterThan, no_match, scratch2, |
| LAST_WASM_OBJECT_TYPE - FIRST_WASM_OBJECT_TYPE, |
| frozen); |
| } |
| |
| // Constant-time subtyping check: load exactly one candidate RTT from the |
| // supertypes list. |
| // Step 1: load the WasmTypeInfo into {tmp1}. |
| constexpr int kTypeInfoOffset = wasm::ObjectAccess::ToTagged( |
| Map::kConstructorOrBackPointerOrNativeContextOffset); |
| __ LoadTaggedPointer(tmp1, tmp1, no_reg, kTypeInfoOffset); |
| // Step 2: check the list's length if needed. |
| uint32_t rtt_depth = GetSubtypingDepth(module, rtt_type.ref_index()); |
| if (rtt_depth >= kMinimumSupertypeArraySize) { |
| LiftoffRegister list_length(scratch2); |
| int offset = |
| ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesLengthOffset); |
| __ LoadSmiAsInt32(list_length, tmp1, offset); |
| __ emit_i32_cond_jumpi(kUnsignedLessThanEqual, no_match, |
| list_length.gp(), rtt_depth, frozen); |
| } |
| // Step 3: load the candidate list slot into {tmp1}, and compare it. |
| __ LoadTaggedPointer( |
| tmp1, tmp1, no_reg, |
| ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset + |
| rtt_depth * kTaggedSize)); |
| __ emit_cond_jump(kNotEqual, no_match, rtt_type.kind(), tmp1, rtt_reg, |
| frozen); |
| } |
| |
| // Fall through to {match}. |
| __ bind(&match); |
| } |
| |
| void RefTest(FullDecoder* decoder, const Value& obj, const Value& rtt, |
| Value* /* result_val */, bool null_succeeds) { |
| Label return_false, done; |
| LiftoffRegList pinned; |
| LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned)); |
| Register scratch_null = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| if (obj.type.is_nullable()) { |
| LoadNullValueForCompare(scratch_null, pinned, obj.type); |
| } |
| |
| { |
| FREEZE_STATE(frozen); |
| SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(), |
| rtt.type, scratch_null, result.gp(), &return_false, |
| null_succeeds ? kNullSucceeds : kNullFails, frozen); |
| |
| __ LoadConstant(result, WasmValue(1)); |
| // TODO(jkummerow): Emit near jumps on platforms that have them. |
| __ emit_jump(&done); |
| |
| __ bind(&return_false); |
| __ LoadConstant(result, WasmValue(0)); |
| __ bind(&done); |
| } |
| __ PushRegister(kI32, result); |
| } |
| |
| void RefTestAbstract(FullDecoder* decoder, const Value& obj, HeapType type, |
| Value* result_val, bool null_succeeds) { |
| switch (type.representation()) { |
| case HeapType::kEq: |
| return RefIsEq(decoder, obj, result_val, null_succeeds); |
| case HeapType::kI31: |
| return RefIsI31(decoder, obj, result_val, null_succeeds); |
| case HeapType::kStruct: |
| return RefIsStruct(decoder, obj, result_val, null_succeeds); |
| case HeapType::kArray: |
| return RefIsArray(decoder, obj, result_val, null_succeeds); |
| case HeapType::kNone: |
| case HeapType::kNoExtern: |
| case HeapType::kNoFunc: |
| DCHECK(null_succeeds); |
| return EmitIsNull(kExprRefIsNull, obj.type); |
| case HeapType::kAny: |
| // Any may never need a cast as it is either implicitly convertible or |
| // never convertible for any given type. |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void RefCast(FullDecoder* decoder, const Value& obj, const Value& rtt, |
| Value* result, bool null_succeeds) { |
| if (v8_flags.experimental_wasm_assume_ref_cast_succeeds) { |
| // Just drop the rtt. |
| __ DropValues(1); |
| return; |
| } |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast); |
| LiftoffRegList pinned; |
| LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned)); |
| Register scratch_null = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register scratch2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| if (obj.type.is_nullable()) { |
| LoadNullValueForCompare(scratch_null, pinned, obj.type); |
| } |
| |
| { |
| FREEZE_STATE(frozen); |
| NullSucceeds on_null = null_succeeds ? kNullSucceeds : kNullFails; |
| SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(), |
| rtt.type, scratch_null, scratch2, trap_label, on_null, |
| frozen); |
| } |
| __ PushRegister(obj.type.kind(), obj_reg); |
| } |
| |
| void RefCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type, |
| Value* result_val, bool null_succeeds) { |
| switch (type.representation()) { |
| case HeapType::kEq: |
| return RefAsEq(decoder, obj, result_val, null_succeeds); |
| case HeapType::kI31: |
| return RefAsI31(decoder, obj, result_val, null_succeeds); |
| case HeapType::kStruct: |
| return RefAsStruct(decoder, obj, result_val, null_succeeds); |
| case HeapType::kArray: |
| return RefAsArray(decoder, obj, result_val, null_succeeds); |
| case HeapType::kNone: |
| case HeapType::kNoExtern: |
| case HeapType::kNoFunc: |
| DCHECK(null_succeeds); |
| return AssertNullTypecheck(decoder, obj, result_val); |
| case HeapType::kAny: |
| // Any may never need a cast as it is either implicitly convertible or |
| // never convertible for any given type. |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void BrOnCast(FullDecoder* decoder, const Value& obj, const Value& rtt, |
| Value* /* result_on_branch */, uint32_t depth, |
| bool null_succeeds) { |
| // Avoid having sequences of branches do duplicate work. |
| if (depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {}); |
| } |
| |
| Label cont_false; |
| LiftoffRegList pinned; |
| LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister obj_reg = pinned.set(__ PeekToRegister(0, pinned)); |
| Register scratch_null = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register scratch2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| if (obj.type.is_nullable()) { |
| LoadNullValue(scratch_null, pinned, kWasmAnyRef); |
| } |
| FREEZE_STATE(frozen); |
| |
| NullSucceeds null_handling = null_succeeds ? kNullSucceeds : kNullFails; |
| SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(), |
| rtt.type, scratch_null, scratch2, &cont_false, null_handling, |
| frozen); |
| |
| BrOrRetImpl(decoder, depth, scratch_null, scratch2); |
| |
| __ bind(&cont_false); |
| } |
| |
| void BrOnCastFail(FullDecoder* decoder, const Value& obj, const Value& rtt, |
| Value* /* result_on_fallthrough */, uint32_t depth, |
| bool null_succeeds) { |
| // Avoid having sequences of branches do duplicate work. |
| if (depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {}); |
| } |
| |
| Label cont_branch, fallthrough; |
| LiftoffRegList pinned; |
| LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister obj_reg = pinned.set(__ PeekToRegister(0, pinned)); |
| Register scratch_null = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register scratch2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| if (obj.type.is_nullable()) { |
| LoadNullValue(scratch_null, pinned, kWasmAnyRef); |
| } |
| FREEZE_STATE(frozen); |
| |
| NullSucceeds null_handling = null_succeeds ? kNullSucceeds : kNullFails; |
| SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(), |
| rtt.type, scratch_null, scratch2, &cont_branch, null_handling, |
| frozen); |
| __ emit_jump(&fallthrough); |
| |
| __ bind(&cont_branch); |
| BrOrRetImpl(decoder, depth, scratch_null, scratch2); |
| |
| __ bind(&fallthrough); |
| } |
| |
| void BrOnCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type, |
| Value* result_on_branch, uint32_t depth, |
| bool null_succeeds) { |
| switch (type.representation()) { |
| case HeapType::kEq: |
| return BrOnEq(decoder, obj, result_on_branch, depth, null_succeeds); |
| case HeapType::kI31: |
| return BrOnI31(decoder, obj, result_on_branch, depth, null_succeeds); |
| case HeapType::kStruct: |
| return BrOnStruct(decoder, obj, result_on_branch, depth, null_succeeds); |
| case HeapType::kArray: |
| return BrOnArray(decoder, obj, result_on_branch, depth, null_succeeds); |
| case HeapType::kNone: |
| case HeapType::kNoExtern: |
| case HeapType::kNoFunc: |
| DCHECK(null_succeeds); |
| return BrOnNull(decoder, obj, depth, /*pass_null_along_branch*/ true, |
| nullptr); |
| case HeapType::kAny: |
| // Any may never need a cast as it is either implicitly convertible or |
| // never convertible for any given type. |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void BrOnCastFailAbstract(FullDecoder* decoder, const Value& obj, |
| HeapType type, Value* result_on_fallthrough, |
| uint32_t depth, bool null_succeeds) { |
| switch (type.representation()) { |
| case HeapType::kEq: |
| return BrOnNonEq(decoder, obj, result_on_fallthrough, depth, |
| null_succeeds); |
| case HeapType::kI31: |
| return BrOnNonI31(decoder, obj, result_on_fallthrough, depth, |
| null_succeeds); |
| case HeapType::kStruct: |
| return BrOnNonStruct(decoder, obj, result_on_fallthrough, depth, |
| null_succeeds); |
| case HeapType::kArray: |
| return BrOnNonArray(decoder, obj, result_on_fallthrough, depth, |
| null_succeeds); |
| case HeapType::kNone: |
| case HeapType::kNoExtern: |
| case HeapType::kNoFunc: |
| DCHECK(null_succeeds); |
| return BrOnNonNull(decoder, obj, nullptr, depth, |
| /*drop_null_on_fallthrough*/ false); |
| case HeapType::kAny: |
| // Any may never need a cast as it is either implicitly convertible or |
| // never convertible for any given type. |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| struct TypeCheck { |
| Register obj_reg = no_reg; |
| ValueType obj_type; |
| Register tmp1 = no_reg; |
| Register tmp2 = no_reg; |
| Label* no_match; |
| bool null_succeeds; |
| |
| TypeCheck(ValueType obj_type, Label* no_match, bool null_succeeds) |
| : obj_type(obj_type), |
| no_match(no_match), |
| null_succeeds(null_succeeds) {} |
| |
| Register null_reg() { return tmp1; } // After {Initialize}. |
| Register instance_type() { return tmp1; } // After {LoadInstanceType}. |
| }; |
| |
| enum PopOrPeek { kPop, kPeek }; |
| |
| void Initialize(TypeCheck& check, PopOrPeek pop_or_peek, ValueType type) { |
| LiftoffRegList pinned; |
| if (pop_or_peek == kPop) { |
| check.obj_reg = pinned.set(__ PopToRegister(pinned)).gp(); |
| } else { |
| check.obj_reg = pinned.set(__ PeekToRegister(0, pinned)).gp(); |
| } |
| check.tmp1 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| check.tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| if (check.obj_type.is_nullable()) { |
| LoadNullValue(check.null_reg(), pinned, type); |
| } |
| } |
| void LoadInstanceType(TypeCheck& check, const FreezeCacheState& frozen, |
| Label* on_smi) { |
| // The check for null_succeeds == true has to be handled by the caller! |
| // TODO(mliedtke): Reiterate the null_succeeds case once all generic cast |
| // instructions are implemented. |
| if (!check.null_succeeds && check.obj_type.is_nullable()) { |
| __ emit_cond_jump(kEqual, check.no_match, kRefNull, check.obj_reg, |
| check.null_reg(), frozen); |
| } |
| __ emit_smi_check(check.obj_reg, on_smi, LiftoffAssembler::kJumpOnSmi, |
| frozen); |
| __ LoadMap(check.instance_type(), check.obj_reg); |
| __ Load(LiftoffRegister(check.instance_type()), check.instance_type(), |
| no_reg, wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset), |
| LoadType::kI32Load16U); |
| } |
| |
| // Abstract type checkers. They all fall through on match. |
| void StructCheck(TypeCheck& check, const FreezeCacheState& frozen) { |
| LoadInstanceType(check, frozen, check.no_match); |
| LiftoffRegister instance_type(check.instance_type()); |
| __ emit_i32_cond_jumpi(kNotEqual, check.no_match, check.instance_type(), |
| WASM_STRUCT_TYPE, frozen); |
| } |
| |
| void ArrayCheck(TypeCheck& check, const FreezeCacheState& frozen) { |
| LoadInstanceType(check, frozen, check.no_match); |
| LiftoffRegister instance_type(check.instance_type()); |
| __ emit_i32_cond_jumpi(kNotEqual, check.no_match, check.instance_type(), |
| WASM_ARRAY_TYPE, frozen); |
| } |
| |
| void I31Check(TypeCheck& check, const FreezeCacheState& frozen) { |
| __ emit_smi_check(check.obj_reg, check.no_match, |
| LiftoffAssembler::kJumpOnNotSmi, frozen); |
| } |
| |
| void EqCheck(TypeCheck& check, const FreezeCacheState& frozen) { |
| Label match; |
| LoadInstanceType(check, frozen, &match); |
| // We're going to test a range of WasmObject instance types with a single |
| // unsigned comparison. |
| Register tmp = check.instance_type(); |
| __ emit_i32_subi(tmp, tmp, FIRST_WASM_OBJECT_TYPE); |
| __ emit_i32_cond_jumpi(kUnsignedGreaterThan, check.no_match, tmp, |
| LAST_WASM_OBJECT_TYPE - FIRST_WASM_OBJECT_TYPE, |
| frozen); |
| __ bind(&match); |
| } |
| |
| using TypeChecker = void (LiftoffCompiler::*)(TypeCheck& check, |
| const FreezeCacheState& frozen); |
| |
| template <TypeChecker type_checker> |
| void AbstractTypeCheck(const Value& object, bool null_succeeds) { |
| Label match, no_match, done; |
| TypeCheck check(object.type, &no_match, null_succeeds); |
| Initialize(check, kPop, object.type); |
| LiftoffRegister result(check.tmp1); |
| { |
| FREEZE_STATE(frozen); |
| |
| if (null_succeeds && check.obj_type.is_nullable()) { |
| __ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg, |
| check.null_reg(), frozen); |
| } |
| |
| (this->*type_checker)(check, frozen); |
| |
| __ bind(&match); |
| __ LoadConstant(result, WasmValue(1)); |
| // TODO(jkummerow): Emit near jumps on platforms that have them. |
| __ emit_jump(&done); |
| |
| __ bind(&no_match); |
| __ LoadConstant(result, WasmValue(0)); |
| __ bind(&done); |
| } |
| __ PushRegister(kI32, result); |
| } |
| |
| void RefIsStruct(FullDecoder* /* decoder */, const Value& object, |
| Value* /* result_val */, bool null_succeeds = false) { |
| AbstractTypeCheck<&LiftoffCompiler::StructCheck>(object, null_succeeds); |
| } |
| |
| void RefIsEq(FullDecoder* /* decoder */, const Value& object, |
| Value* /* result_val */, bool null_succeeds) { |
| AbstractTypeCheck<&LiftoffCompiler::EqCheck>(object, null_succeeds); |
| } |
| |
| void RefIsArray(FullDecoder* /* decoder */, const Value& object, |
| Value* /* result_val */, bool null_succeeds = false) { |
| AbstractTypeCheck<&LiftoffCompiler::ArrayCheck>(object, null_succeeds); |
| } |
| |
| void RefIsI31(FullDecoder* decoder, const Value& object, Value* /* result */, |
| bool null_succeeds = false) { |
| AbstractTypeCheck<&LiftoffCompiler::I31Check>(object, null_succeeds); |
| } |
| |
| template <TypeChecker type_checker> |
| void AbstractTypeCast(const Value& object, FullDecoder* decoder, |
| ValueKind result_kind, bool null_succeeds = false) { |
| Label match; |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast); |
| TypeCheck check(object.type, trap_label, null_succeeds); |
| Initialize(check, kPeek, object.type); |
| FREEZE_STATE(frozen); |
| |
| if (null_succeeds && check.obj_type.is_nullable()) { |
| __ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg, |
| check.null_reg(), frozen); |
| } |
| (this->*type_checker)(check, frozen); |
| __ bind(&match); |
| } |
| |
| void RefAsEq(FullDecoder* decoder, const Value& object, Value* result, |
| bool null_succeeds = false) { |
| AbstractTypeCast<&LiftoffCompiler::EqCheck>(object, decoder, kRef, |
| null_succeeds); |
| } |
| |
| void RefAsStruct(FullDecoder* decoder, const Value& object, |
| Value* /* result */, bool null_succeeds = false) { |
| AbstractTypeCast<&LiftoffCompiler::StructCheck>(object, decoder, kRef, |
| null_succeeds); |
| } |
| |
| void RefAsI31(FullDecoder* decoder, const Value& object, Value* result, |
| bool null_succeeds = false) { |
| AbstractTypeCast<&LiftoffCompiler::I31Check>(object, decoder, kRef, |
| null_succeeds); |
| } |
| |
| void RefAsArray(FullDecoder* decoder, const Value& object, Value* result, |
| bool null_succeeds = false) { |
| AbstractTypeCast<&LiftoffCompiler::ArrayCheck>(object, decoder, kRef, |
| null_succeeds); |
| } |
| |
| template <TypeChecker type_checker> |
| void BrOnAbstractType(const Value& object, FullDecoder* decoder, |
| uint32_t br_depth, bool null_succeeds) { |
| // Avoid having sequences of branches do duplicate work. |
| if (br_depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(br_depth)->br_merge()->arity, {}); |
| } |
| |
| Label no_match, match; |
| TypeCheck check(object.type, &no_match, null_succeeds); |
| Initialize(check, kPeek, object.type); |
| FREEZE_STATE(frozen); |
| |
| if (null_succeeds && check.obj_type.is_nullable()) { |
| __ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg, |
| check.null_reg(), frozen); |
| } |
| |
| (this->*type_checker)(check, frozen); |
| __ bind(&match); |
| BrOrRetImpl(decoder, br_depth, check.tmp1, check.tmp2); |
| |
| __ bind(&no_match); |
| } |
| |
| template <TypeChecker type_checker> |
| void BrOnNonAbstractType(const Value& object, FullDecoder* decoder, |
| uint32_t br_depth, bool null_succeeds) { |
| // Avoid having sequences of branches do duplicate work. |
| if (br_depth != decoder->control_depth() - 1) { |
| __ PrepareForBranch(decoder->control_at(br_depth)->br_merge()->arity, {}); |
| } |
| |
| Label no_match, end; |
| TypeCheck check(object.type, &no_match, null_succeeds); |
| Initialize(check, kPeek, object.type); |
| FREEZE_STATE(frozen); |
| |
| if (null_succeeds && check.obj_type.is_nullable()) { |
| __ emit_cond_jump(kEqual, &end, kRefNull, check.obj_reg, check.null_reg(), |
| frozen); |
| } |
| |
| (this->*type_checker)(check, frozen); |
| __ emit_jump(&end); |
| |
| __ bind(&no_match); |
| BrOrRetImpl(decoder, br_depth, check.tmp1, check.tmp2); |
| |
| __ bind(&end); |
| } |
| |
| void BrOnEq(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnAbstractType<&LiftoffCompiler::EqCheck>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void BrOnStruct(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnAbstractType<&LiftoffCompiler::StructCheck>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void BrOnI31(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnAbstractType<&LiftoffCompiler::I31Check>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void BrOnArray(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnAbstractType<&LiftoffCompiler::ArrayCheck>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void BrOnNonStruct(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnNonAbstractType<&LiftoffCompiler::StructCheck>(object, decoder, |
| br_depth, null_succeeds); |
| } |
| |
| void BrOnNonI31(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnNonAbstractType<&LiftoffCompiler::I31Check>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void BrOnNonArray(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnNonAbstractType<&LiftoffCompiler::ArrayCheck>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void BrOnNonEq(FullDecoder* decoder, const Value& object, |
| Value* /* value_on_branch */, uint32_t br_depth, |
| bool null_succeeds) { |
| BrOnNonAbstractType<&LiftoffCompiler::EqCheck>(object, decoder, br_depth, |
| null_succeeds); |
| } |
| |
| void StringNewWtf8(FullDecoder* decoder, const MemoryIndexImmediate& imm, |
| const unibrow::Utf8Variant variant, const Value& offset, |
| const Value& size, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister memory_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(memory_reg, imm.index); |
| LiftoffAssembler::VarState memory_var(kSmiKind, memory_reg, 0); |
| |
| LiftoffRegister variant_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(variant_reg, static_cast<int32_t>(variant)); |
| LiftoffAssembler::VarState variant_var(kSmiKind, variant_reg, 0); |
| |
| CallRuntimeStub( |
| WasmCode::kWasmStringNewWtf8, |
| MakeSig::Returns(kRefNull).Params(kI32, kI32, kSmiKind, kSmiKind), |
| { |
| __ cache_state()->stack_state.end()[-2], // offset |
| __ cache_state()->stack_state.end()[-1], // size |
| memory_var, |
| variant_var, |
| }, |
| decoder->position()); |
| __ cache_state()->stack_state.pop_back(2); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringNewWtf8Array(FullDecoder* decoder, |
| const unibrow::Utf8Variant variant, |
| const Value& array, const Value& start, |
| const Value& end, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister array_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned)); |
| MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type); |
| LiftoffAssembler::VarState array_var(kRef, array_reg, 0); |
| |
| LiftoffRegister variant_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(variant_reg, static_cast<int32_t>(variant)); |
| LiftoffAssembler::VarState variant_var(kSmiKind, variant_reg, 0); |
| |
| CallRuntimeStub( |
| WasmCode::kWasmStringNewWtf8Array, |
| MakeSig::Returns(kRefNull).Params(kI32, kI32, kRef, kSmiKind), |
| { |
| __ cache_state()->stack_state.end()[-2], // start |
| __ cache_state()->stack_state.end()[-1], // end |
| array_var, |
| variant_var, |
| }, |
| decoder->position()); |
| __ cache_state()->stack_state.pop_back(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringNewWtf16(FullDecoder* decoder, const MemoryIndexImmediate& imm, |
| const Value& offset, const Value& size, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister memory_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(memory_reg, WasmValue(static_cast<int32_t>(imm.index))); |
| LiftoffAssembler::VarState memory_var(kI32, memory_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringNewWtf16, |
| MakeSig::Returns(kRef).Params(kI32, kI32, kI32), |
| { |
| memory_var, |
| __ cache_state()->stack_state.end()[-2], // offset |
| __ cache_state()->stack_state.end()[-1] // size |
| }, |
| decoder->position()); |
| __ cache_state()->stack_state.pop_back(2); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringNewWtf16Array(FullDecoder* decoder, const Value& array, |
| const Value& start, const Value& end, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister array_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned)); |
| MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type); |
| LiftoffAssembler::VarState array_var(kRef, array_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringNewWtf16Array, |
| MakeSig::Returns(kRef).Params(kRef, kI32, kI32), |
| { |
| array_var, |
| __ cache_state()->stack_state.end()[-2], // start |
| __ cache_state()->stack_state.end()[-1], // end |
| }, |
| decoder->position()); |
| __ cache_state()->stack_state.pop_back(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringConst(FullDecoder* decoder, const StringConstImmediate& imm, |
| Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister index_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant(index_reg, WasmValue(static_cast<int32_t>(imm.index))); |
| LiftoffAssembler::VarState index_var(kI32, index_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringConst, |
| MakeSig::Returns(kRef).Params(kI32), |
| { |
| index_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringMeasureWtf8(FullDecoder* decoder, |
| const unibrow::Utf8Variant variant, const Value& str, |
| Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister string_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState string_var(kRef, string_reg, 0); |
| |
| WasmCode::RuntimeStubId stub_id; |
| switch (variant) { |
| case unibrow::Utf8Variant::kUtf8: |
| stub_id = WasmCode::kWasmStringMeasureUtf8; |
| break; |
| case unibrow::Utf8Variant::kLossyUtf8: |
| case unibrow::Utf8Variant::kWtf8: |
| stub_id = WasmCode::kWasmStringMeasureWtf8; |
| break; |
| case unibrow::Utf8Variant::kUtf8NoTrap: |
| UNREACHABLE(); |
| } |
| CallRuntimeStub(stub_id, MakeSig::Returns(kI32).Params(kRef), |
| { |
| string_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringMeasureWtf16(FullDecoder* decoder, const Value& str, |
| Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister string_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type); |
| LiftoffRegister value = __ GetUnusedRegister(kGpReg, pinned); |
| LoadObjectField(value, string_reg.gp(), no_reg, |
| wasm::ObjectAccess::ToTagged(String::kLengthOffset), |
| ValueKind::kI32, false /* is_signed */, pinned); |
| __ PushRegister(kI32, value); |
| } |
| |
| void StringEncodeWtf8(FullDecoder* decoder, const MemoryIndexImmediate& imm, |
| const unibrow::Utf8Variant variant, const Value& str, |
| const Value& offset, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& offset_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| LiftoffRegister string_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState string_var(kRef, string_reg, 0); |
| |
| LiftoffRegister memory_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(memory_reg, imm.index); |
| LiftoffAssembler::VarState memory_var(kSmiKind, memory_reg, 0); |
| |
| LiftoffRegister variant_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(variant_reg, static_cast<int32_t>(variant)); |
| LiftoffAssembler::VarState variant_var(kSmiKind, variant_reg, 0); |
| |
| CallRuntimeStub( |
| WasmCode::kWasmStringEncodeWtf8, |
| MakeSig::Returns(kI32).Params(kRef, kI32, kSmiKind, kSmiKind), |
| { |
| string_var, |
| offset_var, |
| memory_var, |
| variant_var, |
| }, |
| decoder->position()); |
| __ DropValues(2); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringEncodeWtf8Array(FullDecoder* decoder, |
| const unibrow::Utf8Variant variant, |
| const Value& str, const Value& array, |
| const Value& start, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister array_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type); |
| LiftoffAssembler::VarState array_var(kRef, array_reg, 0); |
| |
| LiftoffRegister string_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState string_var(kRef, string_reg, 0); |
| |
| LiftoffAssembler::VarState& start_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| LiftoffRegister variant_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(variant_reg, static_cast<int32_t>(variant)); |
| LiftoffAssembler::VarState variant_var(kSmiKind, variant_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringEncodeWtf8Array, |
| MakeSig::Returns(kI32).Params(kRef, kRef, kI32, kSmiKind), |
| { |
| string_var, |
| array_var, |
| start_var, |
| variant_var, |
| }, |
| decoder->position()); |
| __ DropValues(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringEncodeWtf16(FullDecoder* decoder, const MemoryIndexImmediate& imm, |
| const Value& str, const Value& offset, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& offset_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| LiftoffRegister string_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState string_var(kRef, string_reg, 0); |
| |
| LiftoffRegister memory_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(memory_reg, imm.index); |
| LiftoffAssembler::VarState memory_var(kSmiKind, memory_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringEncodeWtf16, |
| MakeSig::Returns(kI32).Params(kRef, kI32, kSmiKind), |
| { |
| string_var, |
| offset_var, |
| memory_var, |
| }, |
| decoder->position()); |
| __ DropValues(2); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringEncodeWtf16Array(FullDecoder* decoder, const Value& str, |
| const Value& array, const Value& start, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister array_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type); |
| LiftoffAssembler::VarState array_var(kRef, array_reg, 0); |
| |
| LiftoffRegister string_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState string_var(kRef, string_reg, 0); |
| |
| LiftoffAssembler::VarState& start_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| CallRuntimeStub(WasmCode::kWasmStringEncodeWtf16Array, |
| MakeSig::Returns(kI32).Params(kRef, kRef, kI32), |
| { |
| string_var, |
| array_var, |
| start_var, |
| }, |
| decoder->position()); |
| __ DropValues(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringConcat(FullDecoder* decoder, const Value& head, const Value& tail, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister tail_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, tail_reg.gp(), pinned, tail.type); |
| LiftoffAssembler::VarState tail_var(kRef, tail_reg, 0); |
| |
| LiftoffRegister head_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, head_reg.gp(), pinned, head.type); |
| LiftoffAssembler::VarState head_var(kRef, head_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringConcat, |
| MakeSig::Returns(kRef).Params(kRef, kRef), |
| { |
| head_var, |
| tail_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringEq(FullDecoder* decoder, const Value& a, const Value& b, |
| Value* result) { |
| LiftoffRegister result_reg(kReturnRegister0); |
| LiftoffRegList pinned{result_reg}; |
| LiftoffRegister b_reg = pinned.set(__ PopToModifiableRegister(pinned)); |
| LiftoffRegister a_reg = pinned.set(__ PopToModifiableRegister(pinned)); |
| |
| __ SpillAllRegisters(); |
| |
| Label done; |
| |
| { |
| LiftoffRegister null = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| bool check_for_null = a.type.is_nullable() || b.type.is_nullable(); |
| if (check_for_null) { |
| LoadNullValueForCompare(null.gp(), pinned, kWasmStringRef); |
| } |
| |
| FREEZE_STATE(frozen); |
| |
| // If values pointer-equal, result is 1. |
| __ LoadConstant(result_reg, WasmValue(int32_t{1})); |
| __ emit_cond_jump(kEqual, &done, kRefNull, a_reg.gp(), b_reg.gp(), |
| frozen); |
| |
| // Otherwise if either operand is null, result is 0. |
| if (check_for_null) { |
| __ LoadConstant(result_reg, WasmValue(int32_t{0})); |
| if (a.type.is_nullable()) { |
| __ emit_cond_jump(kEqual, &done, kRefNull, a_reg.gp(), null.gp(), |
| frozen); |
| } |
| if (b.type.is_nullable()) { |
| __ emit_cond_jump(kEqual, &done, kRefNull, b_reg.gp(), null.gp(), |
| frozen); |
| } |
| } |
| |
| // Ending the frozen state here is fine, because we already spilled the |
| // rest of the cache, and the subsequent runtime call will reset the cache |
| // state anyway. |
| } |
| |
| // Operands are pointer-distinct and neither is null; call out to the |
| // runtime. |
| LiftoffAssembler::VarState a_var(kRef, a_reg, 0); |
| LiftoffAssembler::VarState b_var(kRef, b_reg, 0); |
| CallRuntimeStub(WasmCode::kWasmStringEqual, |
| MakeSig::Returns(kI32).Params(kRef, kRef), |
| { |
| a_var, |
| b_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| __ bind(&done); |
| |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringIsUSVSequence(FullDecoder* decoder, const Value& str, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState str_var(kRef, str_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringIsUSVSequence, |
| MakeSig::Returns(kI32).Params(kRef), |
| { |
| str_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringAsWtf8(FullDecoder* decoder, const Value& str, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState str_var(kRef, str_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringAsWtf8, |
| MakeSig::Returns(kRef).Params(kRef), |
| { |
| str_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringViewWtf8Advance(FullDecoder* decoder, const Value& view, |
| const Value& pos, const Value& bytes, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& bytes_var = |
| __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState& pos_var = |
| __ cache_state()->stack_state.end()[-2]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewWtf8Advance, |
| MakeSig::Returns(kI32).Params(kRef, kI32, kI32), |
| { |
| view_var, |
| pos_var, |
| bytes_var, |
| }, |
| decoder->position()); |
| __ DropValues(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringViewWtf8Encode(FullDecoder* decoder, |
| const MemoryIndexImmediate& imm, |
| const unibrow::Utf8Variant variant, |
| const Value& view, const Value& addr, |
| const Value& pos, const Value& bytes, |
| Value* next_pos, Value* bytes_written) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& bytes_var = |
| __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState& pos_var = |
| __ cache_state()->stack_state.end()[-2]; |
| LiftoffAssembler::VarState& addr_var = |
| __ cache_state()->stack_state.end()[-3]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-4], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| LiftoffRegister memory_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(memory_reg, imm.index); |
| LiftoffAssembler::VarState memory_var(kSmiKind, memory_reg, 0); |
| |
| LiftoffRegister variant_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(variant_reg, static_cast<int32_t>(variant)); |
| LiftoffAssembler::VarState variant_var(kSmiKind, variant_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewWtf8Encode, |
| MakeSig::Returns(kI32, kI32) |
| .Params(kI32, kI32, kI32, kRef, kSmiKind, kSmiKind), |
| { |
| addr_var, |
| pos_var, |
| bytes_var, |
| view_var, |
| memory_var, |
| variant_var, |
| }, |
| decoder->position()); |
| __ DropValues(4); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister next_pos_reg(kReturnRegister0); |
| __ PushRegister(kI32, next_pos_reg); |
| LiftoffRegister bytes_written_reg(kReturnRegister1); |
| __ PushRegister(kI32, bytes_written_reg); |
| } |
| |
| void StringViewWtf8Slice(FullDecoder* decoder, const Value& view, |
| const Value& start, const Value& end, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& end_var = |
| __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState& start_var = |
| __ cache_state()->stack_state.end()[-2]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewWtf8Slice, |
| MakeSig::Returns(kRef).Params(kRef, kI32, kI32), |
| { |
| view_var, |
| start_var, |
| end_var, |
| }, |
| decoder->position()); |
| __ DropValues(3); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringAsWtf16(FullDecoder* decoder, const Value& str, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState str_var(kRef, str_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringAsWtf16, |
| MakeSig::Returns(kRef).Params(kRef), |
| { |
| str_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringViewWtf16GetCodeUnit(FullDecoder* decoder, const Value& view, |
| const Value& pos, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister pos_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister view_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| LiftoffAssembler::VarState pos_var(kI32, pos_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewWtf16GetCodeUnit, |
| MakeSig::Returns(kI32).Params(kRef, kI32), |
| { |
| view_var, |
| pos_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringViewWtf16Encode(FullDecoder* decoder, |
| const MemoryIndexImmediate& imm, const Value& view, |
| const Value& offset, const Value& pos, |
| const Value& codeunits, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& codeunits_var = |
| __ cache_state()->stack_state.end()[-1]; |
| LiftoffAssembler::VarState& pos_var = |
| __ cache_state()->stack_state.end()[-2]; |
| LiftoffAssembler::VarState& offset_var = |
| __ cache_state()->stack_state.end()[-3]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-4], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| LiftoffRegister memory_reg = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LoadSmi(memory_reg, imm.index); |
| LiftoffAssembler::VarState memory_var(kSmiKind, memory_reg, 0); |
| |
| CallRuntimeStub( |
| WasmCode::kWasmStringViewWtf16Encode, |
| MakeSig::Returns(kI32).Params(kI32, kI32, kI32, kRef, kSmiKind), |
| { |
| offset_var, |
| pos_var, |
| codeunits_var, |
| view_var, |
| memory_var, |
| }, |
| decoder->position()); |
| __ DropValues(4); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringViewWtf16Slice(FullDecoder* decoder, const Value& view, |
| const Value& start, const Value& end, |
| Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister end_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister start_reg = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister view_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| LiftoffAssembler::VarState start_var(kI32, start_reg, 0); |
| LiftoffAssembler::VarState end_var(kI32, end_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewWtf16Slice, |
| MakeSig::Returns(kRef).Params(kRef, kI32, kI32), |
| { |
| view_var, |
| start_var, |
| end_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringAsIter(FullDecoder* decoder, const Value& str, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type); |
| LiftoffAssembler::VarState str_var(kRef, str_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringAsIter, |
| MakeSig::Returns(kRef).Params(kRef), |
| { |
| str_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringViewIterNext(FullDecoder* decoder, const Value& view, |
| Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffRegister view_reg = pinned.set(__ PopToRegister(pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewIterNext, |
| MakeSig::Returns(kI32).Params(kRef), |
| { |
| view_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringViewIterAdvance(FullDecoder* decoder, const Value& view, |
| const Value& codepoints, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& codepoints_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewIterAdvance, |
| MakeSig::Returns(kI32).Params(kRef, kI32), |
| { |
| view_var, |
| codepoints_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ DropValues(2); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringViewIterRewind(FullDecoder* decoder, const Value& view, |
| const Value& codepoints, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& codepoints_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewIterRewind, |
| MakeSig::Returns(kI32).Params(kRef, kI32), |
| { |
| view_var, |
| codepoints_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ DropValues(2); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringViewIterSlice(FullDecoder* decoder, const Value& view, |
| const Value& codepoints, Value* result) { |
| LiftoffRegList pinned; |
| |
| LiftoffAssembler::VarState& codepoints_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| LiftoffRegister view_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); |
| LiftoffAssembler::VarState view_var(kRef, view_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringViewIterSlice, |
| MakeSig::Returns(kRef).Params(kRef, kI32), |
| { |
| view_var, |
| codepoints_var, |
| }, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ DropValues(2); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringCompare(FullDecoder* decoder, const Value& lhs, const Value& rhs, |
| Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister rhs_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-1], pinned)); |
| MaybeEmitNullCheck(decoder, rhs_reg.gp(), pinned, rhs.type); |
| LiftoffAssembler::VarState rhs_var(kRef, rhs_reg, 0); |
| |
| LiftoffRegister lhs_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); |
| MaybeEmitNullCheck(decoder, lhs_reg.gp(), pinned, lhs.type); |
| LiftoffAssembler::VarState lhs_var(kRef, lhs_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kStringCompare, |
| MakeSig::Returns(kSmiKind).Params(kRef, kRef), |
| {lhs_var, rhs_var}, decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ DropValues(2); |
| __ SmiToInt32(kReturnRegister0); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void StringFromCodePoint(FullDecoder* decoder, const Value& code_point, |
| Value* result) { |
| LiftoffAssembler::VarState& codepoint_var = |
| __ cache_state()->stack_state.end()[-1]; |
| |
| CallRuntimeStub(WasmCode::kWasmStringFromCodePoint, |
| MakeSig::Returns(kRef).Params(kI32), {codepoint_var}, |
| decoder->position()); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ DropValues(1); |
| __ PushRegister(kRef, result_reg); |
| } |
| |
| void StringHash(FullDecoder* decoder, const Value& string, Value* result) { |
| LiftoffRegList pinned; |
| LiftoffRegister string_reg = pinned.set( |
| __ LoadToRegister(__ cache_state()->stack_state.end()[-1], pinned)); |
| MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, string.type); |
| LiftoffAssembler::VarState string_var(kRef, string_reg, 0); |
| |
| CallRuntimeStub(WasmCode::kWasmStringHash, |
| MakeSig::Returns(kI32).Params(kRef), {string_var}, |
| decoder->position()); |
| |
| LiftoffRegister result_reg(kReturnRegister0); |
| __ DropValues(1); |
| __ PushRegister(kI32, result_reg); |
| } |
| |
| void Forward(FullDecoder* decoder, const Value& from, Value* to) { |
| // Nothing to do here. |
| } |
| |
| private: |
| void CallDirect(FullDecoder* decoder, const CallFunctionImmediate& imm, |
| const Value args[], Value returns[], TailCall tail_call) { |
| MostlySmallValueKindSig sig(zone_, imm.sig); |
| for (ValueKind ret : sig.returns()) { |
| if (!CheckSupportedType(decoder, ret, "return")) return; |
| } |
| |
| auto call_descriptor = compiler::GetWasmCallDescriptor(zone_, imm.sig); |
| call_descriptor = GetLoweredCallDescriptor(zone_, call_descriptor); |
| |
| // One slot would be enough for call_direct, but would make index |
| // computations much more complicated. |
| size_t vector_slot = encountered_call_instructions_.size() * 2; |
| if (decoder->enabled_.has_inlining()) { |
| encountered_call_instructions_.push_back(imm.index); |
| } |
| |
| if (imm.index < env_->module->num_imported_functions) { |
| // A direct call to an imported function. |
| LiftoffRegList pinned; |
| Register tmp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register target = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| |
| Register imported_targets = tmp; |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(imported_targets, ImportedFunctionTargets, |
| pinned); |
| __ Load( |
| LiftoffRegister(target), imported_targets, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedAddressArray(imm.index), |
| kPointerLoadType); |
| |
| Register imported_function_refs = tmp; |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(imported_function_refs, |
| ImportedFunctionRefs, pinned); |
| Register imported_function_ref = tmp; |
| __ LoadTaggedPointer( |
| imported_function_ref, imported_function_refs, no_reg, |
| ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index)); |
| |
| Register explicit_instance = imported_function_ref; |
| __ PrepareCall(&sig, call_descriptor, &target, explicit_instance); |
| if (tail_call) { |
| __ PrepareTailCall( |
| static_cast<int>(call_descriptor->ParameterSlotCount()), |
| static_cast<int>( |
| call_descriptor->GetStackParameterDelta(descriptor_))); |
| __ TailCallIndirect(target); |
| } else { |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), true); |
| __ CallIndirect(&sig, call_descriptor, target); |
| FinishCall(decoder, &sig, call_descriptor); |
| } |
| } else { |
| // Inlining direct calls isn't speculative, but existence of the |
| // feedback vector currently depends on this flag. |
| if (decoder->enabled_.has_inlining()) { |
| LiftoffRegister vector = __ GetUnusedRegister(kGpReg, {}); |
| __ Fill(vector, liftoff::kFeedbackVectorOffset, kIntPtrKind); |
| __ IncrementSmi(vector, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( |
| static_cast<int>(vector_slot))); |
| // Warning: {vector} may be clobbered by {IncrementSmi}! |
| } |
| // A direct call within this module just gets the current instance. |
| __ PrepareCall(&sig, call_descriptor); |
| // Just encode the function index. This will be patched at instantiation. |
| Address addr = static_cast<Address>(imm.index); |
| if (tail_call) { |
| DCHECK(descriptor_->CanTailCall(call_descriptor)); |
| __ PrepareTailCall( |
| static_cast<int>(call_descriptor->ParameterSlotCount()), |
| static_cast<int>( |
| call_descriptor->GetStackParameterDelta(descriptor_))); |
| __ TailCallNativeWasmCode(addr); |
| } else { |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), true); |
| __ CallNativeWasmCode(addr); |
| FinishCall(decoder, &sig, call_descriptor); |
| } |
| } |
| } |
| |
| void CallIndirect(FullDecoder* decoder, const Value& index_val, |
| const CallIndirectImmediate& imm, TailCall tail_call) { |
| MostlySmallValueKindSig sig(zone_, imm.sig); |
| for (ValueKind ret : sig.returns()) { |
| if (!CheckSupportedType(decoder, ret, "return")) return; |
| } |
| |
| Register index = __ PeekToRegister(0, {}).gp(); |
| |
| LiftoffRegList pinned{index}; |
| // Get all temporary registers unconditionally up front. |
| // We do not use temporary registers directly; instead we rename them as |
| // appropriate in each scope they are used. |
| Register tmp1 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register tmp3 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| Register indirect_function_table = no_reg; |
| if (imm.table_imm.index > 0) { |
| indirect_function_table = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(indirect_function_table, |
| IndirectFunctionTables, pinned); |
| __ LoadTaggedPointer( |
| indirect_function_table, indirect_function_table, no_reg, |
| ObjectAccess::ElementOffsetInTaggedFixedArray(imm.table_imm.index)); |
| } |
| { |
| CODE_COMMENT("Check index is in-bounds"); |
| Register table_size = tmp1; |
| if (imm.table_imm.index == 0) { |
| LOAD_INSTANCE_FIELD(table_size, IndirectFunctionTableSize, kUInt32Size, |
| pinned); |
| } else { |
| __ Load(LiftoffRegister(table_size), indirect_function_table, no_reg, |
| wasm::ObjectAccess::ToTagged( |
| WasmIndirectFunctionTable::kSizeOffset), |
| LoadType::kI32Load); |
| } |
| |
| // Bounds check against the table size: Compare against table size stored |
| // in {instance->indirect_function_table_size}. |
| Label* out_of_bounds_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapTableOutOfBounds); |
| { |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, out_of_bounds_label, kI32, |
| index, table_size, trapping); |
| } |
| } |
| |
| ValueType table_type = decoder->module_->tables[imm.table_imm.index].type; |
| bool needs_type_check = !EquivalentTypes( |
| table_type.AsNonNull(), ValueType::Ref(imm.sig_imm.index), |
| decoder->module_, decoder->module_); |
| bool needs_null_check = table_type.is_nullable(); |
| |
| if (needs_type_check) { |
| CODE_COMMENT("Check indirect call signature"); |
| Register real_sig_id = tmp1; |
| Register formal_sig_id = tmp2; |
| |
| // Load the signature from {instance->ift_sig_ids[key]} |
| if (imm.table_imm.index == 0) { |
| LOAD_INSTANCE_FIELD(real_sig_id, IndirectFunctionTableSigIds, |
| kSystemPointerSize, pinned); |
| } else { |
| __ Load(LiftoffRegister(real_sig_id), indirect_function_table, no_reg, |
| wasm::ObjectAccess::ToTagged( |
| WasmIndirectFunctionTable::kSigIdsOffset), |
| kPointerLoadType); |
| } |
| static_assert((1 << 2) == kInt32Size); |
| __ Load(LiftoffRegister(real_sig_id), real_sig_id, index, 0, |
| LoadType::kI32Load, nullptr, false, false, true); |
| |
| // Compare against expected signature. |
| LOAD_INSTANCE_FIELD(formal_sig_id, IsorecursiveCanonicalTypes, |
| kSystemPointerSize, pinned); |
| __ Load(LiftoffRegister(formal_sig_id), formal_sig_id, no_reg, |
| imm.sig_imm.index * kInt32Size, LoadType::kI32Load); |
| |
| Label* sig_mismatch_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapFuncSigMismatch); |
| __ DropValues(1); |
| |
| if (decoder->enabled_.has_gc() && |
| !decoder->module_->types[imm.sig_imm.index].is_final) { |
| Label success_label; |
| FREEZE_STATE(frozen); |
| __ emit_cond_jump(kEqual, &success_label, kI32, real_sig_id, |
| formal_sig_id, frozen); |
| if (needs_null_check) { |
| __ emit_i32_cond_jumpi(kEqual, sig_mismatch_label, real_sig_id, -1, |
| frozen); |
| } |
| Register real_rtt = tmp3; |
| __ LoadFullPointer( |
| real_rtt, kRootRegister, |
| IsolateData::root_slot_offset(RootIndex::kWasmCanonicalRtts)); |
| __ LoadTaggedPointer(real_rtt, real_rtt, real_sig_id, |
| ObjectAccess::ToTagged(WeakArrayList::kHeaderSize), |
| true); |
| // Remove the weak reference tag. |
| if (kSystemPointerSize == 4) { |
| __ emit_i32_andi(real_rtt, real_rtt, |
| static_cast<int32_t>(~kWeakHeapObjectMask)); |
| } else { |
| __ emit_i64_andi(LiftoffRegister(real_rtt), LiftoffRegister(real_rtt), |
| static_cast<int64_t>(~kWeakHeapObjectMask)); |
| } |
| // Constant-time subtyping check: load exactly one candidate RTT from |
| // the supertypes list. |
| // Step 1: load the WasmTypeInfo. |
| constexpr int kTypeInfoOffset = wasm::ObjectAccess::ToTagged( |
| Map::kConstructorOrBackPointerOrNativeContextOffset); |
| Register type_info = real_rtt; |
| __ LoadTaggedPointer(type_info, real_rtt, no_reg, kTypeInfoOffset); |
| // Step 2: check the list's length if needed. |
| uint32_t rtt_depth = |
| GetSubtypingDepth(decoder->module_, imm.sig_imm.index); |
| if (rtt_depth >= kMinimumSupertypeArraySize) { |
| LiftoffRegister list_length(formal_sig_id); |
| int offset = |
| ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesLengthOffset); |
| __ LoadSmiAsInt32(list_length, type_info, offset); |
| __ emit_i32_cond_jumpi(kUnsignedLessThanEqual, sig_mismatch_label, |
| list_length.gp(), rtt_depth, frozen); |
| } |
| // Step 3: load the candidate list slot, and compare it. |
| Register maybe_match = type_info; |
| __ LoadTaggedPointer( |
| maybe_match, type_info, no_reg, |
| ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset + |
| rtt_depth * kTaggedSize)); |
| Register formal_rtt = formal_sig_id; |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(formal_rtt, ManagedObjectMaps, pinned); |
| __ LoadTaggedPointer( |
| formal_rtt, formal_rtt, no_reg, |
| wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( |
| imm.sig_imm.index)); |
| __ emit_cond_jump(kNotEqual, sig_mismatch_label, kRtt, formal_rtt, |
| maybe_match, frozen); |
| |
| __ bind(&success_label); |
| } else { |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kNotEqual, sig_mismatch_label, kI32, real_sig_id, |
| formal_sig_id, trapping); |
| } |
| } else if (needs_null_check) { |
| CODE_COMMENT("Check indirect call element for nullity"); |
| Register real_sig_id = tmp1; |
| |
| // Load the signature from {instance->ift_sig_ids[key]} |
| if (imm.table_imm.index == 0) { |
| LOAD_INSTANCE_FIELD(real_sig_id, IndirectFunctionTableSigIds, |
| kSystemPointerSize, pinned); |
| } else { |
| __ Load(LiftoffRegister(real_sig_id), indirect_function_table, no_reg, |
| wasm::ObjectAccess::ToTagged( |
| WasmIndirectFunctionTable::kSigIdsOffset), |
| kPointerLoadType); |
| } |
| static_assert((1 << 2) == kInt32Size); |
| __ Load(LiftoffRegister(real_sig_id), real_sig_id, index, 0, |
| LoadType::kI32Load, nullptr, false, false, true); |
| |
| Label* sig_mismatch_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapFuncSigMismatch); |
| __ DropValues(1); |
| |
| FREEZE_STATE(frozen); |
| __ emit_i32_cond_jumpi(kEqual, sig_mismatch_label, real_sig_id, -1, |
| frozen); |
| } else { |
| __ DropValues(1); |
| } |
| { |
| CODE_COMMENT("Execute indirect call"); |
| |
| Register function_instance = tmp1; |
| Register function_target = tmp2; |
| |
| // Load the instance from {instance->ift_instances[key]} |
| if (imm.table_imm.index == 0) { |
| LOAD_TAGGED_PTR_INSTANCE_FIELD(function_instance, |
| IndirectFunctionTableRefs, pinned); |
| } else { |
| __ LoadTaggedPointer(function_instance, indirect_function_table, no_reg, |
| wasm::ObjectAccess::ToTagged( |
| WasmIndirectFunctionTable::kRefsOffset)); |
| } |
| __ LoadTaggedPointer(function_instance, function_instance, index, |
| ObjectAccess::ElementOffsetInTaggedFixedArray(0), |
| true); |
| |
| // Load the target from {instance->ift_targets[key]} |
| if (imm.table_imm.index == 0) { |
| LOAD_INSTANCE_FIELD(function_target, IndirectFunctionTableTargets, |
| kSystemPointerSize, pinned); |
| } else { |
| __ Load(LiftoffRegister(function_target), indirect_function_table, |
| no_reg, |
| wasm::ObjectAccess::ToTagged( |
| WasmIndirectFunctionTable::kTargetsOffset), |
| kPointerLoadType); |
| } |
| __ Load(LiftoffRegister(function_target), function_target, index, 0, |
| kPointerLoadType, nullptr, false, false, true); |
| |
| auto call_descriptor = compiler::GetWasmCallDescriptor(zone_, imm.sig); |
| call_descriptor = GetLoweredCallDescriptor(zone_, call_descriptor); |
| |
| __ PrepareCall(&sig, call_descriptor, &function_target, |
| function_instance); |
| if (tail_call) { |
| __ PrepareTailCall( |
| static_cast<int>(call_descriptor->ParameterSlotCount()), |
| static_cast<int>( |
| call_descriptor->GetStackParameterDelta(descriptor_))); |
| __ TailCallIndirect(function_target); |
| } else { |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), true); |
| __ CallIndirect(&sig, call_descriptor, function_target); |
| |
| FinishCall(decoder, &sig, call_descriptor); |
| } |
| } |
| } |
| |
| void CallRef(FullDecoder* decoder, ValueType func_ref_type, |
| const FunctionSig* type_sig, TailCall tail_call) { |
| MostlySmallValueKindSig sig(zone_, type_sig); |
| for (ValueKind ret : sig.returns()) { |
| if (!CheckSupportedType(decoder, ret, "return")) return; |
| } |
| compiler::CallDescriptor* call_descriptor = |
| compiler::GetWasmCallDescriptor(zone_, type_sig); |
| call_descriptor = GetLoweredCallDescriptor(zone_, call_descriptor); |
| |
| Register target_reg = no_reg, instance_reg = no_reg; |
| |
| if (decoder->enabled_.has_inlining()) { |
| LiftoffRegList pinned; |
| LiftoffRegister func_ref = pinned.set(__ PopToRegister(pinned)); |
| LiftoffRegister vector = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| MaybeEmitNullCheck(decoder, func_ref.gp(), pinned, func_ref_type); |
| LiftoffAssembler::VarState func_ref_var(kRef, func_ref, 0); |
| |
| __ Fill(vector, liftoff::kFeedbackVectorOffset, kRef); |
| LiftoffAssembler::VarState vector_var{kRef, vector, 0}; |
| LiftoffRegister index = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| size_t vector_slot = encountered_call_instructions_.size() * 2; |
| encountered_call_instructions_.push_back( |
| FunctionTypeFeedback::kNonDirectCall); |
| __ LoadConstant(index, WasmValue::ForUintPtr(vector_slot)); |
| LiftoffAssembler::VarState index_var(kIntPtrKind, index, 0); |
| |
| // CallRefIC(vector: FixedArray, index: intptr, |
| // funcref: WasmInternalFunction) |
| CallRuntimeStub(WasmCode::kCallRefIC, |
| MakeSig::Returns(kIntPtrKind, kIntPtrKind) |
| .Params(kRef, kIntPtrKind, kRef), |
| {vector_var, index_var, func_ref_var}, |
| decoder->position()); |
| |
| target_reg = LiftoffRegister(kReturnRegister0).gp(); |
| instance_reg = LiftoffRegister(kReturnRegister1).gp(); |
| |
| } else { // decoder->enabled_.has_inlining() |
| // Non-feedback-collecting version. |
| // Executing a write barrier needs temp registers; doing this on a |
| // conditional branch confuses the LiftoffAssembler's register management. |
| // Spill everything up front to work around that. |
| __ SpillAllRegisters(); |
| |
| // We limit ourselves to four registers: |
| // (1) func_data, initially reused for func_ref. |
| // (2) instance, initially used as temp. |
| // (3) target, initially used as temp. |
| // (4) temp. |
| LiftoffRegList pinned; |
| LiftoffRegister func_ref = pinned.set(__ PopToModifiableRegister(pinned)); |
| MaybeEmitNullCheck(decoder, func_ref.gp(), pinned, func_ref_type); |
| LiftoffRegister instance = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LiftoffRegister target = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LiftoffRegister temp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| |
| // Load "ref" (instance or WasmApiFunctionRef) and target. |
| __ LoadTaggedPointer( |
| instance.gp(), func_ref.gp(), no_reg, |
| wasm::ObjectAccess::ToTagged(WasmInternalFunction::kRefOffset)); |
| |
| #ifdef V8_ENABLE_SANDBOX |
| __ LoadExternalPointer(target.gp(), func_ref.gp(), |
| WasmInternalFunction::kCallTargetOffset, |
| kWasmInternalFunctionCallTargetTag, temp.gp()); |
| #else |
| __ Load( |
| target, func_ref.gp(), no_reg, |
| wasm::ObjectAccess::ToTagged(WasmInternalFunction::kCallTargetOffset), |
| kPointerLoadType); |
| #endif |
| |
| FREEZE_STATE(frozen); |
| Label perform_call; |
| |
| LiftoffRegister null_address = temp; |
| __ LoadConstant(null_address, WasmValue::ForUintPtr(0)); |
| __ emit_cond_jump(kNotEqual, &perform_call, kIntPtrKind, target.gp(), |
| null_address.gp(), frozen); |
| // The cached target can only be null for WasmJSFunctions. |
| __ LoadTaggedPointer( |
| target.gp(), func_ref.gp(), no_reg, |
| wasm::ObjectAccess::ToTagged(WasmInternalFunction::kCodeOffset)); |
| __ LoadCodeInstructionStart(target.gp(), target.gp()); |
| // Fall through to {perform_call}. |
| |
| __ bind(&perform_call); |
| // Now the call target is in {target}, and the right instance object |
| // is in {instance}. |
| target_reg = target.gp(); |
| instance_reg = instance.gp(); |
| } // decoder->enabled_.has_inlining() |
| |
| __ PrepareCall(&sig, call_descriptor, &target_reg, instance_reg); |
| if (tail_call) { |
| __ PrepareTailCall( |
| static_cast<int>(call_descriptor->ParameterSlotCount()), |
| static_cast<int>( |
| call_descriptor->GetStackParameterDelta(descriptor_))); |
| __ TailCallIndirect(target_reg); |
| } else { |
| source_position_table_builder_.AddPosition( |
| __ pc_offset(), SourcePosition(decoder->position()), true); |
| __ CallIndirect(&sig, call_descriptor, target_reg); |
| |
| FinishCall(decoder, &sig, call_descriptor); |
| } |
| } |
| |
| void LoadNullValue(Register null, LiftoffRegList pinned, ValueType type) { |
| __ LoadFullPointer( |
| null, kRootRegister, |
| type == kWasmExternRef || type == kWasmNullExternRef |
| ? IsolateData::root_slot_offset(RootIndex::kNullValue) |
| : IsolateData::root_slot_offset(RootIndex::kWasmNull)); |
| } |
| |
| // Stores the null value representation in the passed register. |
| // If pointer compression is active, only the compressed tagged pointer |
| // will be stored. Any operations with this register therefore must |
| // not compare this against 64 bits using quadword instructions. |
| void LoadNullValueForCompare(Register null, LiftoffRegList pinned, |
| ValueType type) { |
| Tagged_t static_null = |
| wasm::GetWasmEngine()->compressed_wasm_null_value_or_zero(); |
| if (type != kWasmExternRef && type != kWasmNullExternRef && |
| static_null != 0) { |
| // static_null is only set for builds with pointer compression. |
| DCHECK_LE(static_null, std::numeric_limits<uint32_t>::max()); |
| __ LoadConstant(LiftoffRegister(null), |
| WasmValue(static_cast<uint32_t>(static_null))); |
| } else { |
| LoadNullValue(null, pinned, type); |
| } |
| } |
| |
| void LoadExceptionSymbol(Register dst, LiftoffRegList pinned, |
| RootIndex root_index) { |
| __ LoadFullPointer(dst, kRootRegister, |
| IsolateData::root_slot_offset(root_index)); |
| } |
| |
| void MaybeEmitNullCheck(FullDecoder* decoder, Register object, |
| LiftoffRegList pinned, ValueType type) { |
| if (v8_flags.experimental_wasm_skip_null_checks || !type.is_nullable()) { |
| return; |
| } |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapNullDereference); |
| LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); |
| LoadNullValueForCompare(null.gp(), pinned, type); |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kEqual, trap_label, kRefNull, object, null.gp(), |
| trapping); |
| } |
| |
| void BoundsCheckArray(FullDecoder* decoder, LiftoffRegister array, |
| LiftoffRegister index, LiftoffRegList pinned) { |
| if (V8_UNLIKELY(v8_flags.experimental_wasm_skip_bounds_checks)) return; |
| Label* trap_label = |
| AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayOutOfBounds); |
| LiftoffRegister length = __ GetUnusedRegister(kGpReg, pinned); |
| constexpr int kLengthOffset = |
| wasm::ObjectAccess::ToTagged(WasmArray::kLengthOffset); |
| __ Load(length, array.gp(), no_reg, kLengthOffset, LoadType::kI32Load); |
| FREEZE_STATE(trapping); |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, trap_label, kI32, index.gp(), |
| length.gp(), trapping); |
| } |
| |
| int StructFieldOffset(const StructType* struct_type, int field_index) { |
| return wasm::ObjectAccess::ToTagged(WasmStruct::kHeaderSize + |
| struct_type->field_offset(field_index)); |
| } |
| |
| void LoadObjectField(LiftoffRegister dst, Register src, Register offset_reg, |
| int offset, ValueKind kind, bool is_signed, |
| LiftoffRegList pinned) { |
| if (is_reference(kind)) { |
| __ LoadTaggedPointer(dst.gp(), src, offset_reg, offset); |
| } else { |
| // Primitive kind. |
| LoadType load_type = LoadType::ForValueKind(kind, is_signed); |
| __ Load(dst, src, offset_reg, offset, load_type); |
| } |
| } |
| |
| void StoreObjectField(Register obj, Register offset_reg, int offset, |
| LiftoffRegister value, LiftoffRegList pinned, |
| ValueKind kind, |
| LiftoffAssembler::SkipWriteBarrier skip_write_barrier = |
| LiftoffAssembler::kNoSkipWriteBarrier) { |
| if (is_reference(kind)) { |
| __ StoreTaggedPointer(obj, offset_reg, offset, value, pinned, |
| skip_write_barrier); |
| } else { |
| // Primitive kind. |
| StoreType store_type = StoreType::ForValueKind(kind); |
| __ Store(obj, offset_reg, offset, value, store_type, pinned); |
| } |
| } |
| |
| void SetDefaultValue(LiftoffRegister reg, ValueType type, |
| LiftoffRegList pinned) { |
| DCHECK(is_defaultable(type.kind())); |
| switch (type.kind()) { |
| case kI8: |
| case kI16: |
| case kI32: |
| return __ LoadConstant(reg, WasmValue(int32_t{0})); |
| case kI64: |
| return __ LoadConstant(reg, WasmValue(int64_t{0})); |
| case kF32: |
| return __ LoadConstant(reg, WasmValue(float{0.0})); |
| case kF64: |
| return __ LoadConstant(reg, WasmValue(double{0.0})); |
| case kS128: |
| DCHECK(CpuFeatures::SupportsWasmSimd128()); |
| return __ emit_s128_xor(reg, reg, reg); |
| case kRefNull: |
| return LoadNullValue(reg.gp(), pinned, type); |
| case kRtt: |
| case kVoid: |
| case kBottom: |
| case kRef: |
| UNREACHABLE(); |
| } |
| } |
| |
| void MaybeOSR() { |
| if (V8_UNLIKELY(for_debugging_)) { |
| __ MaybeOSR(); |
| } |
| } |
| |
| void FinishCall(FullDecoder* decoder, ValueKindSig* sig, |
| compiler::CallDescriptor* call_descriptor) { |
| DefineSafepoint(); |
| RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); |
| int pc_offset = __ pc_offset(); |
| MaybeOSR(); |
| EmitLandingPad(decoder, pc_offset); |
| __ FinishCall(sig, call_descriptor); |
| } |
| |
| void CheckNan(LiftoffRegister src, LiftoffRegList pinned, ValueKind kind) { |
| DCHECK(kind == ValueKind::kF32 || kind == ValueKind::kF64); |
| auto nondeterminism_addr = __ GetUnusedRegister(kGpReg, pinned); |
| __ LoadConstant( |
| nondeterminism_addr, |
| WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(nondeterminism_))); |
| __ emit_set_if_nan(nondeterminism_addr.gp(), src.fp(), kind); |
| } |
| |
| void CheckS128Nan(LiftoffRegister dst, LiftoffRegList pinned, |
| ValueKind lane_kind) { |
| RegClass rc = reg_class_for(kS128); |
| LiftoffRegister tmp_gp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| LiftoffRegister tmp_s128 = pinned.set(__ GetUnusedRegister(rc, pinned)); |
| LiftoffRegister nondeterminism_addr = |
| pinned.set(__ GetUnusedRegister(kGpReg, pinned)); |
| __ LoadConstant( |
| nondeterminism_addr, |
| WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(nondeterminism_))); |
| __ emit_s128_set_if_nan(nondeterminism_addr.gp(), dst, tmp_gp.gp(), |
| tmp_s128, lane_kind); |
| } |
| |
| void ArrayFillImpl(LiftoffRegList pinned, LiftoffRegister obj, |
| LiftoffRegister index, LiftoffRegister value, |
| LiftoffRegister length, ValueKind elem_kind, |
| LiftoffAssembler::SkipWriteBarrier skip_write_barrier) { |
| // initial_offset = WasmArray::kHeaderSize + index * elem_size. |
| LiftoffRegister offset = index; |
| if (value_kind_size_log2(elem_kind) != 0) { |
| __ emit_i32_shli(offset.gp(), index.gp(), |
| value_kind_size_log2(elem_kind)); |
| } |
| __ emit_i32_addi(offset.gp(), offset.gp(), |
| wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize)); |
| |
| // end_offset = initial_offset + length * elem_size. |
| LiftoffRegister end_offset = length; |
| if (value_kind_size_log2(elem_kind) != 0) { |
| __ emit_i32_shli(end_offset.gp(), length.gp(), |
| value_kind_size_log2(elem_kind)); |
| } |
| __ emit_i32_add(end_offset.gp(), end_offset.gp(), offset.gp()); |
| |
| Label loop, done; |
| __ bind(&loop); |
| { |
| // This is subtle: {StoreObjectField} can request a temp register, which |
| // is precisely what {FREEZE_STATE} (with non-trivial live range) is |
| // supposed to guard against. In this case it's fine though, because we |
| // are explicitly requesting {unused_and_unpinned} here, therefore we know |
| // a register is available, so requesting a temp register won't actually |
| // cause any state changes. |
| // TODO(jkummerow): See if we can make this more elegant, e.g. by passing |
| // a temp register to {StoreObjectField}. |
| LiftoffRegister unused_and_unpinned = __ GetUnusedRegister(pinned); |
| USE(unused_and_unpinned); |
| FREEZE_STATE(in_this_case_its_fine); |
| __ emit_cond_jump(kUnsignedGreaterThanEqual, &done, kI32, offset.gp(), |
| end_offset.gp(), in_this_case_its_fine); |
| } |
| StoreObjectField(obj.gp(), offset.gp(), 0, value, pinned, elem_kind, |
| skip_write_barrier); |
| __ emit_i32_addi(offset.gp(), offset.gp(), value_kind_size(elem_kind)); |
| __ emit_jump(&loop); |
| |
| __ bind(&done); |
| } |
| |
| bool has_outstanding_op() const { |
| return outstanding_op_ != kNoOutstandingOp; |
| } |
| |
| bool test_and_reset_outstanding_op(WasmOpcode opcode) { |
| DCHECK_NE(kNoOutstandingOp, opcode); |
| if (outstanding_op_ != opcode) return false; |
| outstanding_op_ = kNoOutstandingOp; |
| return true; |
| } |
| |
| void TraceCacheState(FullDecoder* decoder) const { |
| if (!v8_flags.trace_liftoff) return; |
| StdoutStream os; |
| for (int control_depth = decoder->control_depth() - 1; control_depth >= -1; |
| --control_depth) { |
| auto* cache_state = |
| control_depth == -1 ? __ cache_state() |
| : &decoder->control_at(control_depth) |
| ->label_state; |
| os << PrintCollection(cache_state->stack_state); |
| if (control_depth != -1) PrintF("; "); |
| } |
| os << "\n"; |
| } |
| |
| void DefineSafepoint() { |
| auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_); |
| __ cache_state()->DefineSafepoint(safepoint); |
| } |
| |
| void DefineSafepointWithCalleeSavedRegisters() { |
| auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_); |
| __ cache_state()->DefineSafepointWithCalleeSavedRegisters(safepoint); |
| } |
| |
| // Return a register holding the instance, populating the "cached instance" |
| // register if possible. If no free register is available, the cache is not |
| // set and we use {fallback} instead. This can be freely overwritten by the |
| // caller then. |
| V8_INLINE Register LoadInstanceIntoRegister(LiftoffRegList pinned, |
| Register fallback) { |
| Register instance = __ cache_state()->cached_instance; |
| if (V8_UNLIKELY(instance == no_reg)) { |
| instance = LoadInstanceIntoRegister_Slow(pinned, fallback); |
| } |
| return instance; |
| } |
| |
| V8_NOINLINE V8_PRESERVE_MOST Register |
| LoadInstanceIntoRegister_Slow(LiftoffRegList pinned, Register fallback) { |
| DCHECK_EQ(no_reg, __ cache_state()->cached_instance); |
| SCOPED_CODE_COMMENT("load instance"); |
| Register instance = __ cache_state()->TrySetCachedInstanceRegister( |
| pinned | LiftoffRegList{fallback}); |
| if (instance == no_reg) instance = fallback; |
| __ LoadInstanceFromFrame(instance); |
| return instance; |
| } |
| |
| static constexpr WasmOpcode kNoOutstandingOp = kExprUnreachable; |
| static constexpr base::EnumSet<ValueKind> kUnconditionallySupported{ |
| // MVP: |
| kI32, kI64, kF32, kF64, |
| // Extern ref: |
| kRef, kRefNull, kRtt, kI8, kI16}; |
| |
| LiftoffAssembler asm_; |
| |
| // Used for merging code generation of subsequent operations (via look-ahead). |
| // Set by the first opcode, reset by the second. |
| WasmOpcode outstanding_op_ = kNoOutstandingOp; |
| |
| // {supported_types_} is updated in {MaybeBailoutForUnsupportedType}. |
| base::EnumSet<ValueKind> supported_types_ = kUnconditionallySupported; |
| compiler::CallDescriptor* const descriptor_; |
| CompilationEnv* const env_; |
| DebugSideTableBuilder* const debug_sidetable_builder_; |
| const ForDebugging for_debugging_; |
| LiftoffBailoutReason bailout_reason_ = kSuccess; |
| const int func_index_; |
| ZoneVector<OutOfLineCode> out_of_line_code_; |
| SourcePositionTableBuilder source_position_table_builder_; |
| ZoneVector<trap_handler::ProtectedInstructionData> protected_instructions_; |
| // Zone used to store information during compilation. The result will be |
| // stored independently, such that this zone can die together with the |
| // LiftoffCompiler after compilation. |
| Zone* zone_; |
| SafepointTableBuilder safepoint_table_builder_; |
| // The pc offset of the instructions to reserve the stack frame. Needed to |
| // patch the actually needed stack size in the end. |
| uint32_t pc_offset_stack_frame_construction_ = 0; |
| // For emitting breakpoint, we store a pointer to the position of the next |
| // breakpoint, and a pointer after the list of breakpoints as end marker. |
| // A single breakpoint at offset 0 indicates that we should prepare the |
| // function for stepping by flooding it with breakpoints. |
| const int* next_breakpoint_ptr_ = nullptr; |
| const int* next_breakpoint_end_ = nullptr; |
| |
| // Introduce a dead breakpoint to ensure that the calculation of the return |
| // address in OSR is correct. |
| int dead_breakpoint_ = 0; |
| |
| // Remember whether the did function-entry break checks (for "hook on function |
| // call" and "break on entry" a.k.a. instrumentation breakpoint). This happens |
| // at the first breakable opcode in the function (if compiling for debugging). |
| bool did_function_entry_break_checks_ = false; |
| |
| struct HandlerInfo { |
| MovableLabel handler; |
| int pc_offset; |
| }; |
| |
| ZoneVector<HandlerInfo> handlers_; |
| int handler_table_offset_ = Assembler::kNoHandlerTable; |
| |
| // Current number of exception refs on the stack. |
| int num_exceptions_ = 0; |
| |
| // Updated during compilation on every "call" or "call_ref" instruction. |
| // Holds the call target, or {FunctionTypeFeedback::kNonDirectCall} for |
| // "call_ref". |
| // After compilation, this is transferred into {WasmModule::type_feedback}. |
| std::vector<uint32_t> encountered_call_instructions_; |
| |
| int32_t* max_steps_; |
| int32_t* nondeterminism_; |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(LiftoffCompiler); |
| }; |
| |
| // static |
| constexpr WasmOpcode LiftoffCompiler::kNoOutstandingOp; |
| // static |
| constexpr base::EnumSet<ValueKind> LiftoffCompiler::kUnconditionallySupported; |
| |
| std::unique_ptr<AssemblerBuffer> NewLiftoffAssemblerBuffer( |
| AssemblerBufferCache* assembler_buffer_cache, int func_body_size) { |
| size_t code_size_estimate = |
| WasmCodeManager::EstimateLiftoffCodeSize(func_body_size); |
| // Allocate the initial buffer a bit bigger to avoid reallocation during code |
| // generation. Overflows when casting to int are fine, as we will allocate at |
| // least {AssemblerBase::kMinimalBufferSize} anyway, so in the worst case we |
| // have to grow more often. |
| int initial_buffer_size = static_cast<int>(128 + code_size_estimate * 4 / 3); |
| |
| return assembler_buffer_cache |
| ? assembler_buffer_cache->GetAssemblerBuffer(initial_buffer_size) |
| : NewAssemblerBuffer(initial_buffer_size); |
| } |
| |
| } // namespace |
| |
| WasmCompilationResult ExecuteLiftoffCompilation( |
| CompilationEnv* env, const FunctionBody& func_body, |
| const LiftoffOptions& compiler_options) { |
| DCHECK(compiler_options.is_initialized()); |
| // Liftoff does not validate the code, so that should have run before. |
| DCHECK(env->module->function_was_validated(compiler_options.func_index)); |
| base::TimeTicks start_time; |
| if (V8_UNLIKELY(v8_flags.trace_wasm_compilation_times)) { |
| start_time = base::TimeTicks::Now(); |
| } |
| int func_body_size = static_cast<int>(func_body.end - func_body.start); |
| TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), |
| "wasm.CompileBaseline", "funcIndex", compiler_options.func_index, |
| "bodySize", func_body_size); |
| |
| Zone zone(GetWasmEngine()->allocator(), "LiftoffCompilationZone"); |
| auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, func_body.sig); |
| |
| std::unique_ptr<DebugSideTableBuilder> debug_sidetable_builder; |
| if (compiler_options.debug_sidetable) { |
| debug_sidetable_builder = std::make_unique<DebugSideTableBuilder>(); |
| } |
| DCHECK_IMPLIES(compiler_options.max_steps, |
| compiler_options.for_debugging == kForDebugging); |
| WasmFeatures unused_detected_features; |
| |
| WasmFullDecoder<Decoder::NoValidationTag, LiftoffCompiler> decoder( |
| &zone, env->module, env->enabled_features, |
| compiler_options.detected_features ? compiler_options.detected_features |
| : &unused_detected_features, |
| func_body, call_descriptor, env, &zone, |
| NewLiftoffAssemblerBuffer(compiler_options.assembler_buffer_cache, |
| func_body_size), |
| debug_sidetable_builder.get(), compiler_options); |
| decoder.Decode(); |
| LiftoffCompiler* compiler = &decoder.interface(); |
| if (decoder.failed()) compiler->OnFirstError(&decoder); |
| |
| if (auto* counters = compiler_options.counters) { |
| // Check that the histogram for the bailout reasons has the correct size. |
| DCHECK_EQ(0, counters->liftoff_bailout_reasons()->min()); |
| DCHECK_EQ(kNumBailoutReasons - 1, |
| counters->liftoff_bailout_reasons()->max()); |
| DCHECK_EQ(kNumBailoutReasons, |
| counters->liftoff_bailout_reasons()->num_buckets()); |
| // Register the bailout reason (can also be {kSuccess}). |
| counters->liftoff_bailout_reasons()->AddSample( |
| static_cast<int>(compiler->bailout_reason())); |
| } |
| |
| if (compiler->did_bailout()) return WasmCompilationResult{}; |
| |
| WasmCompilationResult result; |
| compiler->GetCode(&result.code_desc); |
| result.instr_buffer = compiler->ReleaseBuffer(); |
| result.source_positions = compiler->GetSourcePositionTable(); |
| result.protected_instructions_data = compiler->GetProtectedInstructionsData(); |
| result.frame_slot_count = compiler->GetTotalFrameSlotCountForGC(); |
| auto* lowered_call_desc = GetLoweredCallDescriptor(&zone, call_descriptor); |
| result.tagged_parameter_slots = lowered_call_desc->GetTaggedParameterSlots(); |
| result.func_index = compiler_options.func_index; |
| result.result_tier = ExecutionTier::kLiftoff; |
| result.for_debugging = compiler_options.for_debugging; |
| result.frame_has_feedback_slot = env->enabled_features.has_inlining(); |
| if (auto* debug_sidetable = compiler_options.debug_sidetable) { |
| *debug_sidetable = debug_sidetable_builder->GenerateDebugSideTable(); |
| } |
| |
| if (V8_UNLIKELY(v8_flags.trace_wasm_compilation_times)) { |
| base::TimeDelta time = base::TimeTicks::Now() - start_time; |
| int codesize = result.code_desc.body_size(); |
| StdoutStream{} << "Compiled function " |
| << reinterpret_cast<const void*>(env->module) << "#" |
| << compiler_options.func_index << " using Liftoff, took " |
| << time.InMilliseconds() << " ms and " |
| << zone.allocation_size() << " bytes; bodysize " |
| << func_body_size << " codesize " << codesize << std::endl; |
| } |
| |
| DCHECK(result.succeeded()); |
| |
| return result; |
| } |
| |
| std::unique_ptr<DebugSideTable> GenerateLiftoffDebugSideTable( |
| const WasmCode* code) { |
| auto* native_module = code->native_module(); |
| auto* function = &native_module->module()->functions[code->index()]; |
| ModuleWireBytes wire_bytes{native_module->wire_bytes()}; |
| base::Vector<const byte> function_bytes = |
| wire_bytes.GetFunctionBytes(function); |
| CompilationEnv env = native_module->CreateCompilationEnv(); |
| FunctionBody func_body{function->sig, 0, function_bytes.begin(), |
| function_bytes.end()}; |
| |
| Zone zone(GetWasmEngine()->allocator(), "LiftoffDebugSideTableZone"); |
| auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, function->sig); |
| DebugSideTableBuilder debug_sidetable_builder; |
| WasmFeatures detected; |
| constexpr int kSteppingBreakpoints[] = {0}; |
| DCHECK(code->for_debugging() == kForDebugging || |
| code->for_debugging() == kForStepping); |
| base::Vector<const int> breakpoints = |
| code->for_debugging() == kForStepping |
| ? base::ArrayVector(kSteppingBreakpoints) |
| : base::Vector<const int>{}; |
| WasmFullDecoder<Decoder::NoValidationTag, LiftoffCompiler> decoder( |
| &zone, native_module->module(), env.enabled_features, &detected, |
| func_body, call_descriptor, &env, &zone, |
| NewAssemblerBuffer(AssemblerBase::kDefaultBufferSize), |
| &debug_sidetable_builder, |
| LiftoffOptions{} |
| .set_func_index(code->index()) |
| .set_for_debugging(code->for_debugging()) |
| .set_breakpoints(breakpoints)); |
| decoder.Decode(); |
| DCHECK(decoder.ok()); |
| DCHECK(!decoder.interface().did_bailout()); |
| return debug_sidetable_builder.GenerateDebugSideTable(); |
| } |
| |
| } // namespace v8::internal::wasm |