1// Copyright 2017 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/wasm/baseline/liftoff-compiler.h" 6 7#include "src/base/enum-set.h" 8#include "src/base/optional.h" 9#include "src/base/platform/wrappers.h" 10#include "src/codegen/assembler-inl.h" 11// TODO(clemensb): Remove dependences on compiler stuff. 12#include "src/codegen/external-reference.h" 13#include "src/codegen/interface-descriptors-inl.h" 14#include "src/codegen/machine-type.h" 15#include "src/codegen/macro-assembler-inl.h" 16#include "src/compiler/linkage.h" 17#include "src/compiler/wasm-compiler.h" 18#include "src/logging/counters.h" 19#include "src/logging/log.h" 20#include "src/objects/smi.h" 21#include "src/tracing/trace-event.h" 22#include "src/utils/ostreams.h" 23#include "src/utils/utils.h" 24#include "src/wasm/baseline/liftoff-assembler.h" 25#include "src/wasm/baseline/liftoff-register.h" 26#include "src/wasm/function-body-decoder-impl.h" 27#include "src/wasm/function-compiler.h" 28#include "src/wasm/memory-tracing.h" 29#include "src/wasm/object-access.h" 30#include "src/wasm/simd-shuffle.h" 31#include "src/wasm/wasm-debug.h" 32#include "src/wasm/wasm-engine.h" 33#include "src/wasm/wasm-linkage.h" 34#include "src/wasm/wasm-objects.h" 35#include "src/wasm/wasm-opcodes-inl.h" 36 37namespace v8 { 38namespace internal { 39namespace wasm { 40 41constexpr auto kRegister = LiftoffAssembler::VarState::kRegister; 42constexpr auto kIntConst = LiftoffAssembler::VarState::kIntConst; 43constexpr auto kStack = LiftoffAssembler::VarState::kStack; 44 45namespace { 46 47#define __ asm_. 48 49#define TRACE(...) \ 50 do { \ 51 if (FLAG_trace_liftoff) PrintF("[liftoff] " __VA_ARGS__); \ 52 } while (false) 53 54#define WASM_INSTANCE_OBJECT_FIELD_OFFSET(name) \ 55 ObjectAccess::ToTagged(WasmInstanceObject::k##name##Offset) 56 57template <int expected_size, int actual_size> 58struct assert_field_size { 59 static_assert(expected_size == actual_size, 60 "field in WasmInstance does not have the expected size"); 61 static constexpr int size = actual_size; 62}; 63 64#define WASM_INSTANCE_OBJECT_FIELD_SIZE(name) \ 65 FIELD_SIZE(WasmInstanceObject::k##name##Offset) 66 67#define LOAD_INSTANCE_FIELD(dst, name, load_size, pinned) \ 68 __ LoadFromInstance(dst, LoadInstanceIntoRegister(pinned, dst), \ 69 WASM_INSTANCE_OBJECT_FIELD_OFFSET(name), \ 70 assert_field_size<WASM_INSTANCE_OBJECT_FIELD_SIZE(name), \ 71 load_size>::size); 72 73#define LOAD_TAGGED_PTR_INSTANCE_FIELD(dst, name, pinned) \ 74 static_assert(WASM_INSTANCE_OBJECT_FIELD_SIZE(name) == kTaggedSize, \ 75 "field in WasmInstance does not have the expected size"); \ 76 __ LoadTaggedPointerFromInstance(dst, LoadInstanceIntoRegister(pinned, dst), \ 77 WASM_INSTANCE_OBJECT_FIELD_OFFSET(name)); 78 79#ifdef V8_CODE_COMMENTS 80#define CODE_COMMENT(str) \ 81 do { \ 82 __ RecordComment(str); \ 83 } while (false) 84#else 85#define CODE_COMMENT(str) ((void)0) 86#endif 87 88constexpr LoadType::LoadTypeValue kPointerLoadType = 89 kSystemPointerSize == 8 ? LoadType::kI64Load : LoadType::kI32Load; 90 91constexpr ValueKind kPointerKind = LiftoffAssembler::kPointerKind; 92constexpr ValueKind kSmiKind = LiftoffAssembler::kSmiKind; 93constexpr ValueKind kTaggedKind = LiftoffAssembler::kTaggedKind; 94 95// Used to construct fixed-size signatures: MakeSig::Returns(...).Params(...); 96using MakeSig = FixedSizeSignature<ValueKind>; 97 98#if V8_TARGET_ARCH_ARM64 99// On ARM64, the Assembler keeps track of pointers to Labels to resolve 100// branches to distant targets. Moving labels would confuse the Assembler, 101// thus store the label on the heap and keep a unique_ptr. 102class MovableLabel { 103 public: 104 MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(MovableLabel); 105 MovableLabel() : label_(new Label()) {} 106 107 Label* get() { return label_.get(); } 108 109 private: 110 std::unique_ptr<Label> label_; 111}; 112#else 113// On all other platforms, just store the Label directly. 114class MovableLabel { 115 public: 116 MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(MovableLabel); 117 118 Label* get() { return &label_; } 119 120 private: 121 Label label_; 122}; 123#endif 124 125compiler::CallDescriptor* GetLoweredCallDescriptor( 126 Zone* zone, compiler::CallDescriptor* call_desc) { 127 return kSystemPointerSize == 4 128 ? compiler::GetI32WasmCallDescriptor(zone, call_desc) 129 : call_desc; 130} 131 132constexpr LiftoffRegList GetGpParamRegisters() { 133 LiftoffRegList registers; 134 for (auto reg : kGpParamRegisters) registers.set(reg); 135 return registers; 136} 137 138constexpr LiftoffCondition GetCompareCondition(WasmOpcode opcode) { 139 switch (opcode) { 140 case kExprI32Eq: 141 return kEqual; 142 case kExprI32Ne: 143 return kUnequal; 144 case kExprI32LtS: 145 return kSignedLessThan; 146 case kExprI32LtU: 147 return kUnsignedLessThan; 148 case kExprI32GtS: 149 return kSignedGreaterThan; 150 case kExprI32GtU: 151 return kUnsignedGreaterThan; 152 case kExprI32LeS: 153 return kSignedLessEqual; 154 case kExprI32LeU: 155 return kUnsignedLessEqual; 156 case kExprI32GeS: 157 return kSignedGreaterEqual; 158 case kExprI32GeU: 159 return kUnsignedGreaterEqual; 160 default: 161 UNREACHABLE(); 162 } 163} 164 165// Builds a {DebugSideTable}. 166class DebugSideTableBuilder { 167 using Entry = DebugSideTable::Entry; 168 using Value = Entry::Value; 169 170 public: 171 enum AssumeSpilling { 172 // All register values will be spilled before the pc covered by the debug 173 // side table entry. Register slots will be marked as stack slots in the 174 // generated debug side table entry. 175 kAssumeSpilling, 176 // Register slots will be written out as they are. 177 kAllowRegisters, 178 // Register slots cannot appear since we already spilled. 179 kDidSpill 180 }; 181 182 class EntryBuilder { 183 public: 184 explicit EntryBuilder(int pc_offset, int stack_height, 185 std::vector<Value> changed_values) 186 : pc_offset_(pc_offset), 187 stack_height_(stack_height), 188 changed_values_(std::move(changed_values)) {} 189 190 Entry ToTableEntry() { 191 return Entry{pc_offset_, stack_height_, std::move(changed_values_)}; 192 } 193 194 void MinimizeBasedOnPreviousStack(const std::vector<Value>& last_values) { 195 auto dst = changed_values_.begin(); 196 auto end = changed_values_.end(); 197 for (auto src = dst; src != end; ++src) { 198 if (src->index < static_cast<int>(last_values.size()) && 199 *src == last_values[src->index]) { 200 continue; 201 } 202 if (dst != src) *dst = *src; 203 ++dst; 204 } 205 changed_values_.erase(dst, end); 206 } 207 208 int pc_offset() const { return pc_offset_; } 209 void set_pc_offset(int new_pc_offset) { pc_offset_ = new_pc_offset; } 210 211 private: 212 int pc_offset_; 213 int stack_height_; 214 std::vector<Value> changed_values_; 215 }; 216 217 // Adds a new entry in regular code. 218 void NewEntry(int pc_offset, 219 base::Vector<DebugSideTable::Entry::Value> values) { 220 entries_.emplace_back(pc_offset, static_cast<int>(values.size()), 221 GetChangedStackValues(last_values_, values)); 222 } 223 224 // Adds a new entry for OOL code, and returns a pointer to a builder for 225 // modifying that entry. 226 EntryBuilder* NewOOLEntry(base::Vector<DebugSideTable::Entry::Value> values) { 227 constexpr int kNoPcOffsetYet = -1; 228 ool_entries_.emplace_back(kNoPcOffsetYet, static_cast<int>(values.size()), 229 GetChangedStackValues(last_ool_values_, values)); 230 return &ool_entries_.back(); 231 } 232 233 void SetNumLocals(int num_locals) { 234 DCHECK_EQ(-1, num_locals_); 235 DCHECK_LE(0, num_locals); 236 num_locals_ = num_locals; 237 } 238 239 std::unique_ptr<DebugSideTable> GenerateDebugSideTable() { 240 DCHECK_LE(0, num_locals_); 241 242 // Connect {entries_} and {ool_entries_} by removing redundant stack 243 // information from the first {ool_entries_} entry (based on 244 // {last_values_}). 245 if (!entries_.empty() && !ool_entries_.empty()) { 246 ool_entries_.front().MinimizeBasedOnPreviousStack(last_values_); 247 } 248 249 std::vector<Entry> entries; 250 entries.reserve(entries_.size() + ool_entries_.size()); 251 for (auto& entry : entries_) entries.push_back(entry.ToTableEntry()); 252 for (auto& entry : ool_entries_) entries.push_back(entry.ToTableEntry()); 253 DCHECK(std::is_sorted( 254 entries.begin(), entries.end(), 255 [](Entry& a, Entry& b) { return a.pc_offset() < b.pc_offset(); })); 256 return std::make_unique<DebugSideTable>(num_locals_, std::move(entries)); 257 } 258 259 private: 260 static std::vector<Value> GetChangedStackValues( 261 std::vector<Value>& last_values, 262 base::Vector<DebugSideTable::Entry::Value> values) { 263 std::vector<Value> changed_values; 264 int old_stack_size = static_cast<int>(last_values.size()); 265 last_values.resize(values.size()); 266 267 int index = 0; 268 for (const auto& value : values) { 269 if (index >= old_stack_size || last_values[index] != value) { 270 changed_values.push_back(value); 271 last_values[index] = value; 272 } 273 ++index; 274 } 275 return changed_values; 276 } 277 278 int num_locals_ = -1; 279 // Keep a snapshot of the stack of the last entry, to generate a delta to the 280 // next entry. 281 std::vector<Value> last_values_; 282 std::vector<EntryBuilder> entries_; 283 // Keep OOL code entries separate so we can do proper delta-encoding (more 284 // entries might be added between the existing {entries_} and the 285 // {ool_entries_}). Store the entries in a list so the pointer is not 286 // invalidated by adding more entries. 287 std::vector<Value> last_ool_values_; 288 std::list<EntryBuilder> ool_entries_; 289}; 290 291void CheckBailoutAllowed(LiftoffBailoutReason reason, const char* detail, 292 const CompilationEnv* env) { 293 // Decode errors are ok. 294 if (reason == kDecodeError) return; 295 296 // --liftoff-only ensures that tests actually exercise the Liftoff path 297 // without bailing out. We also fail for missing CPU support, to avoid 298 // running any TurboFan code under --liftoff-only. 299 if (FLAG_liftoff_only) { 300 FATAL("--liftoff-only: treating bailout as fatal error. Cause: %s", detail); 301 } 302 303 // Missing CPU features are generally OK, except with --liftoff-only. 304 if (reason == kMissingCPUFeature) return; 305 306 // If --enable-testing-opcode-in-wasm is set, we are expected to bailout with 307 // "testing opcode". 308 if (FLAG_enable_testing_opcode_in_wasm && 309 strcmp(detail, "testing opcode") == 0) { 310 return; 311 } 312 313 // Some externally maintained architectures don't fully implement Liftoff yet. 314#if V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_S390X || \ 315 V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64 || V8_TARGET_ARCH_LOONG64 316 return; 317#endif 318 319#if V8_TARGET_ARCH_ARM 320 // Allow bailout for missing ARMv7 support. 321 if (!CpuFeatures::IsSupported(ARMv7) && reason == kUnsupportedArchitecture) { 322 return; 323 } 324#endif 325 326#define LIST_FEATURE(name, ...) kFeature_##name, 327 constexpr WasmFeatures kExperimentalFeatures{ 328 FOREACH_WASM_EXPERIMENTAL_FEATURE_FLAG(LIST_FEATURE)}; 329#undef LIST_FEATURE 330 331 // Bailout is allowed if any experimental feature is enabled. 332 if (env->enabled_features.contains_any(kExperimentalFeatures)) return; 333 334 // Otherwise, bailout is not allowed. 335 FATAL("Liftoff bailout should not happen. Cause: %s\n", detail); 336} 337 338class LiftoffCompiler { 339 public: 340 // TODO(clemensb): Make this a template parameter. 341 static constexpr Decoder::ValidateFlag validate = Decoder::kBooleanValidation; 342 343 using Value = ValueBase<validate>; 344 345 struct ElseState { 346 MovableLabel label; 347 LiftoffAssembler::CacheState state; 348 }; 349 350 struct TryInfo { 351 TryInfo() = default; 352 LiftoffAssembler::CacheState catch_state; 353 Label catch_label; 354 bool catch_reached = false; 355 bool in_handler = false; 356 }; 357 358 struct Control : public ControlBase<Value, validate> { 359 std::unique_ptr<ElseState> else_state; 360 LiftoffAssembler::CacheState label_state; 361 MovableLabel label; 362 std::unique_ptr<TryInfo> try_info; 363 // Number of exceptions on the stack below this control. 364 int num_exceptions = 0; 365 366 MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Control); 367 368 template <typename... Args> 369 explicit Control(Args&&... args) V8_NOEXCEPT 370 : ControlBase(std::forward<Args>(args)...) {} 371 }; 372 373 using FullDecoder = WasmFullDecoder<validate, LiftoffCompiler>; 374 using ValueKindSig = LiftoffAssembler::ValueKindSig; 375 376 class MostlySmallValueKindSig : public Signature<ValueKind> { 377 public: 378 MostlySmallValueKindSig(Zone* zone, const FunctionSig* sig) 379 : Signature<ValueKind>(sig->return_count(), sig->parameter_count(), 380 MakeKinds(inline_storage_, zone, sig)) {} 381 382 private: 383 static constexpr size_t kInlineStorage = 8; 384 385 static ValueKind* MakeKinds(ValueKind* storage, Zone* zone, 386 const FunctionSig* sig) { 387 const size_t size = sig->parameter_count() + sig->return_count(); 388 if (V8_UNLIKELY(size > kInlineStorage)) { 389 storage = zone->NewArray<ValueKind>(size); 390 } 391 std::transform(sig->all().begin(), sig->all().end(), storage, 392 [](ValueType type) { return type.kind(); }); 393 return storage; 394 } 395 396 ValueKind inline_storage_[kInlineStorage]; 397 }; 398 399 // For debugging, we need to spill registers before a trap or a stack check to 400 // be able to inspect them. 401 struct SpilledRegistersForInspection : public ZoneObject { 402 struct Entry { 403 int offset; 404 LiftoffRegister reg; 405 ValueKind kind; 406 }; 407 ZoneVector<Entry> entries; 408 409 explicit SpilledRegistersForInspection(Zone* zone) : entries(zone) {} 410 }; 411 412 struct OutOfLineSafepointInfo { 413 ZoneVector<int> slots; 414 LiftoffRegList spills; 415 416 explicit OutOfLineSafepointInfo(Zone* zone) : slots(zone) {} 417 }; 418 419 struct OutOfLineCode { 420 MovableLabel label; 421 MovableLabel continuation; 422 WasmCode::RuntimeStubId stub; 423 WasmCodePosition position; 424 LiftoffRegList regs_to_save; 425 Register cached_instance; 426 OutOfLineSafepointInfo* safepoint_info; 427 uint32_t pc; // for trap handler. 428 // These two pointers will only be used for debug code: 429 SpilledRegistersForInspection* spilled_registers; 430 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder; 431 432 // Named constructors: 433 static OutOfLineCode Trap( 434 WasmCode::RuntimeStubId s, WasmCodePosition pos, 435 SpilledRegistersForInspection* spilled_registers, 436 OutOfLineSafepointInfo* safepoint_info, uint32_t pc, 437 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) { 438 DCHECK_LT(0, pos); 439 return { 440 {}, // label 441 {}, // continuation 442 s, // stub 443 pos, // position 444 {}, // regs_to_save 445 no_reg, // cached_instance 446 safepoint_info, // safepoint_info 447 pc, // pc 448 spilled_registers, // spilled_registers 449 debug_sidetable_entry_builder // debug_side_table_entry_builder 450 }; 451 } 452 static OutOfLineCode StackCheck( 453 WasmCodePosition pos, LiftoffRegList regs_to_save, 454 Register cached_instance, SpilledRegistersForInspection* spilled_regs, 455 OutOfLineSafepointInfo* safepoint_info, 456 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) { 457 return { 458 {}, // label 459 {}, // continuation 460 WasmCode::kWasmStackGuard, // stub 461 pos, // position 462 regs_to_save, // regs_to_save 463 cached_instance, // cached_instance 464 safepoint_info, // safepoint_info 465 0, // pc 466 spilled_regs, // spilled_registers 467 debug_sidetable_entry_builder // debug_side_table_entry_builder 468 }; 469 } 470 static OutOfLineCode TierupCheck( 471 WasmCodePosition pos, LiftoffRegList regs_to_save, 472 Register cached_instance, SpilledRegistersForInspection* spilled_regs, 473 OutOfLineSafepointInfo* safepoint_info, 474 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) { 475 return { 476 {}, // label 477 {}, // continuation, 478 WasmCode::kWasmTriggerTierUp, // stub 479 pos, // position 480 regs_to_save, // regs_to_save 481 cached_instance, // cached_instance 482 safepoint_info, // safepoint_info 483 0, // pc 484 spilled_regs, // spilled_registers 485 debug_sidetable_entry_builder // debug_side_table_entry_builder 486 }; 487 } 488 }; 489 490 LiftoffCompiler(compiler::CallDescriptor* call_descriptor, 491 CompilationEnv* env, Zone* compilation_zone, 492 std::unique_ptr<AssemblerBuffer> buffer, 493 DebugSideTableBuilder* debug_sidetable_builder, 494 ForDebugging for_debugging, int func_index, 495 base::Vector<const int> breakpoints = {}, 496 int dead_breakpoint = 0, int32_t* max_steps = nullptr, 497 int32_t* nondeterminism = nullptr) 498 : asm_(std::move(buffer)), 499 descriptor_( 500 GetLoweredCallDescriptor(compilation_zone, call_descriptor)), 501 env_(env), 502 debug_sidetable_builder_(debug_sidetable_builder), 503 for_debugging_(for_debugging), 504 func_index_(func_index), 505 out_of_line_code_(compilation_zone), 506 source_position_table_builder_(compilation_zone), 507 protected_instructions_(compilation_zone), 508 compilation_zone_(compilation_zone), 509 safepoint_table_builder_(compilation_zone_), 510 next_breakpoint_ptr_(breakpoints.begin()), 511 next_breakpoint_end_(breakpoints.end()), 512 dead_breakpoint_(dead_breakpoint), 513 handlers_(compilation_zone), 514 max_steps_(max_steps), 515 nondeterminism_(nondeterminism) { 516 if (breakpoints.empty()) { 517 next_breakpoint_ptr_ = next_breakpoint_end_ = nullptr; 518 } 519 } 520 521 bool did_bailout() const { return bailout_reason_ != kSuccess; } 522 LiftoffBailoutReason bailout_reason() const { return bailout_reason_; } 523 524 void GetCode(CodeDesc* desc) { 525 asm_.GetCode(nullptr, desc, &safepoint_table_builder_, 526 handler_table_offset_); 527 } 528 529 std::unique_ptr<AssemblerBuffer> ReleaseBuffer() { 530 return asm_.ReleaseBuffer(); 531 } 532 533 base::OwnedVector<uint8_t> GetSourcePositionTable() { 534 return source_position_table_builder_.ToSourcePositionTableVector(); 535 } 536 537 base::OwnedVector<uint8_t> GetProtectedInstructionsData() const { 538 return base::OwnedVector<uint8_t>::Of(base::Vector<const uint8_t>::cast( 539 base::VectorOf(protected_instructions_))); 540 } 541 542 uint32_t GetTotalFrameSlotCountForGC() const { 543 return __ GetTotalFrameSlotCountForGC(); 544 } 545 546 int GetFeedbackVectorSlots() const { 547 // The number of instructions is capped by max function size. 548 STATIC_ASSERT(kV8MaxWasmFunctionSize < std::numeric_limits<int>::max()); 549 return static_cast<int>(num_call_instructions_) * 2; 550 } 551 552 void unsupported(FullDecoder* decoder, LiftoffBailoutReason reason, 553 const char* detail) { 554 DCHECK_NE(kSuccess, reason); 555 if (did_bailout()) return; 556 bailout_reason_ = reason; 557 TRACE("unsupported: %s\n", detail); 558 decoder->errorf(decoder->pc_offset(), "unsupported liftoff operation: %s", 559 detail); 560 UnuseLabels(decoder); 561 CheckBailoutAllowed(reason, detail, env_); 562 } 563 564 bool DidAssemblerBailout(FullDecoder* decoder) { 565 if (decoder->failed() || !__ did_bailout()) return false; 566 unsupported(decoder, __ bailout_reason(), __ bailout_detail()); 567 return true; 568 } 569 570 V8_INLINE bool CheckSupportedType(FullDecoder* decoder, ValueKind kind, 571 const char* context) { 572 if (V8_LIKELY(supported_types_.contains(kind))) return true; 573 return MaybeBailoutForUnsupportedType(decoder, kind, context); 574 } 575 576 V8_NOINLINE bool MaybeBailoutForUnsupportedType(FullDecoder* decoder, 577 ValueKind kind, 578 const char* context) { 579 DCHECK(!supported_types_.contains(kind)); 580 581 // Lazily update {supported_types_}; then check again. 582 if (CpuFeatures::SupportsWasmSimd128()) supported_types_.Add(kS128); 583 if (supported_types_.contains(kind)) return true; 584 585 LiftoffBailoutReason bailout_reason; 586 switch (kind) { 587 case kS128: 588 bailout_reason = kMissingCPUFeature; 589 break; 590 case kRef: 591 case kOptRef: 592 case kRtt: 593 case kI8: 594 case kI16: 595 bailout_reason = kGC; 596 break; 597 default: 598 UNREACHABLE(); 599 } 600 base::EmbeddedVector<char, 128> buffer; 601 SNPrintF(buffer, "%s %s", name(kind), context); 602 unsupported(decoder, bailout_reason, buffer.begin()); 603 return false; 604 } 605 606 void UnuseLabels(FullDecoder* decoder) { 607#ifdef DEBUG 608 auto Unuse = [](Label* label) { 609 label->Unuse(); 610 label->UnuseNear(); 611 }; 612 // Unuse all labels now, otherwise their destructor will fire a DCHECK error 613 // if they where referenced before. 614 uint32_t control_depth = decoder ? decoder->control_depth() : 0; 615 for (uint32_t i = 0; i < control_depth; ++i) { 616 Control* c = decoder->control_at(i); 617 Unuse(c->label.get()); 618 if (c->else_state) Unuse(c->else_state->label.get()); 619 if (c->try_info != nullptr) Unuse(&c->try_info->catch_label); 620 } 621 for (auto& ool : out_of_line_code_) Unuse(ool.label.get()); 622#endif 623 } 624 625 void StartFunction(FullDecoder* decoder) { 626 if (FLAG_trace_liftoff && !FLAG_trace_wasm_decoder) { 627 StdoutStream{} << "hint: add --trace-wasm-decoder to also see the wasm " 628 "instructions being decoded\n"; 629 } 630 int num_locals = decoder->num_locals(); 631 __ set_num_locals(num_locals); 632 for (int i = 0; i < num_locals; ++i) { 633 ValueKind kind = decoder->local_type(i).kind(); 634 __ set_local_kind(i, kind); 635 } 636 } 637 638 constexpr static LiftoffRegList RegsUnusedByParams() { 639 LiftoffRegList regs = kGpCacheRegList; 640 for (auto reg : kGpParamRegisters) { 641 regs.clear(reg); 642 } 643 return regs; 644 } 645 646 // Returns the number of inputs processed (1 or 2). 647 uint32_t ProcessParameter(ValueKind kind, uint32_t input_idx) { 648 const bool needs_pair = needs_gp_reg_pair(kind); 649 const ValueKind reg_kind = needs_pair ? kI32 : kind; 650 const RegClass rc = reg_class_for(reg_kind); 651 652 auto LoadToReg = [this, reg_kind, rc](compiler::LinkageLocation location, 653 LiftoffRegList pinned) { 654 if (location.IsRegister()) { 655 DCHECK(!location.IsAnyRegister()); 656 return LiftoffRegister::from_external_code(rc, reg_kind, 657 location.AsRegister()); 658 } 659 DCHECK(location.IsCallerFrameSlot()); 660 // For reference type parameters we have to use registers that were not 661 // used for parameters because some reference type stack parameters may 662 // get processed before some value type register parameters. 663 static constexpr auto kRegsUnusedByParams = RegsUnusedByParams(); 664 LiftoffRegister reg = is_reference(reg_kind) 665 ? __ GetUnusedRegister(kRegsUnusedByParams) 666 : __ GetUnusedRegister(rc, pinned); 667 __ LoadCallerFrameSlot(reg, -location.AsCallerFrameSlot(), reg_kind); 668 return reg; 669 }; 670 671 LiftoffRegister reg = 672 LoadToReg(descriptor_->GetInputLocation(input_idx), {}); 673 if (needs_pair) { 674 LiftoffRegister reg2 = LoadToReg( 675 descriptor_->GetInputLocation(input_idx + 1), LiftoffRegList{reg}); 676 reg = LiftoffRegister::ForPair(reg.gp(), reg2.gp()); 677 } 678 __ PushRegister(kind, reg); 679 680 return needs_pair ? 2 : 1; 681 } 682 683 void StackCheck(FullDecoder* decoder, WasmCodePosition position) { 684 CODE_COMMENT("stack check"); 685 if (!FLAG_wasm_stack_checks || !env_->runtime_exception_support) return; 686 687 // Loading the limit address can change the stack state, hence do this 688 // before storing information about registers. 689 Register limit_address = __ GetUnusedRegister(kGpReg, {}).gp(); 690 LOAD_INSTANCE_FIELD(limit_address, StackLimitAddress, kSystemPointerSize, 691 {}); 692 693 LiftoffRegList regs_to_save = __ cache_state()->used_registers; 694 // The cached instance will be reloaded separately. 695 if (__ cache_state()->cached_instance != no_reg) { 696 DCHECK(regs_to_save.has(__ cache_state()->cached_instance)); 697 regs_to_save.clear(__ cache_state()->cached_instance); 698 } 699 SpilledRegistersForInspection* spilled_regs = nullptr; 700 701 OutOfLineSafepointInfo* safepoint_info = 702 compilation_zone_->New<OutOfLineSafepointInfo>(compilation_zone_); 703 __ cache_state()->GetTaggedSlotsForOOLCode( 704 &safepoint_info->slots, &safepoint_info->spills, 705 for_debugging_ 706 ? LiftoffAssembler::CacheState::SpillLocation::kStackSlots 707 : LiftoffAssembler::CacheState::SpillLocation::kTopOfStack); 708 if (V8_UNLIKELY(for_debugging_)) { 709 // When debugging, we do not just push all registers to the stack, but we 710 // spill them to their proper stack locations such that we can inspect 711 // them. 712 // The only exception is the cached memory start, which we just push 713 // before the stack check and pop afterwards. 714 regs_to_save = {}; 715 if (__ cache_state()->cached_mem_start != no_reg) { 716 regs_to_save.set(__ cache_state()->cached_mem_start); 717 } 718 spilled_regs = GetSpilledRegistersForInspection(); 719 } 720 out_of_line_code_.push_back(OutOfLineCode::StackCheck( 721 position, regs_to_save, __ cache_state()->cached_instance, spilled_regs, 722 safepoint_info, RegisterOOLDebugSideTableEntry(decoder))); 723 OutOfLineCode& ool = out_of_line_code_.back(); 724 __ StackCheck(ool.label.get(), limit_address); 725 __ bind(ool.continuation.get()); 726 } 727 728 void TierupCheck(FullDecoder* decoder, WasmCodePosition position, 729 int budget_used) { 730 // We should always decrement the budget, and we don't expect integer 731 // overflows in the budget calculation. 732 DCHECK_LE(1, budget_used); 733 734 if (for_debugging_ != kNoDebugging) return; 735 CODE_COMMENT("tierup check"); 736 // We never want to blow the entire budget at once. 737 const int kMax = FLAG_wasm_tiering_budget / 4; 738 if (budget_used > kMax) budget_used = kMax; 739 740 LiftoffRegister budget_reg = __ GetUnusedRegister(kGpReg, {}); 741 __ Fill(budget_reg, liftoff::kTierupBudgetOffset, ValueKind::kI32); 742 LiftoffRegList regs_to_save = __ cache_state()->used_registers; 743 // The cached instance will be reloaded separately. 744 if (__ cache_state()->cached_instance != no_reg) { 745 DCHECK(regs_to_save.has(__ cache_state()->cached_instance)); 746 regs_to_save.clear(__ cache_state()->cached_instance); 747 } 748 SpilledRegistersForInspection* spilled_regs = nullptr; 749 750 OutOfLineSafepointInfo* safepoint_info = 751 compilation_zone_->New<OutOfLineSafepointInfo>(compilation_zone_); 752 __ cache_state()->GetTaggedSlotsForOOLCode( 753 &safepoint_info->slots, &safepoint_info->spills, 754 LiftoffAssembler::CacheState::SpillLocation::kTopOfStack); 755 out_of_line_code_.push_back(OutOfLineCode::TierupCheck( 756 position, regs_to_save, __ cache_state()->cached_instance, spilled_regs, 757 safepoint_info, RegisterOOLDebugSideTableEntry(decoder))); 758 OutOfLineCode& ool = out_of_line_code_.back(); 759 __ emit_i32_subi_jump_negative(budget_reg.gp(), budget_used, 760 ool.label.get()); 761 __ Spill(liftoff::kTierupBudgetOffset, budget_reg, ValueKind::kI32); 762 __ bind(ool.continuation.get()); 763 } 764 765 bool SpillLocalsInitially(FullDecoder* decoder, uint32_t num_params) { 766 int actual_locals = __ num_locals() - num_params; 767 DCHECK_LE(0, actual_locals); 768 constexpr int kNumCacheRegisters = kLiftoffAssemblerGpCacheRegs.Count(); 769 // If we have many locals, we put them on the stack initially. This avoids 770 // having to spill them on merge points. Use of these initial values should 771 // be rare anyway. 772 if (actual_locals > kNumCacheRegisters / 2) return true; 773 // If there are locals which are not i32 or i64, we also spill all locals, 774 // because other types cannot be initialized to constants. 775 for (uint32_t param_idx = num_params; param_idx < __ num_locals(); 776 ++param_idx) { 777 ValueKind kind = __ local_kind(param_idx); 778 if (kind != kI32 && kind != kI64) return true; 779 } 780 return false; 781 } 782 783 void TraceFunctionEntry(FullDecoder* decoder) { 784 CODE_COMMENT("trace function entry"); 785 __ SpillAllRegisters(); 786 source_position_table_builder_.AddPosition( 787 __ pc_offset(), SourcePosition(decoder->position()), false); 788 __ CallRuntimeStub(WasmCode::kWasmTraceEnter); 789 DefineSafepoint(); 790 } 791 792 bool dynamic_tiering() { 793 return env_->dynamic_tiering == DynamicTiering::kEnabled && 794 for_debugging_ == kNoDebugging && 795 (FLAG_wasm_tier_up_filter == -1 || 796 FLAG_wasm_tier_up_filter == func_index_); 797 } 798 799 void StartFunctionBody(FullDecoder* decoder, Control* block) { 800 for (uint32_t i = 0; i < __ num_locals(); ++i) { 801 if (!CheckSupportedType(decoder, __ local_kind(i), "param")) return; 802 } 803 804 // Parameter 0 is the instance parameter. 805 uint32_t num_params = 806 static_cast<uint32_t>(decoder->sig_->parameter_count()); 807 808 __ CodeEntry(); 809 810 __ EnterFrame(StackFrame::WASM); 811 __ set_has_frame(true); 812 pc_offset_stack_frame_construction_ = __ PrepareStackFrame(); 813 // {PrepareStackFrame} is the first platform-specific assembler method. 814 // If this failed, we can bail out immediately, avoiding runtime overhead 815 // and potential failures because of other unimplemented methods. 816 // A platform implementing {PrepareStackFrame} must ensure that we can 817 // finish compilation without errors even if we hit unimplemented 818 // LiftoffAssembler methods. 819 if (DidAssemblerBailout(decoder)) return; 820 821 // Input 0 is the call target, the instance is at 1. 822 constexpr int kInstanceParameterIndex = 1; 823 // Check that {kWasmInstanceRegister} matches our call descriptor. 824 DCHECK_EQ(kWasmInstanceRegister, 825 Register::from_code( 826 descriptor_->GetInputLocation(kInstanceParameterIndex) 827 .AsRegister())); 828 __ cache_state()->SetInstanceCacheRegister(kWasmInstanceRegister); 829 // Load the feedback vector and cache it in a stack slot. 830 constexpr LiftoffRegList kGpParamRegisters = GetGpParamRegisters(); 831 if (FLAG_wasm_speculative_inlining) { 832 CODE_COMMENT("load feedback vector"); 833 int declared_func_index = 834 func_index_ - env_->module->num_imported_functions; 835 DCHECK_GE(declared_func_index, 0); 836 LiftoffRegList pinned = kGpParamRegisters; 837 LiftoffRegister tmp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 838 __ LoadTaggedPointerFromInstance( 839 tmp.gp(), kWasmInstanceRegister, 840 WASM_INSTANCE_OBJECT_FIELD_OFFSET(FeedbackVectors)); 841 __ LoadTaggedPointer(tmp.gp(), tmp.gp(), no_reg, 842 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( 843 declared_func_index), 844 pinned); 845 __ Spill(liftoff::kFeedbackVectorOffset, tmp, kPointerKind); 846 } 847 if (dynamic_tiering()) { 848 CODE_COMMENT("load tier up budget"); 849 LiftoffRegList pinned = kGpParamRegisters; 850 LiftoffRegister tmp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 851 LOAD_INSTANCE_FIELD(tmp.gp(), TieringBudgetArray, kSystemPointerSize, 852 pinned); 853 uint32_t offset = 854 kInt32Size * declared_function_index(env_->module, func_index_); 855 __ Load(tmp, tmp.gp(), no_reg, offset, LoadType::kI32Load, pinned); 856 __ Spill(liftoff::kTierupBudgetOffset, tmp, ValueKind::kI32); 857 } 858 if (for_debugging_) __ ResetOSRTarget(); 859 860 // Process parameters. 861 if (num_params) CODE_COMMENT("process parameters"); 862 // Input 0 is the code target, 1 is the instance. First parameter at 2. 863 uint32_t input_idx = kInstanceParameterIndex + 1; 864 for (uint32_t param_idx = 0; param_idx < num_params; ++param_idx) { 865 input_idx += ProcessParameter(__ local_kind(param_idx), input_idx); 866 } 867 int params_size = __ TopSpillOffset(); 868 DCHECK_EQ(input_idx, descriptor_->InputCount()); 869 870 // Initialize locals beyond parameters. 871 if (num_params < __ num_locals()) CODE_COMMENT("init locals"); 872 if (SpillLocalsInitially(decoder, num_params)) { 873 bool has_refs = false; 874 for (uint32_t param_idx = num_params; param_idx < __ num_locals(); 875 ++param_idx) { 876 ValueKind kind = __ local_kind(param_idx); 877 has_refs |= is_reference(kind); 878 __ PushStack(kind); 879 } 880 int spill_size = __ TopSpillOffset() - params_size; 881 __ FillStackSlotsWithZero(params_size, spill_size); 882 883 // Initialize all reference type locals with ref.null. 884 if (has_refs) { 885 Register null_ref_reg = __ GetUnusedRegister(kGpReg, {}).gp(); 886 LoadNullValue(null_ref_reg, {}); 887 for (uint32_t local_index = num_params; local_index < __ num_locals(); 888 ++local_index) { 889 ValueKind kind = __ local_kind(local_index); 890 if (is_reference(kind)) { 891 __ Spill(__ cache_state()->stack_state[local_index].offset(), 892 LiftoffRegister(null_ref_reg), kind); 893 } 894 } 895 } 896 } else { 897 for (uint32_t param_idx = num_params; param_idx < __ num_locals(); 898 ++param_idx) { 899 ValueKind kind = __ local_kind(param_idx); 900 // Anything which is not i32 or i64 requires spilling. 901 DCHECK(kind == kI32 || kind == kI64); 902 __ PushConstant(kind, int32_t{0}); 903 } 904 } 905 906 DCHECK_EQ(__ num_locals(), __ cache_state()->stack_height()); 907 908 if (V8_UNLIKELY(debug_sidetable_builder_)) { 909 debug_sidetable_builder_->SetNumLocals(__ num_locals()); 910 } 911 912 // The function-prologue stack check is associated with position 0, which 913 // is never a position of any instruction in the function. 914 StackCheck(decoder, 0); 915 916 if (FLAG_trace_wasm) TraceFunctionEntry(decoder); 917 } 918 919 void GenerateOutOfLineCode(OutOfLineCode* ool) { 920 CODE_COMMENT( 921 (std::string("OOL: ") + GetRuntimeStubName(ool->stub)).c_str()); 922 __ bind(ool->label.get()); 923 const bool is_stack_check = ool->stub == WasmCode::kWasmStackGuard; 924 const bool is_tierup = ool->stub == WasmCode::kWasmTriggerTierUp; 925 926 // Only memory OOB traps need a {pc}, but not unconditionally. Static OOB 927 // accesses do not need protected instruction information, hence they also 928 // do not set {pc}. 929 DCHECK_IMPLIES(ool->stub != WasmCode::kThrowWasmTrapMemOutOfBounds, 930 ool->pc == 0); 931 932 if (env_->bounds_checks == kTrapHandler && ool->pc != 0) { 933 uint32_t pc = static_cast<uint32_t>(__ pc_offset()); 934 DCHECK_EQ(pc, __ pc_offset()); 935 protected_instructions_.emplace_back( 936 trap_handler::ProtectedInstructionData{ool->pc, pc}); 937 } 938 939 if (!env_->runtime_exception_support) { 940 // We cannot test calls to the runtime in cctest/test-run-wasm. 941 // Therefore we emit a call to C here instead of a call to the runtime. 942 // In this mode, we never generate stack checks. 943 DCHECK(!is_stack_check); 944 __ CallTrapCallbackForTesting(); 945 __ LeaveFrame(StackFrame::WASM); 946 __ DropStackSlotsAndRet( 947 static_cast<uint32_t>(descriptor_->ParameterSlotCount())); 948 return; 949 } 950 951 if (!ool->regs_to_save.is_empty()) { 952 __ PushRegisters(ool->regs_to_save); 953 } 954 if (V8_UNLIKELY(ool->spilled_registers != nullptr)) { 955 for (auto& entry : ool->spilled_registers->entries) { 956 // We should not push and spill the same register. 957 DCHECK(!ool->regs_to_save.has(entry.reg)); 958 __ Spill(entry.offset, entry.reg, entry.kind); 959 } 960 } 961 962 source_position_table_builder_.AddPosition( 963 __ pc_offset(), SourcePosition(ool->position), true); 964 __ CallRuntimeStub(ool->stub); 965 auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_); 966 967 if (ool->safepoint_info) { 968 for (auto index : ool->safepoint_info->slots) { 969 safepoint.DefineTaggedStackSlot(index); 970 } 971 972 int total_frame_size = __ GetTotalFrameSize(); 973 LiftoffRegList gp_regs = ool->regs_to_save & kGpCacheRegList; 974 // {total_frame_size} is the highest offset from the FP that is used to 975 // store a value. The offset of the first spill slot should therefore be 976 // {(total_frame_size / kSystemPointerSize) + 1}. However, spill slots 977 // don't start at offset '0' but at offset '-1' (or 978 // {-kSystemPointerSize}). Therefore we have to add another '+ 1' to the 979 // index of the first spill slot. 980 int index = (total_frame_size / kSystemPointerSize) + 2; 981 982 __ RecordSpillsInSafepoint(safepoint, gp_regs, 983 ool->safepoint_info->spills, index); 984 } 985 if (is_tierup) { 986 // Reset the budget. 987 __ Spill(liftoff::kTierupBudgetOffset, 988 WasmValue(FLAG_wasm_tiering_budget)); 989 } 990 991 DCHECK_EQ(!debug_sidetable_builder_, !ool->debug_sidetable_entry_builder); 992 if (V8_UNLIKELY(ool->debug_sidetable_entry_builder)) { 993 ool->debug_sidetable_entry_builder->set_pc_offset(__ pc_offset()); 994 } 995 DCHECK_EQ(ool->continuation.get()->is_bound(), is_stack_check || is_tierup); 996 if (is_stack_check) { 997 MaybeOSR(); 998 } 999 if (!ool->regs_to_save.is_empty()) __ PopRegisters(ool->regs_to_save); 1000 if (is_stack_check || is_tierup) { 1001 if (V8_UNLIKELY(ool->spilled_registers != nullptr)) { 1002 DCHECK(for_debugging_); 1003 for (auto& entry : ool->spilled_registers->entries) { 1004 __ Fill(entry.reg, entry.offset, entry.kind); 1005 } 1006 } 1007 if (ool->cached_instance != no_reg) { 1008 __ LoadInstanceFromFrame(ool->cached_instance); 1009 } 1010 __ emit_jump(ool->continuation.get()); 1011 } else { 1012 __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap); 1013 } 1014 } 1015 1016 void FinishFunction(FullDecoder* decoder) { 1017 if (DidAssemblerBailout(decoder)) return; 1018 __ AlignFrameSize(); 1019#if DEBUG 1020 int frame_size = __ GetTotalFrameSize(); 1021#endif 1022 for (OutOfLineCode& ool : out_of_line_code_) { 1023 GenerateOutOfLineCode(&ool); 1024 } 1025 DCHECK_EQ(frame_size, __ GetTotalFrameSize()); 1026 __ PatchPrepareStackFrame(pc_offset_stack_frame_construction_, 1027 &safepoint_table_builder_); 1028 __ FinishCode(); 1029 safepoint_table_builder_.Emit(&asm_, __ GetTotalFrameSlotCountForGC()); 1030 // Emit the handler table. 1031 if (!handlers_.empty()) { 1032 handler_table_offset_ = HandlerTable::EmitReturnTableStart(&asm_); 1033 for (auto& handler : handlers_) { 1034 HandlerTable::EmitReturnEntry(&asm_, handler.pc_offset, 1035 handler.handler.get()->pos()); 1036 } 1037 } 1038 __ MaybeEmitOutOfLineConstantPool(); 1039 // The previous calls may have also generated a bailout. 1040 DidAssemblerBailout(decoder); 1041 DCHECK_EQ(num_exceptions_, 0); 1042 } 1043 1044 void OnFirstError(FullDecoder* decoder) { 1045 if (!did_bailout()) bailout_reason_ = kDecodeError; 1046 UnuseLabels(decoder); 1047 asm_.AbortCompilation(); 1048 } 1049 1050 V8_NOINLINE void EmitDebuggingInfo(FullDecoder* decoder, WasmOpcode opcode) { 1051 DCHECK(for_debugging_); 1052 if (!WasmOpcodes::IsBreakable(opcode)) return; 1053 bool has_breakpoint = false; 1054 if (next_breakpoint_ptr_) { 1055 if (*next_breakpoint_ptr_ == 0) { 1056 // A single breakpoint at offset 0 indicates stepping. 1057 DCHECK_EQ(next_breakpoint_ptr_ + 1, next_breakpoint_end_); 1058 has_breakpoint = true; 1059 } else { 1060 while (next_breakpoint_ptr_ != next_breakpoint_end_ && 1061 *next_breakpoint_ptr_ < decoder->position()) { 1062 // Skip unreachable breakpoints. 1063 ++next_breakpoint_ptr_; 1064 } 1065 if (next_breakpoint_ptr_ == next_breakpoint_end_) { 1066 next_breakpoint_ptr_ = next_breakpoint_end_ = nullptr; 1067 } else if (*next_breakpoint_ptr_ == decoder->position()) { 1068 has_breakpoint = true; 1069 } 1070 } 1071 } 1072 if (has_breakpoint) { 1073 CODE_COMMENT("breakpoint"); 1074 EmitBreakpoint(decoder); 1075 // Once we emitted an unconditional breakpoint, we don't need to check 1076 // function entry breaks any more. 1077 did_function_entry_break_checks_ = true; 1078 } else if (!did_function_entry_break_checks_) { 1079 did_function_entry_break_checks_ = true; 1080 CODE_COMMENT("check function entry break"); 1081 Label do_break; 1082 Label no_break; 1083 Register flag = __ GetUnusedRegister(kGpReg, {}).gp(); 1084 1085 // Check the "hook on function call" flag. If set, trigger a break. 1086 LOAD_INSTANCE_FIELD(flag, HookOnFunctionCallAddress, kSystemPointerSize, 1087 {}); 1088 __ Load(LiftoffRegister{flag}, flag, no_reg, 0, LoadType::kI32Load8U, {}); 1089 __ emit_cond_jump(kNotEqualZero, &do_break, kI32, flag); 1090 1091 // Check if we should stop on "script entry". 1092 LOAD_INSTANCE_FIELD(flag, BreakOnEntry, kUInt8Size, {}); 1093 __ emit_cond_jump(kEqualZero, &no_break, kI32, flag); 1094 1095 __ bind(&do_break); 1096 EmitBreakpoint(decoder); 1097 __ bind(&no_break); 1098 } else if (dead_breakpoint_ == decoder->position()) { 1099 DCHECK(!next_breakpoint_ptr_ || 1100 *next_breakpoint_ptr_ != dead_breakpoint_); 1101 // The top frame is paused at this position, but the breakpoint was 1102 // removed. Adding a dead breakpoint here ensures that the source 1103 // position exists, and that the offset to the return address is the 1104 // same as in the old code. 1105 CODE_COMMENT("dead breakpoint"); 1106 Label cont; 1107 __ emit_jump(&cont); 1108 EmitBreakpoint(decoder); 1109 __ bind(&cont); 1110 } 1111 if (V8_UNLIKELY(max_steps_ != nullptr)) { 1112 CODE_COMMENT("check max steps"); 1113 LiftoffRegList pinned; 1114 LiftoffRegister max_steps = __ GetUnusedRegister(kGpReg, {}); 1115 pinned.set(max_steps); 1116 LiftoffRegister max_steps_addr = __ GetUnusedRegister(kGpReg, pinned); 1117 pinned.set(max_steps_addr); 1118 __ LoadConstant( 1119 max_steps_addr, 1120 WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(max_steps_))); 1121 __ Load(max_steps, max_steps_addr.gp(), no_reg, 0, LoadType::kI32Load, 1122 pinned); 1123 Label cont; 1124 __ emit_i32_cond_jumpi(kUnequal, &cont, max_steps.gp(), 0); 1125 // Abort. 1126 Trap(decoder, kTrapUnreachable); 1127 __ bind(&cont); 1128 __ emit_i32_subi(max_steps.gp(), max_steps.gp(), 1); 1129 __ Store(max_steps_addr.gp(), no_reg, 0, max_steps, StoreType::kI32Store, 1130 pinned); 1131 } 1132 } 1133 1134 void NextInstruction(FullDecoder* decoder, WasmOpcode opcode) { 1135 // Add a single check, so that the fast path can be inlined while 1136 // {EmitDebuggingInfo} stays outlined. 1137 if (V8_UNLIKELY(for_debugging_)) EmitDebuggingInfo(decoder, opcode); 1138 TraceCacheState(decoder); 1139 SLOW_DCHECK(__ ValidateCacheState()); 1140 CODE_COMMENT(WasmOpcodes::OpcodeName( 1141 WasmOpcodes::IsPrefixOpcode(opcode) 1142 ? decoder->read_prefixed_opcode<Decoder::kFullValidation>( 1143 decoder->pc()) 1144 : opcode)); 1145 } 1146 1147 void EmitBreakpoint(FullDecoder* decoder) { 1148 DCHECK(for_debugging_); 1149 source_position_table_builder_.AddPosition( 1150 __ pc_offset(), SourcePosition(decoder->position()), true); 1151 __ CallRuntimeStub(WasmCode::kWasmDebugBreak); 1152 DefineSafepointWithCalleeSavedRegisters(); 1153 RegisterDebugSideTableEntry(decoder, 1154 DebugSideTableBuilder::kAllowRegisters); 1155 MaybeOSR(); 1156 } 1157 1158 void PushControl(Control* block) { 1159 // The Liftoff stack includes implicit exception refs stored for catch 1160 // blocks, so that they can be rethrown. 1161 block->num_exceptions = num_exceptions_; 1162 } 1163 1164 void Block(FullDecoder* decoder, Control* block) { PushControl(block); } 1165 1166 void Loop(FullDecoder* decoder, Control* loop) { 1167 // Before entering a loop, spill all locals to the stack, in order to free 1168 // the cache registers, and to avoid unnecessarily reloading stack values 1169 // into registers at branches. 1170 // TODO(clemensb): Come up with a better strategy here, involving 1171 // pre-analysis of the function. 1172 __ SpillLocals(); 1173 1174 __ PrepareLoopArgs(loop->start_merge.arity); 1175 1176 // Loop labels bind at the beginning of the block. 1177 __ bind(loop->label.get()); 1178 1179 // Save the current cache state for the merge when jumping to this loop. 1180 loop->label_state.Split(*__ cache_state()); 1181 1182 PushControl(loop); 1183 1184 if (!dynamic_tiering()) { 1185 // When the budget-based tiering mechanism is enabled, use that to 1186 // check for interrupt requests; otherwise execute a stack check in the 1187 // loop header. 1188 StackCheck(decoder, decoder->position()); 1189 } 1190 } 1191 1192 void Try(FullDecoder* decoder, Control* block) { 1193 block->try_info = std::make_unique<TryInfo>(); 1194 PushControl(block); 1195 } 1196 1197 // Load the property in {kReturnRegister0}. 1198 LiftoffRegister GetExceptionProperty(LiftoffAssembler::VarState& exception, 1199 RootIndex root_index) { 1200 DCHECK(root_index == RootIndex::kwasm_exception_tag_symbol || 1201 root_index == RootIndex::kwasm_exception_values_symbol); 1202 1203 LiftoffRegList pinned; 1204 LiftoffRegister tag_symbol_reg = 1205 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 1206 LoadExceptionSymbol(tag_symbol_reg.gp(), pinned, root_index); 1207 LiftoffRegister context_reg = 1208 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 1209 LOAD_TAGGED_PTR_INSTANCE_FIELD(context_reg.gp(), NativeContext, pinned); 1210 1211 LiftoffAssembler::VarState tag_symbol(kPointerKind, tag_symbol_reg, 0); 1212 LiftoffAssembler::VarState context(kPointerKind, context_reg, 0); 1213 1214 CallRuntimeStub(WasmCode::kWasmGetOwnProperty, 1215 MakeSig::Returns(kPointerKind) 1216 .Params(kPointerKind, kPointerKind, kPointerKind), 1217 {exception, tag_symbol, context}, kNoSourcePosition); 1218 1219 return LiftoffRegister(kReturnRegister0); 1220 } 1221 1222 void CatchException(FullDecoder* decoder, 1223 const TagIndexImmediate<validate>& imm, Control* block, 1224 base::Vector<Value> values) { 1225 DCHECK(block->is_try_catch()); 1226 __ emit_jump(block->label.get()); 1227 1228 // The catch block is unreachable if no possible throws in the try block 1229 // exist. We only build a landing pad if some node in the try block can 1230 // (possibly) throw. Otherwise the catch environments remain empty. 1231 if (!block->try_info->catch_reached) { 1232 block->reachability = kSpecOnlyReachable; 1233 return; 1234 } 1235 1236 // This is the last use of this label. Re-use the field for the label of the 1237 // next catch block, and jump there if the tag does not match. 1238 __ bind(&block->try_info->catch_label); 1239 new (&block->try_info->catch_label) Label(); 1240 1241 __ cache_state()->Split(block->try_info->catch_state); 1242 1243 CODE_COMMENT("load caught exception tag"); 1244 DCHECK_EQ(__ cache_state()->stack_state.back().kind(), kRef); 1245 LiftoffRegister caught_tag = 1246 GetExceptionProperty(__ cache_state()->stack_state.back(), 1247 RootIndex::kwasm_exception_tag_symbol); 1248 LiftoffRegList pinned; 1249 pinned.set(caught_tag); 1250 1251 CODE_COMMENT("load expected exception tag"); 1252 Register imm_tag = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 1253 LOAD_TAGGED_PTR_INSTANCE_FIELD(imm_tag, TagsTable, pinned); 1254 __ LoadTaggedPointer( 1255 imm_tag, imm_tag, no_reg, 1256 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index), {}); 1257 1258 CODE_COMMENT("compare tags"); 1259 Label caught; 1260 __ emit_cond_jump(kEqual, &caught, kI32, imm_tag, caught_tag.gp()); 1261 // The tags don't match, merge the current state into the catch state and 1262 // jump to the next handler. 1263 __ MergeFullStackWith(block->try_info->catch_state, *__ cache_state()); 1264 __ emit_jump(&block->try_info->catch_label); 1265 1266 __ bind(&caught); 1267 if (!block->try_info->in_handler) { 1268 block->try_info->in_handler = true; 1269 num_exceptions_++; 1270 } 1271 GetExceptionValues(decoder, __ cache_state()->stack_state.back(), imm.tag); 1272 } 1273 1274 void Rethrow(FullDecoder* decoder, 1275 const LiftoffAssembler::VarState& exception) { 1276 DCHECK_EQ(exception.kind(), kRef); 1277 CallRuntimeStub(WasmCode::kWasmRethrow, MakeSig::Params(kPointerKind), 1278 {exception}, decoder->position()); 1279 } 1280 1281 void Delegate(FullDecoder* decoder, uint32_t depth, Control* block) { 1282 DCHECK_EQ(block, decoder->control_at(0)); 1283 Control* target = decoder->control_at(depth); 1284 DCHECK(block->is_incomplete_try()); 1285 __ bind(&block->try_info->catch_label); 1286 if (block->try_info->catch_reached) { 1287 __ cache_state()->Steal(block->try_info->catch_state); 1288 if (depth == decoder->control_depth() - 1) { 1289 // Delegate to the caller, do not emit a landing pad. 1290 Rethrow(decoder, __ cache_state()->stack_state.back()); 1291 MaybeOSR(); 1292 } else { 1293 DCHECK(target->is_incomplete_try()); 1294 if (!target->try_info->catch_reached) { 1295 target->try_info->catch_state.InitMerge( 1296 *__ cache_state(), __ num_locals(), 1, 1297 target->stack_depth + target->num_exceptions); 1298 target->try_info->catch_reached = true; 1299 } 1300 __ MergeStackWith(target->try_info->catch_state, 1, 1301 LiftoffAssembler::kForwardJump); 1302 __ emit_jump(&target->try_info->catch_label); 1303 } 1304 } 1305 } 1306 1307 void Rethrow(FullDecoder* decoder, Control* try_block) { 1308 int index = try_block->try_info->catch_state.stack_height() - 1; 1309 auto& exception = __ cache_state()->stack_state[index]; 1310 Rethrow(decoder, exception); 1311 int pc_offset = __ pc_offset(); 1312 MaybeOSR(); 1313 EmitLandingPad(decoder, pc_offset); 1314 } 1315 1316 void CatchAll(FullDecoder* decoder, Control* block) { 1317 DCHECK(block->is_try_catchall() || block->is_try_catch()); 1318 DCHECK_EQ(decoder->control_at(0), block); 1319 1320 // The catch block is unreachable if no possible throws in the try block 1321 // exist. We only build a landing pad if some node in the try block can 1322 // (possibly) throw. Otherwise the catch environments remain empty. 1323 if (!block->try_info->catch_reached) { 1324 decoder->SetSucceedingCodeDynamicallyUnreachable(); 1325 return; 1326 } 1327 1328 __ bind(&block->try_info->catch_label); 1329 __ cache_state()->Steal(block->try_info->catch_state); 1330 if (!block->try_info->in_handler) { 1331 block->try_info->in_handler = true; 1332 num_exceptions_++; 1333 } 1334 } 1335 1336 void JumpIfFalse(FullDecoder* decoder, Label* false_dst) { 1337 LiftoffCondition cond = 1338 test_and_reset_outstanding_op(kExprI32Eqz) ? kNotEqualZero : kEqualZero; 1339 1340 if (!has_outstanding_op()) { 1341 // Unary comparison. 1342 Register value = __ PopToRegister().gp(); 1343 __ emit_cond_jump(cond, false_dst, kI32, value); 1344 return; 1345 } 1346 1347 // Binary comparison of i32 values. 1348 cond = Negate(GetCompareCondition(outstanding_op_)); 1349 outstanding_op_ = kNoOutstandingOp; 1350 LiftoffAssembler::VarState rhs_slot = __ cache_state()->stack_state.back(); 1351 if (rhs_slot.is_const()) { 1352 // Compare to a constant. 1353 int32_t rhs_imm = rhs_slot.i32_const(); 1354 __ cache_state()->stack_state.pop_back(); 1355 Register lhs = __ PopToRegister().gp(); 1356 __ emit_i32_cond_jumpi(cond, false_dst, lhs, rhs_imm); 1357 return; 1358 } 1359 1360 Register rhs = __ PopToRegister().gp(); 1361 LiftoffAssembler::VarState lhs_slot = __ cache_state()->stack_state.back(); 1362 if (lhs_slot.is_const()) { 1363 // Compare a constant to an arbitrary value. 1364 int32_t lhs_imm = lhs_slot.i32_const(); 1365 __ cache_state()->stack_state.pop_back(); 1366 // Flip the condition, because {lhs} and {rhs} are swapped. 1367 __ emit_i32_cond_jumpi(Flip(cond), false_dst, rhs, lhs_imm); 1368 return; 1369 } 1370 1371 // Compare two arbitrary values. 1372 Register lhs = __ PopToRegister(LiftoffRegList{rhs}).gp(); 1373 __ emit_cond_jump(cond, false_dst, kI32, lhs, rhs); 1374 } 1375 1376 void If(FullDecoder* decoder, const Value& cond, Control* if_block) { 1377 DCHECK_EQ(if_block, decoder->control_at(0)); 1378 DCHECK(if_block->is_if()); 1379 1380 // Allocate the else state. 1381 if_block->else_state = std::make_unique<ElseState>(); 1382 1383 // Test the condition on the value stack, jump to else if zero. 1384 JumpIfFalse(decoder, if_block->else_state->label.get()); 1385 1386 // Store the state (after popping the value) for executing the else branch. 1387 if_block->else_state->state.Split(*__ cache_state()); 1388 1389 PushControl(if_block); 1390 } 1391 1392 void FallThruTo(FullDecoder* decoder, Control* c) { 1393 if (!c->end_merge.reached) { 1394 c->label_state.InitMerge(*__ cache_state(), __ num_locals(), 1395 c->end_merge.arity, 1396 c->stack_depth + c->num_exceptions); 1397 } 1398 DCHECK(!c->is_try_catchall()); 1399 if (c->is_try_catch()) { 1400 // Drop the implicit exception ref if any. There may be none if this is a 1401 // catch-less try block. 1402 __ MergeStackWith(c->label_state, c->br_merge()->arity, 1403 LiftoffAssembler::kForwardJump); 1404 } else { 1405 __ MergeFullStackWith(c->label_state, *__ cache_state()); 1406 } 1407 __ emit_jump(c->label.get()); 1408 TraceCacheState(decoder); 1409 } 1410 1411 void FinishOneArmedIf(FullDecoder* decoder, Control* c) { 1412 DCHECK(c->is_onearmed_if()); 1413 if (c->end_merge.reached) { 1414 // Someone already merged to the end of the if. Merge both arms into that. 1415 if (c->reachable()) { 1416 // Merge the if state into the end state. 1417 __ MergeFullStackWith(c->label_state, *__ cache_state()); 1418 __ emit_jump(c->label.get()); 1419 } 1420 // Merge the else state into the end state. Set this state as the current 1421 // state first so helper functions know which registers are in use. 1422 __ bind(c->else_state->label.get()); 1423 __ cache_state()->Steal(c->else_state->state); 1424 __ MergeFullStackWith(c->label_state, *__ cache_state()); 1425 __ cache_state()->Steal(c->label_state); 1426 } else if (c->reachable()) { 1427 // No merge yet at the end of the if, but we need to create a merge for 1428 // the both arms of this if. Thus init the merge point from the else 1429 // state, then merge the if state into that. 1430 DCHECK_EQ(c->start_merge.arity, c->end_merge.arity); 1431 c->label_state.InitMerge(c->else_state->state, __ num_locals(), 1432 c->start_merge.arity, 1433 c->stack_depth + c->num_exceptions); 1434 __ MergeFullStackWith(c->label_state, *__ cache_state()); 1435 __ emit_jump(c->label.get()); 1436 // Merge the else state into the end state. Set this state as the current 1437 // state first so helper functions know which registers are in use. 1438 __ bind(c->else_state->label.get()); 1439 __ cache_state()->Steal(c->else_state->state); 1440 __ MergeFullStackWith(c->label_state, *__ cache_state()); 1441 __ cache_state()->Steal(c->label_state); 1442 } else { 1443 // No merge needed, just continue with the else state. 1444 __ bind(c->else_state->label.get()); 1445 __ cache_state()->Steal(c->else_state->state); 1446 } 1447 } 1448 1449 void FinishTry(FullDecoder* decoder, Control* c) { 1450 DCHECK(c->is_try_catch() || c->is_try_catchall()); 1451 if (!c->end_merge.reached) { 1452 if (c->try_info->catch_reached) { 1453 // Drop the implicit exception ref. 1454 __ DropValue(__ num_locals() + c->stack_depth + c->num_exceptions); 1455 } 1456 // Else we did not enter the catch state, continue with the current state. 1457 } else { 1458 if (c->reachable()) { 1459 __ MergeStackWith(c->label_state, c->br_merge()->arity, 1460 LiftoffAssembler::kForwardJump); 1461 } 1462 __ cache_state()->Steal(c->label_state); 1463 } 1464 if (c->try_info->catch_reached) { 1465 num_exceptions_--; 1466 } 1467 } 1468 1469 void PopControl(FullDecoder* decoder, Control* c) { 1470 if (c->is_loop()) return; // A loop just falls through. 1471 if (c->is_onearmed_if()) { 1472 // Special handling for one-armed ifs. 1473 FinishOneArmedIf(decoder, c); 1474 } else if (c->is_try_catch() || c->is_try_catchall()) { 1475 FinishTry(decoder, c); 1476 } else if (c->end_merge.reached) { 1477 // There is a merge already. Merge our state into that, then continue with 1478 // that state. 1479 if (c->reachable()) { 1480 __ MergeFullStackWith(c->label_state, *__ cache_state()); 1481 } 1482 __ cache_state()->Steal(c->label_state); 1483 } else { 1484 // No merge, just continue with our current state. 1485 } 1486 1487 if (!c->label.get()->is_bound()) __ bind(c->label.get()); 1488 } 1489 1490 void GenerateCCall(const LiftoffRegister* result_regs, 1491 const ValueKindSig* sig, ValueKind out_argument_kind, 1492 const LiftoffRegister* arg_regs, 1493 ExternalReference ext_ref) { 1494 // Before making a call, spill all cache registers. 1495 __ SpillAllRegisters(); 1496 1497 // Store arguments on our stack, then align the stack for calling to C. 1498 int param_bytes = 0; 1499 for (ValueKind param_kind : sig->parameters()) { 1500 param_bytes += value_kind_size(param_kind); 1501 } 1502 int out_arg_bytes = 1503 out_argument_kind == kVoid ? 0 : value_kind_size(out_argument_kind); 1504 int stack_bytes = std::max(param_bytes, out_arg_bytes); 1505 __ CallC(sig, arg_regs, result_regs, out_argument_kind, stack_bytes, 1506 ext_ref); 1507 } 1508 1509 template <typename EmitFn, typename... Args> 1510 typename std::enable_if<!std::is_member_function_pointer<EmitFn>::value>::type 1511 CallEmitFn(EmitFn fn, Args... args) { 1512 fn(args...); 1513 } 1514 1515 template <typename EmitFn, typename... Args> 1516 typename std::enable_if<std::is_member_function_pointer<EmitFn>::value>::type 1517 CallEmitFn(EmitFn fn, Args... args) { 1518 (asm_.*fn)(ConvertAssemblerArg(args)...); 1519 } 1520 1521 // Wrap a {LiftoffRegister} with implicit conversions to {Register} and 1522 // {DoubleRegister}. 1523 struct AssemblerRegisterConverter { 1524 LiftoffRegister reg; 1525 operator LiftoffRegister() { return reg; } 1526 operator Register() { return reg.gp(); } 1527 operator DoubleRegister() { return reg.fp(); } 1528 }; 1529 1530 // Convert {LiftoffRegister} to {AssemblerRegisterConverter}, other types stay 1531 // unchanged. 1532 template <typename T> 1533 typename std::conditional<std::is_same<LiftoffRegister, T>::value, 1534 AssemblerRegisterConverter, T>::type 1535 ConvertAssemblerArg(T t) { 1536 return {t}; 1537 } 1538 1539 template <typename EmitFn, typename ArgType> 1540 struct EmitFnWithFirstArg { 1541 EmitFn fn; 1542 ArgType first_arg; 1543 }; 1544 1545 template <typename EmitFn, typename ArgType> 1546 EmitFnWithFirstArg<EmitFn, ArgType> BindFirst(EmitFn fn, ArgType arg) { 1547 return {fn, arg}; 1548 } 1549 1550 template <typename EmitFn, typename T, typename... Args> 1551 void CallEmitFn(EmitFnWithFirstArg<EmitFn, T> bound_fn, Args... args) { 1552 CallEmitFn(bound_fn.fn, bound_fn.first_arg, ConvertAssemblerArg(args)...); 1553 } 1554 1555 template <ValueKind src_kind, ValueKind result_kind, 1556 ValueKind result_lane_kind = kVoid, class EmitFn> 1557 void EmitUnOp(EmitFn fn) { 1558 constexpr RegClass src_rc = reg_class_for(src_kind); 1559 constexpr RegClass result_rc = reg_class_for(result_kind); 1560 LiftoffRegister src = __ PopToRegister(); 1561 LiftoffRegister dst = src_rc == result_rc 1562 ? __ GetUnusedRegister(result_rc, {src}, {}) 1563 : __ GetUnusedRegister(result_rc, {}); 1564 CallEmitFn(fn, dst, src); 1565 if (V8_UNLIKELY(nondeterminism_)) { 1566 LiftoffRegList pinned = {dst}; 1567 if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) { 1568 CheckNan(dst, pinned, result_kind); 1569 } else if (result_kind == ValueKind::kS128 && 1570 (result_lane_kind == kF32 || result_lane_kind == kF64)) { 1571 CheckS128Nan(dst, pinned, result_lane_kind); 1572 } 1573 } 1574 __ PushRegister(result_kind, dst); 1575 } 1576 1577 template <ValueKind kind> 1578 void EmitFloatUnOpWithCFallback( 1579 bool (LiftoffAssembler::*emit_fn)(DoubleRegister, DoubleRegister), 1580 ExternalReference (*fallback_fn)()) { 1581 auto emit_with_c_fallback = [=](LiftoffRegister dst, LiftoffRegister src) { 1582 if ((asm_.*emit_fn)(dst.fp(), src.fp())) return; 1583 ExternalReference ext_ref = fallback_fn(); 1584 auto sig = MakeSig::Params(kind); 1585 GenerateCCall(&dst, &sig, kind, &src, ext_ref); 1586 }; 1587 EmitUnOp<kind, kind>(emit_with_c_fallback); 1588 } 1589 1590 enum TypeConversionTrapping : bool { kCanTrap = true, kNoTrap = false }; 1591 template <ValueKind dst_kind, ValueKind src_kind, 1592 TypeConversionTrapping can_trap> 1593 void EmitTypeConversion(FullDecoder* decoder, WasmOpcode opcode, 1594 ExternalReference (*fallback_fn)()) { 1595 static constexpr RegClass src_rc = reg_class_for(src_kind); 1596 static constexpr RegClass dst_rc = reg_class_for(dst_kind); 1597 LiftoffRegister src = __ PopToRegister(); 1598 LiftoffRegister dst = src_rc == dst_rc 1599 ? __ GetUnusedRegister(dst_rc, {src}, {}) 1600 : __ GetUnusedRegister(dst_rc, {}); 1601 Label* trap = 1602 can_trap ? AddOutOfLineTrap( 1603 decoder, WasmCode::kThrowWasmTrapFloatUnrepresentable) 1604 : nullptr; 1605 if (!__ emit_type_conversion(opcode, dst, src, trap)) { 1606 DCHECK_NOT_NULL(fallback_fn); 1607 ExternalReference ext_ref = fallback_fn(); 1608 if (can_trap) { 1609 // External references for potentially trapping conversions return int. 1610 auto sig = MakeSig::Returns(kI32).Params(src_kind); 1611 LiftoffRegister ret_reg = 1612 __ GetUnusedRegister(kGpReg, LiftoffRegList{dst}); 1613 LiftoffRegister dst_regs[] = {ret_reg, dst}; 1614 GenerateCCall(dst_regs, &sig, dst_kind, &src, ext_ref); 1615 __ emit_cond_jump(kEqual, trap, kI32, ret_reg.gp()); 1616 } else { 1617 ValueKind sig_kinds[] = {src_kind}; 1618 ValueKindSig sig(0, 1, sig_kinds); 1619 GenerateCCall(&dst, &sig, dst_kind, &src, ext_ref); 1620 } 1621 } 1622 __ PushRegister(dst_kind, dst); 1623 } 1624 1625 void UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& value, 1626 Value* result) { 1627#define CASE_I32_UNOP(opcode, fn) \ 1628 case kExpr##opcode: \ 1629 return EmitUnOp<kI32, kI32>(&LiftoffAssembler::emit_##fn); 1630#define CASE_I64_UNOP(opcode, fn) \ 1631 case kExpr##opcode: \ 1632 return EmitUnOp<kI64, kI64>(&LiftoffAssembler::emit_##fn); 1633#define CASE_FLOAT_UNOP(opcode, kind, fn) \ 1634 case kExpr##opcode: \ 1635 return EmitUnOp<k##kind, k##kind>(&LiftoffAssembler::emit_##fn); 1636#define CASE_FLOAT_UNOP_WITH_CFALLBACK(opcode, kind, fn) \ 1637 case kExpr##opcode: \ 1638 return EmitFloatUnOpWithCFallback<k##kind>(&LiftoffAssembler::emit_##fn, \ 1639 &ExternalReference::wasm_##fn); 1640#define CASE_TYPE_CONVERSION(opcode, dst_kind, src_kind, ext_ref, can_trap) \ 1641 case kExpr##opcode: \ 1642 return EmitTypeConversion<k##dst_kind, k##src_kind, can_trap>( \ 1643 decoder, kExpr##opcode, ext_ref); 1644 switch (opcode) { 1645 CASE_I32_UNOP(I32Clz, i32_clz) 1646 CASE_I32_UNOP(I32Ctz, i32_ctz) 1647 CASE_FLOAT_UNOP(F32Abs, F32, f32_abs) 1648 CASE_FLOAT_UNOP(F32Neg, F32, f32_neg) 1649 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Ceil, F32, f32_ceil) 1650 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Floor, F32, f32_floor) 1651 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Trunc, F32, f32_trunc) 1652 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32NearestInt, F32, f32_nearest_int) 1653 CASE_FLOAT_UNOP(F32Sqrt, F32, f32_sqrt) 1654 CASE_FLOAT_UNOP(F64Abs, F64, f64_abs) 1655 CASE_FLOAT_UNOP(F64Neg, F64, f64_neg) 1656 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Ceil, F64, f64_ceil) 1657 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Floor, F64, f64_floor) 1658 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Trunc, F64, f64_trunc) 1659 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64NearestInt, F64, f64_nearest_int) 1660 CASE_FLOAT_UNOP(F64Sqrt, F64, f64_sqrt) 1661 CASE_TYPE_CONVERSION(I32ConvertI64, I32, I64, nullptr, kNoTrap) 1662 CASE_TYPE_CONVERSION(I32SConvertF32, I32, F32, nullptr, kCanTrap) 1663 CASE_TYPE_CONVERSION(I32UConvertF32, I32, F32, nullptr, kCanTrap) 1664 CASE_TYPE_CONVERSION(I32SConvertF64, I32, F64, nullptr, kCanTrap) 1665 CASE_TYPE_CONVERSION(I32UConvertF64, I32, F64, nullptr, kCanTrap) 1666 CASE_TYPE_CONVERSION(I32ReinterpretF32, I32, F32, nullptr, kNoTrap) 1667 CASE_TYPE_CONVERSION(I64SConvertI32, I64, I32, nullptr, kNoTrap) 1668 CASE_TYPE_CONVERSION(I64UConvertI32, I64, I32, nullptr, kNoTrap) 1669 CASE_TYPE_CONVERSION(I64SConvertF32, I64, F32, 1670 &ExternalReference::wasm_float32_to_int64, kCanTrap) 1671 CASE_TYPE_CONVERSION(I64UConvertF32, I64, F32, 1672 &ExternalReference::wasm_float32_to_uint64, kCanTrap) 1673 CASE_TYPE_CONVERSION(I64SConvertF64, I64, F64, 1674 &ExternalReference::wasm_float64_to_int64, kCanTrap) 1675 CASE_TYPE_CONVERSION(I64UConvertF64, I64, F64, 1676 &ExternalReference::wasm_float64_to_uint64, kCanTrap) 1677 CASE_TYPE_CONVERSION(I64ReinterpretF64, I64, F64, nullptr, kNoTrap) 1678 CASE_TYPE_CONVERSION(F32SConvertI32, F32, I32, nullptr, kNoTrap) 1679 CASE_TYPE_CONVERSION(F32UConvertI32, F32, I32, nullptr, kNoTrap) 1680 CASE_TYPE_CONVERSION(F32SConvertI64, F32, I64, 1681 &ExternalReference::wasm_int64_to_float32, kNoTrap) 1682 CASE_TYPE_CONVERSION(F32UConvertI64, F32, I64, 1683 &ExternalReference::wasm_uint64_to_float32, kNoTrap) 1684 CASE_TYPE_CONVERSION(F32ConvertF64, F32, F64, nullptr, kNoTrap) 1685 CASE_TYPE_CONVERSION(F32ReinterpretI32, F32, I32, nullptr, kNoTrap) 1686 CASE_TYPE_CONVERSION(F64SConvertI32, F64, I32, nullptr, kNoTrap) 1687 CASE_TYPE_CONVERSION(F64UConvertI32, F64, I32, nullptr, kNoTrap) 1688 CASE_TYPE_CONVERSION(F64SConvertI64, F64, I64, 1689 &ExternalReference::wasm_int64_to_float64, kNoTrap) 1690 CASE_TYPE_CONVERSION(F64UConvertI64, F64, I64, 1691 &ExternalReference::wasm_uint64_to_float64, kNoTrap) 1692 CASE_TYPE_CONVERSION(F64ConvertF32, F64, F32, nullptr, kNoTrap) 1693 CASE_TYPE_CONVERSION(F64ReinterpretI64, F64, I64, nullptr, kNoTrap) 1694 CASE_I32_UNOP(I32SExtendI8, i32_signextend_i8) 1695 CASE_I32_UNOP(I32SExtendI16, i32_signextend_i16) 1696 CASE_I64_UNOP(I64SExtendI8, i64_signextend_i8) 1697 CASE_I64_UNOP(I64SExtendI16, i64_signextend_i16) 1698 CASE_I64_UNOP(I64SExtendI32, i64_signextend_i32) 1699 CASE_I64_UNOP(I64Clz, i64_clz) 1700 CASE_I64_UNOP(I64Ctz, i64_ctz) 1701 CASE_TYPE_CONVERSION(I32SConvertSatF32, I32, F32, nullptr, kNoTrap) 1702 CASE_TYPE_CONVERSION(I32UConvertSatF32, I32, F32, nullptr, kNoTrap) 1703 CASE_TYPE_CONVERSION(I32SConvertSatF64, I32, F64, nullptr, kNoTrap) 1704 CASE_TYPE_CONVERSION(I32UConvertSatF64, I32, F64, nullptr, kNoTrap) 1705 CASE_TYPE_CONVERSION(I64SConvertSatF32, I64, F32, 1706 &ExternalReference::wasm_float32_to_int64_sat, 1707 kNoTrap) 1708 CASE_TYPE_CONVERSION(I64UConvertSatF32, I64, F32, 1709 &ExternalReference::wasm_float32_to_uint64_sat, 1710 kNoTrap) 1711 CASE_TYPE_CONVERSION(I64SConvertSatF64, I64, F64, 1712 &ExternalReference::wasm_float64_to_int64_sat, 1713 kNoTrap) 1714 CASE_TYPE_CONVERSION(I64UConvertSatF64, I64, F64, 1715 &ExternalReference::wasm_float64_to_uint64_sat, 1716 kNoTrap) 1717 case kExprI32Eqz: 1718 DCHECK(decoder->lookahead(0, kExprI32Eqz)); 1719 if ((decoder->lookahead(1, kExprBrIf) || 1720 decoder->lookahead(1, kExprIf)) && 1721 !for_debugging_) { 1722 DCHECK(!has_outstanding_op()); 1723 outstanding_op_ = kExprI32Eqz; 1724 break; 1725 } 1726 return EmitUnOp<kI32, kI32>(&LiftoffAssembler::emit_i32_eqz); 1727 case kExprI64Eqz: 1728 return EmitUnOp<kI64, kI32>(&LiftoffAssembler::emit_i64_eqz); 1729 case kExprI32Popcnt: 1730 return EmitUnOp<kI32, kI32>( 1731 [=](LiftoffRegister dst, LiftoffRegister src) { 1732 if (__ emit_i32_popcnt(dst.gp(), src.gp())) return; 1733 auto sig = MakeSig::Returns(kI32).Params(kI32); 1734 GenerateCCall(&dst, &sig, kVoid, &src, 1735 ExternalReference::wasm_word32_popcnt()); 1736 }); 1737 case kExprI64Popcnt: 1738 return EmitUnOp<kI64, kI64>( 1739 [=](LiftoffRegister dst, LiftoffRegister src) { 1740 if (__ emit_i64_popcnt(dst, src)) return; 1741 // The c function returns i32. We will zero-extend later. 1742 auto sig = MakeSig::Returns(kI32).Params(kI64); 1743 LiftoffRegister c_call_dst = kNeedI64RegPair ? dst.low() : dst; 1744 GenerateCCall(&c_call_dst, &sig, kVoid, &src, 1745 ExternalReference::wasm_word64_popcnt()); 1746 // Now zero-extend the result to i64. 1747 __ emit_type_conversion(kExprI64UConvertI32, dst, c_call_dst, 1748 nullptr); 1749 }); 1750 case kExprRefIsNull: 1751 // We abuse ref.as_non_null, which isn't otherwise used in this switch, as 1752 // a sentinel for the negation of ref.is_null. 1753 case kExprRefAsNonNull: { 1754 LiftoffRegList pinned; 1755 LiftoffRegister ref = pinned.set(__ PopToRegister()); 1756 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); 1757 LoadNullValue(null.gp(), pinned); 1758 // Prefer to overwrite one of the input registers with the result 1759 // of the comparison. 1760 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {ref, null}, {}); 1761 __ emit_ptrsize_set_cond(opcode == kExprRefIsNull ? kEqual : kUnequal, 1762 dst.gp(), ref, null); 1763 __ PushRegister(kI32, dst); 1764 return; 1765 } 1766 default: 1767 UNREACHABLE(); 1768 } 1769#undef CASE_I32_UNOP 1770#undef CASE_I64_UNOP 1771#undef CASE_FLOAT_UNOP 1772#undef CASE_FLOAT_UNOP_WITH_CFALLBACK 1773#undef CASE_TYPE_CONVERSION 1774 } 1775 1776 template <ValueKind src_kind, ValueKind result_kind, typename EmitFn, 1777 typename EmitFnImm> 1778 void EmitBinOpImm(EmitFn fn, EmitFnImm fnImm) { 1779 static constexpr RegClass src_rc = reg_class_for(src_kind); 1780 static constexpr RegClass result_rc = reg_class_for(result_kind); 1781 1782 LiftoffAssembler::VarState rhs_slot = __ cache_state()->stack_state.back(); 1783 // Check if the RHS is an immediate. 1784 if (rhs_slot.is_const()) { 1785 __ cache_state()->stack_state.pop_back(); 1786 int32_t imm = rhs_slot.i32_const(); 1787 1788 LiftoffRegister lhs = __ PopToRegister(); 1789 // Either reuse {lhs} for {dst}, or choose a register (pair) which does 1790 // not overlap, for easier code generation. 1791 LiftoffRegList pinned = {lhs}; 1792 LiftoffRegister dst = src_rc == result_rc 1793 ? __ GetUnusedRegister(result_rc, {lhs}, pinned) 1794 : __ GetUnusedRegister(result_rc, pinned); 1795 1796 CallEmitFn(fnImm, dst, lhs, imm); 1797 static_assert(result_kind != kF32 && result_kind != kF64, 1798 "Unhandled nondeterminism for fuzzing."); 1799 __ PushRegister(result_kind, dst); 1800 } else { 1801 // The RHS was not an immediate. 1802 EmitBinOp<src_kind, result_kind>(fn); 1803 } 1804 } 1805 1806 template <ValueKind src_kind, ValueKind result_kind, 1807 bool swap_lhs_rhs = false, ValueKind result_lane_kind = kVoid, 1808 typename EmitFn> 1809 void EmitBinOp(EmitFn fn) { 1810 static constexpr RegClass src_rc = reg_class_for(src_kind); 1811 static constexpr RegClass result_rc = reg_class_for(result_kind); 1812 LiftoffRegister rhs = __ PopToRegister(); 1813 LiftoffRegister lhs = __ PopToRegister(LiftoffRegList{rhs}); 1814 LiftoffRegister dst = src_rc == result_rc 1815 ? __ GetUnusedRegister(result_rc, {lhs, rhs}, {}) 1816 : __ GetUnusedRegister(result_rc, {}); 1817 1818 if (swap_lhs_rhs) std::swap(lhs, rhs); 1819 1820 CallEmitFn(fn, dst, lhs, rhs); 1821 if (V8_UNLIKELY(nondeterminism_)) { 1822 LiftoffRegList pinned = {dst}; 1823 if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) { 1824 CheckNan(dst, pinned, result_kind); 1825 } else if (result_kind == ValueKind::kS128 && 1826 (result_lane_kind == kF32 || result_lane_kind == kF64)) { 1827 CheckS128Nan(dst, pinned, result_lane_kind); 1828 } 1829 } 1830 __ PushRegister(result_kind, dst); 1831 } 1832 1833 void EmitDivOrRem64CCall(LiftoffRegister dst, LiftoffRegister lhs, 1834 LiftoffRegister rhs, ExternalReference ext_ref, 1835 Label* trap_by_zero, 1836 Label* trap_unrepresentable = nullptr) { 1837 // Cannot emit native instructions, build C call. 1838 LiftoffRegister ret = __ GetUnusedRegister(kGpReg, LiftoffRegList{dst}); 1839 LiftoffRegister tmp = 1840 __ GetUnusedRegister(kGpReg, LiftoffRegList{dst, ret}); 1841 LiftoffRegister arg_regs[] = {lhs, rhs}; 1842 LiftoffRegister result_regs[] = {ret, dst}; 1843 auto sig = MakeSig::Returns(kI32).Params(kI64, kI64); 1844 GenerateCCall(result_regs, &sig, kI64, arg_regs, ext_ref); 1845 __ LoadConstant(tmp, WasmValue(int32_t{0})); 1846 __ emit_cond_jump(kEqual, trap_by_zero, kI32, ret.gp(), tmp.gp()); 1847 if (trap_unrepresentable) { 1848 __ LoadConstant(tmp, WasmValue(int32_t{-1})); 1849 __ emit_cond_jump(kEqual, trap_unrepresentable, kI32, ret.gp(), tmp.gp()); 1850 } 1851 } 1852 1853 template <WasmOpcode opcode> 1854 void EmitI32CmpOp(FullDecoder* decoder) { 1855 DCHECK(decoder->lookahead(0, opcode)); 1856 if ((decoder->lookahead(1, kExprBrIf) || decoder->lookahead(1, kExprIf)) && 1857 !for_debugging_) { 1858 DCHECK(!has_outstanding_op()); 1859 outstanding_op_ = opcode; 1860 return; 1861 } 1862 return EmitBinOp<kI32, kI32>(BindFirst(&LiftoffAssembler::emit_i32_set_cond, 1863 GetCompareCondition(opcode))); 1864 } 1865 1866 void BinOp(FullDecoder* decoder, WasmOpcode opcode, const Value& lhs, 1867 const Value& rhs, Value* result) { 1868#define CASE_I64_SHIFTOP(opcode, fn) \ 1869 case kExpr##opcode: \ 1870 return EmitBinOpImm<kI64, kI64>( \ 1871 [=](LiftoffRegister dst, LiftoffRegister src, \ 1872 LiftoffRegister amount) { \ 1873 __ emit_##fn(dst, src, \ 1874 amount.is_gp_pair() ? amount.low_gp() : amount.gp()); \ 1875 }, \ 1876 &LiftoffAssembler::emit_##fn##i); 1877#define CASE_CCALL_BINOP(opcode, kind, ext_ref_fn) \ 1878 case kExpr##opcode: \ 1879 return EmitBinOp<k##kind, k##kind>( \ 1880 [=](LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs) { \ 1881 LiftoffRegister args[] = {lhs, rhs}; \ 1882 auto ext_ref = ExternalReference::ext_ref_fn(); \ 1883 ValueKind sig_kinds[] = {k##kind, k##kind, k##kind}; \ 1884 const bool out_via_stack = k##kind == kI64; \ 1885 ValueKindSig sig(out_via_stack ? 0 : 1, 2, sig_kinds); \ 1886 ValueKind out_arg_kind = out_via_stack ? kI64 : kVoid; \ 1887 GenerateCCall(&dst, &sig, out_arg_kind, args, ext_ref); \ 1888 }); 1889 switch (opcode) { 1890 case kExprI32Add: 1891 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_add, 1892 &LiftoffAssembler::emit_i32_addi); 1893 case kExprI32Sub: 1894 return EmitBinOp<kI32, kI32>(&LiftoffAssembler::emit_i32_sub); 1895 case kExprI32Mul: 1896 return EmitBinOp<kI32, kI32>(&LiftoffAssembler::emit_i32_mul); 1897 case kExprI32And: 1898 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_and, 1899 &LiftoffAssembler::emit_i32_andi); 1900 case kExprI32Ior: 1901 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_or, 1902 &LiftoffAssembler::emit_i32_ori); 1903 case kExprI32Xor: 1904 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_xor, 1905 &LiftoffAssembler::emit_i32_xori); 1906 case kExprI32Eq: 1907 return EmitI32CmpOp<kExprI32Eq>(decoder); 1908 case kExprI32Ne: 1909 return EmitI32CmpOp<kExprI32Ne>(decoder); 1910 case kExprI32LtS: 1911 return EmitI32CmpOp<kExprI32LtS>(decoder); 1912 case kExprI32LtU: 1913 return EmitI32CmpOp<kExprI32LtU>(decoder); 1914 case kExprI32GtS: 1915 return EmitI32CmpOp<kExprI32GtS>(decoder); 1916 case kExprI32GtU: 1917 return EmitI32CmpOp<kExprI32GtU>(decoder); 1918 case kExprI32LeS: 1919 return EmitI32CmpOp<kExprI32LeS>(decoder); 1920 case kExprI32LeU: 1921 return EmitI32CmpOp<kExprI32LeU>(decoder); 1922 case kExprI32GeS: 1923 return EmitI32CmpOp<kExprI32GeS>(decoder); 1924 case kExprI32GeU: 1925 return EmitI32CmpOp<kExprI32GeU>(decoder); 1926 case kExprI64Add: 1927 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_add, 1928 &LiftoffAssembler::emit_i64_addi); 1929 case kExprI64Sub: 1930 return EmitBinOp<kI64, kI64>(&LiftoffAssembler::emit_i64_sub); 1931 case kExprI64Mul: 1932 return EmitBinOp<kI64, kI64>(&LiftoffAssembler::emit_i64_mul); 1933 case kExprI64And: 1934 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_and, 1935 &LiftoffAssembler::emit_i64_andi); 1936 case kExprI64Ior: 1937 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_or, 1938 &LiftoffAssembler::emit_i64_ori); 1939 case kExprI64Xor: 1940 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_xor, 1941 &LiftoffAssembler::emit_i64_xori); 1942 case kExprI64Eq: 1943 return EmitBinOp<kI64, kI32>( 1944 BindFirst(&LiftoffAssembler::emit_i64_set_cond, kEqual)); 1945 case kExprI64Ne: 1946 return EmitBinOp<kI64, kI32>( 1947 BindFirst(&LiftoffAssembler::emit_i64_set_cond, kUnequal)); 1948 case kExprI64LtS: 1949 return EmitBinOp<kI64, kI32>( 1950 BindFirst(&LiftoffAssembler::emit_i64_set_cond, kSignedLessThan)); 1951 case kExprI64LtU: 1952 return EmitBinOp<kI64, kI32>( 1953 BindFirst(&LiftoffAssembler::emit_i64_set_cond, kUnsignedLessThan)); 1954 case kExprI64GtS: 1955 return EmitBinOp<kI64, kI32>(BindFirst( 1956 &LiftoffAssembler::emit_i64_set_cond, kSignedGreaterThan)); 1957 case kExprI64GtU: 1958 return EmitBinOp<kI64, kI32>(BindFirst( 1959 &LiftoffAssembler::emit_i64_set_cond, kUnsignedGreaterThan)); 1960 case kExprI64LeS: 1961 return EmitBinOp<kI64, kI32>( 1962 BindFirst(&LiftoffAssembler::emit_i64_set_cond, kSignedLessEqual)); 1963 case kExprI64LeU: 1964 return EmitBinOp<kI64, kI32>(BindFirst( 1965 &LiftoffAssembler::emit_i64_set_cond, kUnsignedLessEqual)); 1966 case kExprI64GeS: 1967 return EmitBinOp<kI64, kI32>(BindFirst( 1968 &LiftoffAssembler::emit_i64_set_cond, kSignedGreaterEqual)); 1969 case kExprI64GeU: 1970 return EmitBinOp<kI64, kI32>(BindFirst( 1971 &LiftoffAssembler::emit_i64_set_cond, kUnsignedGreaterEqual)); 1972 case kExprF32Eq: 1973 return EmitBinOp<kF32, kI32>( 1974 BindFirst(&LiftoffAssembler::emit_f32_set_cond, kEqual)); 1975 case kExprF32Ne: 1976 return EmitBinOp<kF32, kI32>( 1977 BindFirst(&LiftoffAssembler::emit_f32_set_cond, kUnequal)); 1978 case kExprF32Lt: 1979 return EmitBinOp<kF32, kI32>( 1980 BindFirst(&LiftoffAssembler::emit_f32_set_cond, kUnsignedLessThan)); 1981 case kExprF32Gt: 1982 return EmitBinOp<kF32, kI32>(BindFirst( 1983 &LiftoffAssembler::emit_f32_set_cond, kUnsignedGreaterThan)); 1984 case kExprF32Le: 1985 return EmitBinOp<kF32, kI32>(BindFirst( 1986 &LiftoffAssembler::emit_f32_set_cond, kUnsignedLessEqual)); 1987 case kExprF32Ge: 1988 return EmitBinOp<kF32, kI32>(BindFirst( 1989 &LiftoffAssembler::emit_f32_set_cond, kUnsignedGreaterEqual)); 1990 case kExprF64Eq: 1991 return EmitBinOp<kF64, kI32>( 1992 BindFirst(&LiftoffAssembler::emit_f64_set_cond, kEqual)); 1993 case kExprF64Ne: 1994 return EmitBinOp<kF64, kI32>( 1995 BindFirst(&LiftoffAssembler::emit_f64_set_cond, kUnequal)); 1996 case kExprF64Lt: 1997 return EmitBinOp<kF64, kI32>( 1998 BindFirst(&LiftoffAssembler::emit_f64_set_cond, kUnsignedLessThan)); 1999 case kExprF64Gt: 2000 return EmitBinOp<kF64, kI32>(BindFirst( 2001 &LiftoffAssembler::emit_f64_set_cond, kUnsignedGreaterThan)); 2002 case kExprF64Le: 2003 return EmitBinOp<kF64, kI32>(BindFirst( 2004 &LiftoffAssembler::emit_f64_set_cond, kUnsignedLessEqual)); 2005 case kExprF64Ge: 2006 return EmitBinOp<kF64, kI32>(BindFirst( 2007 &LiftoffAssembler::emit_f64_set_cond, kUnsignedGreaterEqual)); 2008 case kExprI32Shl: 2009 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_shl, 2010 &LiftoffAssembler::emit_i32_shli); 2011 case kExprI32ShrS: 2012 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_sar, 2013 &LiftoffAssembler::emit_i32_sari); 2014 case kExprI32ShrU: 2015 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_shr, 2016 &LiftoffAssembler::emit_i32_shri); 2017 CASE_CCALL_BINOP(I32Rol, I32, wasm_word32_rol) 2018 CASE_CCALL_BINOP(I32Ror, I32, wasm_word32_ror) 2019 CASE_I64_SHIFTOP(I64Shl, i64_shl) 2020 CASE_I64_SHIFTOP(I64ShrS, i64_sar) 2021 CASE_I64_SHIFTOP(I64ShrU, i64_shr) 2022 CASE_CCALL_BINOP(I64Rol, I64, wasm_word64_rol) 2023 CASE_CCALL_BINOP(I64Ror, I64, wasm_word64_ror) 2024 case kExprF32Add: 2025 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_add); 2026 case kExprF32Sub: 2027 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_sub); 2028 case kExprF32Mul: 2029 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_mul); 2030 case kExprF32Div: 2031 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_div); 2032 case kExprF32Min: 2033 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_min); 2034 case kExprF32Max: 2035 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_max); 2036 case kExprF32CopySign: 2037 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_copysign); 2038 case kExprF64Add: 2039 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_add); 2040 case kExprF64Sub: 2041 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_sub); 2042 case kExprF64Mul: 2043 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_mul); 2044 case kExprF64Div: 2045 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_div); 2046 case kExprF64Min: 2047 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_min); 2048 case kExprF64Max: 2049 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_max); 2050 case kExprF64CopySign: 2051 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_copysign); 2052 case kExprI32DivS: 2053 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, 2054 LiftoffRegister lhs, 2055 LiftoffRegister rhs) { 2056 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); 2057 // Adding the second trap might invalidate the pointer returned for 2058 // the first one, thus get both pointers afterwards. 2059 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivUnrepresentable); 2060 Label* div_by_zero = out_of_line_code_.end()[-2].label.get(); 2061 Label* div_unrepresentable = out_of_line_code_.end()[-1].label.get(); 2062 __ emit_i32_divs(dst.gp(), lhs.gp(), rhs.gp(), div_by_zero, 2063 div_unrepresentable); 2064 }); 2065 case kExprI32DivU: 2066 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, 2067 LiftoffRegister lhs, 2068 LiftoffRegister rhs) { 2069 Label* div_by_zero = 2070 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); 2071 __ emit_i32_divu(dst.gp(), lhs.gp(), rhs.gp(), div_by_zero); 2072 }); 2073 case kExprI32RemS: 2074 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, 2075 LiftoffRegister lhs, 2076 LiftoffRegister rhs) { 2077 Label* rem_by_zero = 2078 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); 2079 __ emit_i32_rems(dst.gp(), lhs.gp(), rhs.gp(), rem_by_zero); 2080 }); 2081 case kExprI32RemU: 2082 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst, 2083 LiftoffRegister lhs, 2084 LiftoffRegister rhs) { 2085 Label* rem_by_zero = 2086 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); 2087 __ emit_i32_remu(dst.gp(), lhs.gp(), rhs.gp(), rem_by_zero); 2088 }); 2089 case kExprI64DivS: 2090 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, 2091 LiftoffRegister lhs, 2092 LiftoffRegister rhs) { 2093 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); 2094 // Adding the second trap might invalidate the pointer returned for 2095 // the first one, thus get both pointers afterwards. 2096 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivUnrepresentable); 2097 Label* div_by_zero = out_of_line_code_.end()[-2].label.get(); 2098 Label* div_unrepresentable = out_of_line_code_.end()[-1].label.get(); 2099 if (!__ emit_i64_divs(dst, lhs, rhs, div_by_zero, 2100 div_unrepresentable)) { 2101 ExternalReference ext_ref = ExternalReference::wasm_int64_div(); 2102 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, div_by_zero, 2103 div_unrepresentable); 2104 } 2105 }); 2106 case kExprI64DivU: 2107 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, 2108 LiftoffRegister lhs, 2109 LiftoffRegister rhs) { 2110 Label* div_by_zero = 2111 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapDivByZero); 2112 if (!__ emit_i64_divu(dst, lhs, rhs, div_by_zero)) { 2113 ExternalReference ext_ref = ExternalReference::wasm_uint64_div(); 2114 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, div_by_zero); 2115 } 2116 }); 2117 case kExprI64RemS: 2118 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, 2119 LiftoffRegister lhs, 2120 LiftoffRegister rhs) { 2121 Label* rem_by_zero = 2122 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); 2123 if (!__ emit_i64_rems(dst, lhs, rhs, rem_by_zero)) { 2124 ExternalReference ext_ref = ExternalReference::wasm_int64_mod(); 2125 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, rem_by_zero); 2126 } 2127 }); 2128 case kExprI64RemU: 2129 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst, 2130 LiftoffRegister lhs, 2131 LiftoffRegister rhs) { 2132 Label* rem_by_zero = 2133 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapRemByZero); 2134 if (!__ emit_i64_remu(dst, lhs, rhs, rem_by_zero)) { 2135 ExternalReference ext_ref = ExternalReference::wasm_uint64_mod(); 2136 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, rem_by_zero); 2137 } 2138 }); 2139 case kExprRefEq: { 2140 return EmitBinOp<kOptRef, kI32>( 2141 BindFirst(&LiftoffAssembler::emit_ptrsize_set_cond, kEqual)); 2142 } 2143 2144 default: 2145 UNREACHABLE(); 2146 } 2147#undef CASE_I64_SHIFTOP 2148#undef CASE_CCALL_BINOP 2149 } 2150 2151 void I32Const(FullDecoder* decoder, Value* result, int32_t value) { 2152 __ PushConstant(kI32, value); 2153 } 2154 2155 void I64Const(FullDecoder* decoder, Value* result, int64_t value) { 2156 // The {VarState} stores constant values as int32_t, thus we only store 2157 // 64-bit constants in this field if it fits in an int32_t. Larger values 2158 // cannot be used as immediate value anyway, so we can also just put them in 2159 // a register immediately. 2160 int32_t value_i32 = static_cast<int32_t>(value); 2161 if (value_i32 == value) { 2162 __ PushConstant(kI64, value_i32); 2163 } else { 2164 LiftoffRegister reg = __ GetUnusedRegister(reg_class_for(kI64), {}); 2165 __ LoadConstant(reg, WasmValue(value)); 2166 __ PushRegister(kI64, reg); 2167 } 2168 } 2169 2170 void F32Const(FullDecoder* decoder, Value* result, float value) { 2171 LiftoffRegister reg = __ GetUnusedRegister(kFpReg, {}); 2172 __ LoadConstant(reg, WasmValue(value)); 2173 __ PushRegister(kF32, reg); 2174 } 2175 2176 void F64Const(FullDecoder* decoder, Value* result, double value) { 2177 LiftoffRegister reg = __ GetUnusedRegister(kFpReg, {}); 2178 __ LoadConstant(reg, WasmValue(value)); 2179 __ PushRegister(kF64, reg); 2180 } 2181 2182 void RefNull(FullDecoder* decoder, ValueType type, Value*) { 2183 LiftoffRegister null = __ GetUnusedRegister(kGpReg, {}); 2184 LoadNullValue(null.gp(), {}); 2185 __ PushRegister(type.kind(), null); 2186 } 2187 2188 void RefFunc(FullDecoder* decoder, uint32_t function_index, Value* result) { 2189 LiftoffRegister func_index_reg = __ GetUnusedRegister(kGpReg, {}); 2190 __ LoadConstant(func_index_reg, WasmValue(function_index)); 2191 LiftoffAssembler::VarState func_index_var(kI32, func_index_reg, 0); 2192 CallRuntimeStub(WasmCode::kWasmRefFunc, MakeSig::Returns(kRef).Params(kI32), 2193 {func_index_var}, decoder->position()); 2194 __ PushRegister(kRef, LiftoffRegister(kReturnRegister0)); 2195 } 2196 2197 void RefAsNonNull(FullDecoder* decoder, const Value& arg, Value* result) { 2198 LiftoffRegList pinned; 2199 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); 2200 MaybeEmitNullCheck(decoder, obj.gp(), pinned, arg.type); 2201 __ PushRegister(kRef, obj); 2202 } 2203 2204 void Drop(FullDecoder* decoder) { __ DropValues(1); } 2205 2206 void TraceFunctionExit(FullDecoder* decoder) { 2207 CODE_COMMENT("trace function exit"); 2208 // Before making the runtime call, spill all cache registers. 2209 __ SpillAllRegisters(); 2210 LiftoffRegList pinned; 2211 // Get a register to hold the stack slot for the return value. 2212 LiftoffRegister info = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2213 __ AllocateStackSlot(info.gp(), sizeof(int64_t)); 2214 2215 // Store the return value if there is exactly one. Multiple return values 2216 // are not handled yet. 2217 size_t num_returns = decoder->sig_->return_count(); 2218 if (num_returns == 1) { 2219 ValueKind return_kind = decoder->sig_->GetReturn(0).kind(); 2220 LiftoffRegister return_reg = 2221 __ LoadToRegister(__ cache_state()->stack_state.back(), pinned); 2222 if (is_reference(return_kind)) { 2223 __ StoreTaggedPointer(info.gp(), no_reg, 0, return_reg, pinned); 2224 } else { 2225 __ Store(info.gp(), no_reg, 0, return_reg, 2226 StoreType::ForValueKind(return_kind), pinned); 2227 } 2228 } 2229 // Put the parameter in its place. 2230 WasmTraceExitDescriptor descriptor; 2231 DCHECK_EQ(0, descriptor.GetStackParameterCount()); 2232 DCHECK_EQ(1, descriptor.GetRegisterParameterCount()); 2233 Register param_reg = descriptor.GetRegisterParameter(0); 2234 if (info.gp() != param_reg) { 2235 __ Move(param_reg, info.gp(), kPointerKind); 2236 } 2237 2238 source_position_table_builder_.AddPosition( 2239 __ pc_offset(), SourcePosition(decoder->position()), false); 2240 __ CallRuntimeStub(WasmCode::kWasmTraceExit); 2241 DefineSafepoint(); 2242 2243 __ DeallocateStackSlot(sizeof(int64_t)); 2244 } 2245 2246 void TierupCheckOnExit(FullDecoder* decoder) { 2247 if (!dynamic_tiering()) return; 2248 TierupCheck(decoder, decoder->position(), __ pc_offset()); 2249 CODE_COMMENT("update tiering budget"); 2250 LiftoffRegList pinned; 2251 LiftoffRegister budget = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2252 LiftoffRegister array = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2253 LOAD_INSTANCE_FIELD(array.gp(), TieringBudgetArray, kSystemPointerSize, 2254 pinned); 2255 uint32_t offset = 2256 kInt32Size * declared_function_index(env_->module, func_index_); 2257 __ Fill(budget, liftoff::kTierupBudgetOffset, ValueKind::kI32); 2258 __ Store(array.gp(), no_reg, offset, budget, StoreType::kI32Store, pinned); 2259 } 2260 2261 void DoReturn(FullDecoder* decoder, uint32_t /* drop_values */) { 2262 if (FLAG_trace_wasm) TraceFunctionExit(decoder); 2263 TierupCheckOnExit(decoder); 2264 size_t num_returns = decoder->sig_->return_count(); 2265 if (num_returns > 0) __ MoveToReturnLocations(decoder->sig_, descriptor_); 2266 __ LeaveFrame(StackFrame::WASM); 2267 __ DropStackSlotsAndRet( 2268 static_cast<uint32_t>(descriptor_->ParameterSlotCount())); 2269 } 2270 2271 void LocalGet(FullDecoder* decoder, Value* result, 2272 const IndexImmediate<validate>& imm) { 2273 auto local_slot = __ cache_state()->stack_state[imm.index]; 2274 __ cache_state()->stack_state.emplace_back( 2275 local_slot.kind(), __ NextSpillOffset(local_slot.kind())); 2276 auto* slot = &__ cache_state()->stack_state.back(); 2277 if (local_slot.is_reg()) { 2278 __ cache_state()->inc_used(local_slot.reg()); 2279 slot->MakeRegister(local_slot.reg()); 2280 } else if (local_slot.is_const()) { 2281 slot->MakeConstant(local_slot.i32_const()); 2282 } else { 2283 DCHECK(local_slot.is_stack()); 2284 auto rc = reg_class_for(local_slot.kind()); 2285 LiftoffRegister reg = __ GetUnusedRegister(rc, {}); 2286 __ cache_state()->inc_used(reg); 2287 slot->MakeRegister(reg); 2288 __ Fill(reg, local_slot.offset(), local_slot.kind()); 2289 } 2290 } 2291 2292 void LocalSetFromStackSlot(LiftoffAssembler::VarState* dst_slot, 2293 uint32_t local_index) { 2294 auto& state = *__ cache_state(); 2295 auto& src_slot = state.stack_state.back(); 2296 ValueKind kind = dst_slot->kind(); 2297 if (dst_slot->is_reg()) { 2298 LiftoffRegister slot_reg = dst_slot->reg(); 2299 if (state.get_use_count(slot_reg) == 1) { 2300 __ Fill(dst_slot->reg(), src_slot.offset(), kind); 2301 return; 2302 } 2303 state.dec_used(slot_reg); 2304 dst_slot->MakeStack(); 2305 } 2306 DCHECK(CheckCompatibleStackSlotTypes(kind, __ local_kind(local_index))); 2307 RegClass rc = reg_class_for(kind); 2308 LiftoffRegister dst_reg = __ GetUnusedRegister(rc, {}); 2309 __ Fill(dst_reg, src_slot.offset(), kind); 2310 *dst_slot = LiftoffAssembler::VarState(kind, dst_reg, dst_slot->offset()); 2311 __ cache_state()->inc_used(dst_reg); 2312 } 2313 2314 void LocalSet(uint32_t local_index, bool is_tee) { 2315 auto& state = *__ cache_state(); 2316 auto& source_slot = state.stack_state.back(); 2317 auto& target_slot = state.stack_state[local_index]; 2318 switch (source_slot.loc()) { 2319 case kRegister: 2320 if (target_slot.is_reg()) state.dec_used(target_slot.reg()); 2321 target_slot.Copy(source_slot); 2322 if (is_tee) state.inc_used(target_slot.reg()); 2323 break; 2324 case kIntConst: 2325 if (target_slot.is_reg()) state.dec_used(target_slot.reg()); 2326 target_slot.Copy(source_slot); 2327 break; 2328 case kStack: 2329 LocalSetFromStackSlot(&target_slot, local_index); 2330 break; 2331 } 2332 if (!is_tee) __ cache_state()->stack_state.pop_back(); 2333 } 2334 2335 void LocalSet(FullDecoder* decoder, const Value& value, 2336 const IndexImmediate<validate>& imm) { 2337 LocalSet(imm.index, false); 2338 } 2339 2340 void LocalTee(FullDecoder* decoder, const Value& value, Value* result, 2341 const IndexImmediate<validate>& imm) { 2342 LocalSet(imm.index, true); 2343 } 2344 2345 void AllocateLocals(FullDecoder* decoder, base::Vector<Value> local_values) { 2346 // TODO(7748): Introduce typed functions bailout reason 2347 unsupported(decoder, kGC, "let"); 2348 } 2349 2350 void DeallocateLocals(FullDecoder* decoder, uint32_t count) { 2351 // TODO(7748): Introduce typed functions bailout reason 2352 unsupported(decoder, kGC, "let"); 2353 } 2354 2355 Register GetGlobalBaseAndOffset(const WasmGlobal* global, 2356 LiftoffRegList* pinned, uint32_t* offset) { 2357 Register addr = pinned->set(__ GetUnusedRegister(kGpReg, {})).gp(); 2358 if (global->mutability && global->imported) { 2359 LOAD_INSTANCE_FIELD(addr, ImportedMutableGlobals, kSystemPointerSize, 2360 *pinned); 2361 __ Load(LiftoffRegister(addr), addr, no_reg, 2362 global->index * sizeof(Address), kPointerLoadType, *pinned); 2363 *offset = 0; 2364 } else { 2365 LOAD_INSTANCE_FIELD(addr, GlobalsStart, kSystemPointerSize, *pinned); 2366 *offset = global->offset; 2367 } 2368 return addr; 2369 } 2370 2371 void GetBaseAndOffsetForImportedMutableExternRefGlobal( 2372 const WasmGlobal* global, LiftoffRegList* pinned, Register* base, 2373 Register* offset) { 2374 Register globals_buffer = 2375 pinned->set(__ GetUnusedRegister(kGpReg, *pinned)).gp(); 2376 LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, 2377 ImportedMutableGlobalsBuffers, *pinned); 2378 *base = globals_buffer; 2379 __ LoadTaggedPointer( 2380 *base, globals_buffer, no_reg, 2381 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(global->offset), 2382 *pinned); 2383 2384 // For the offset we need the index of the global in the buffer, and 2385 // then calculate the actual offset from the index. Load the index from 2386 // the ImportedMutableGlobals array of the instance. 2387 Register imported_mutable_globals = 2388 pinned->set(__ GetUnusedRegister(kGpReg, *pinned)).gp(); 2389 2390 LOAD_INSTANCE_FIELD(imported_mutable_globals, ImportedMutableGlobals, 2391 kSystemPointerSize, *pinned); 2392 *offset = imported_mutable_globals; 2393 __ Load(LiftoffRegister(*offset), imported_mutable_globals, no_reg, 2394 global->index * sizeof(Address), 2395 kSystemPointerSize == 4 ? LoadType::kI32Load : LoadType::kI64Load, 2396 *pinned); 2397 __ emit_i32_shli(*offset, *offset, kTaggedSizeLog2); 2398 __ emit_i32_addi(*offset, *offset, 2399 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(0)); 2400 } 2401 2402 void GlobalGet(FullDecoder* decoder, Value* result, 2403 const GlobalIndexImmediate<validate>& imm) { 2404 const auto* global = &env_->module->globals[imm.index]; 2405 ValueKind kind = global->type.kind(); 2406 if (!CheckSupportedType(decoder, kind, "global")) { 2407 return; 2408 } 2409 2410 if (is_reference(kind)) { 2411 if (global->mutability && global->imported) { 2412 LiftoffRegList pinned; 2413 Register base = no_reg; 2414 Register offset = no_reg; 2415 GetBaseAndOffsetForImportedMutableExternRefGlobal(global, &pinned, 2416 &base, &offset); 2417 __ LoadTaggedPointer(base, base, offset, 0, pinned); 2418 __ PushRegister(kind, LiftoffRegister(base)); 2419 return; 2420 } 2421 2422 LiftoffRegList pinned; 2423 Register globals_buffer = 2424 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 2425 LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, TaggedGlobalsBuffer, 2426 pinned); 2427 Register value = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 2428 __ LoadTaggedPointer(value, globals_buffer, no_reg, 2429 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( 2430 imm.global->offset), 2431 pinned); 2432 __ PushRegister(kind, LiftoffRegister(value)); 2433 return; 2434 } 2435 LiftoffRegList pinned; 2436 uint32_t offset = 0; 2437 Register addr = GetGlobalBaseAndOffset(global, &pinned, &offset); 2438 LiftoffRegister value = 2439 pinned.set(__ GetUnusedRegister(reg_class_for(kind), pinned)); 2440 LoadType type = LoadType::ForValueKind(kind); 2441 __ Load(value, addr, no_reg, offset, type, pinned, nullptr, false); 2442 __ PushRegister(kind, value); 2443 } 2444 2445 void GlobalSet(FullDecoder* decoder, const Value&, 2446 const GlobalIndexImmediate<validate>& imm) { 2447 auto* global = &env_->module->globals[imm.index]; 2448 ValueKind kind = global->type.kind(); 2449 if (!CheckSupportedType(decoder, kind, "global")) { 2450 return; 2451 } 2452 2453 if (is_reference(kind)) { 2454 if (global->mutability && global->imported) { 2455 LiftoffRegList pinned; 2456 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 2457 Register base = no_reg; 2458 Register offset = no_reg; 2459 GetBaseAndOffsetForImportedMutableExternRefGlobal(global, &pinned, 2460 &base, &offset); 2461 __ StoreTaggedPointer(base, offset, 0, value, pinned); 2462 return; 2463 } 2464 2465 LiftoffRegList pinned; 2466 Register globals_buffer = 2467 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 2468 LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, TaggedGlobalsBuffer, 2469 pinned); 2470 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 2471 __ StoreTaggedPointer(globals_buffer, no_reg, 2472 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( 2473 imm.global->offset), 2474 value, pinned); 2475 return; 2476 } 2477 LiftoffRegList pinned; 2478 uint32_t offset = 0; 2479 Register addr = GetGlobalBaseAndOffset(global, &pinned, &offset); 2480 LiftoffRegister reg = pinned.set(__ PopToRegister(pinned)); 2481 StoreType type = StoreType::ForValueKind(kind); 2482 __ Store(addr, no_reg, offset, reg, type, {}, nullptr, false); 2483 } 2484 2485 void TableGet(FullDecoder* decoder, const Value&, Value*, 2486 const IndexImmediate<validate>& imm) { 2487 LiftoffRegList pinned; 2488 2489 LiftoffRegister table_index_reg = 2490 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2491 __ LoadConstant(table_index_reg, WasmValue(imm.index)); 2492 LiftoffAssembler::VarState table_index(kPointerKind, table_index_reg, 0); 2493 2494 LiftoffAssembler::VarState index = __ cache_state()->stack_state.back(); 2495 2496 ValueKind result_kind = env_->module->tables[imm.index].type.kind(); 2497 CallRuntimeStub(WasmCode::kWasmTableGet, 2498 MakeSig::Returns(result_kind).Params(kI32, kI32), 2499 {table_index, index}, decoder->position()); 2500 2501 // Pop parameters from the value stack. 2502 __ cache_state()->stack_state.pop_back(1); 2503 2504 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 2505 2506 __ PushRegister(result_kind, LiftoffRegister(kReturnRegister0)); 2507 } 2508 2509 void TableSet(FullDecoder* decoder, const Value&, const Value&, 2510 const IndexImmediate<validate>& imm) { 2511 LiftoffRegList pinned; 2512 2513 LiftoffRegister table_index_reg = 2514 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2515 __ LoadConstant(table_index_reg, WasmValue(imm.index)); 2516 LiftoffAssembler::VarState table_index(kPointerKind, table_index_reg, 0); 2517 2518 LiftoffAssembler::VarState value = __ cache_state()->stack_state.end()[-1]; 2519 LiftoffAssembler::VarState index = __ cache_state()->stack_state.end()[-2]; 2520 2521 ValueKind table_kind = env_->module->tables[imm.index].type.kind(); 2522 2523 CallRuntimeStub(WasmCode::kWasmTableSet, 2524 MakeSig::Params(kI32, kI32, table_kind), 2525 {table_index, index, value}, decoder->position()); 2526 2527 // Pop parameters from the value stack. 2528 __ cache_state()->stack_state.pop_back(2); 2529 2530 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 2531 } 2532 2533 WasmCode::RuntimeStubId GetRuntimeStubIdForTrapReason(TrapReason reason) { 2534 switch (reason) { 2535#define RUNTIME_STUB_FOR_TRAP(trap_reason) \ 2536 case k##trap_reason: \ 2537 return WasmCode::kThrowWasm##trap_reason; 2538 2539 FOREACH_WASM_TRAPREASON(RUNTIME_STUB_FOR_TRAP) 2540#undef RUNTIME_STUB_FOR_TRAP 2541 default: 2542 UNREACHABLE(); 2543 } 2544 } 2545 2546 void Trap(FullDecoder* decoder, TrapReason reason) { 2547 Label* trap_label = 2548 AddOutOfLineTrap(decoder, GetRuntimeStubIdForTrapReason(reason)); 2549 __ emit_jump(trap_label); 2550 __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap); 2551 } 2552 2553 void AssertNull(FullDecoder* decoder, const Value& arg, Value* result) { 2554 LiftoffRegList pinned; 2555 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); 2556 Label* trap_label = 2557 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast); 2558 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); 2559 LoadNullValue(null.gp(), pinned); 2560 __ emit_cond_jump(kUnequal, trap_label, kOptRef, obj.gp(), null.gp()); 2561 __ PushRegister(kOptRef, obj); 2562 } 2563 2564 void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) { 2565 unsupported(decoder, kOtherReason, "testing opcode"); 2566 } 2567 2568 void Select(FullDecoder* decoder, const Value& cond, const Value& fval, 2569 const Value& tval, Value* result) { 2570 LiftoffRegList pinned; 2571 Register condition = pinned.set(__ PopToRegister()).gp(); 2572 ValueKind kind = __ cache_state()->stack_state.end()[-1].kind(); 2573 DCHECK(CheckCompatibleStackSlotTypes( 2574 kind, __ cache_state()->stack_state.end()[-2].kind())); 2575 LiftoffRegister false_value = pinned.set(__ PopToRegister(pinned)); 2576 LiftoffRegister true_value = __ PopToRegister(pinned); 2577 LiftoffRegister dst = __ GetUnusedRegister(true_value.reg_class(), 2578 {true_value, false_value}, {}); 2579 if (!__ emit_select(dst, condition, true_value, false_value, kind)) { 2580 // Emit generic code (using branches) instead. 2581 Label cont; 2582 Label case_false; 2583 __ emit_cond_jump(kEqual, &case_false, kI32, condition); 2584 if (dst != true_value) __ Move(dst, true_value, kind); 2585 __ emit_jump(&cont); 2586 2587 __ bind(&case_false); 2588 if (dst != false_value) __ Move(dst, false_value, kind); 2589 __ bind(&cont); 2590 } 2591 __ PushRegister(kind, dst); 2592 } 2593 2594 void BrImpl(FullDecoder* decoder, Control* target) { 2595 if (dynamic_tiering()) { 2596 if (target->is_loop()) { 2597 DCHECK(target->label.get()->is_bound()); 2598 int jump_distance = __ pc_offset() - target->label.get()->pos(); 2599 // For now we just add one as the cost for the tier up check. We might 2600 // want to revisit this when tuning tiering budgets later. 2601 const int kTierUpCheckCost = 1; 2602 TierupCheck(decoder, decoder->position(), 2603 jump_distance + kTierUpCheckCost); 2604 } else { 2605 // To estimate time spent in this function more accurately, we could 2606 // increment the tiering budget on forward jumps. However, we don't 2607 // know the jump distance yet; using a blanket value has been tried 2608 // and found to not make a difference. 2609 } 2610 } 2611 if (!target->br_merge()->reached) { 2612 target->label_state.InitMerge( 2613 *__ cache_state(), __ num_locals(), target->br_merge()->arity, 2614 target->stack_depth + target->num_exceptions); 2615 } 2616 __ MergeStackWith(target->label_state, target->br_merge()->arity, 2617 target->is_loop() ? LiftoffAssembler::kBackwardJump 2618 : LiftoffAssembler::kForwardJump); 2619 __ jmp(target->label.get()); 2620 } 2621 2622 void BrOrRet(FullDecoder* decoder, uint32_t depth, 2623 uint32_t /* drop_values */) { 2624 BrOrRetImpl(decoder, depth); 2625 } 2626 2627 void BrOrRetImpl(FullDecoder* decoder, uint32_t depth) { 2628 if (depth == decoder->control_depth() - 1) { 2629 DoReturn(decoder, 0); 2630 } else { 2631 BrImpl(decoder, decoder->control_at(depth)); 2632 } 2633 } 2634 2635 void BrIf(FullDecoder* decoder, const Value& /* cond */, uint32_t depth) { 2636 // Before branching, materialize all constants. This avoids repeatedly 2637 // materializing them for each conditional branch. 2638 // TODO(clemensb): Do the same for br_table. 2639 if (depth != decoder->control_depth() - 1) { 2640 __ MaterializeMergedConstants( 2641 decoder->control_at(depth)->br_merge()->arity); 2642 } 2643 2644 Label cont_false; 2645 2646 // Test the condition on the value stack, jump to {cont_false} if zero. 2647 JumpIfFalse(decoder, &cont_false); 2648 2649 // As a quickfix for https://crbug.com/1314184 we store the cache state 2650 // before calling {BrOrRetImpl} under dynamic tiering, because the tier up 2651 // check modifies the cache state (GetUnusedRegister, 2652 // LoadInstanceIntoRegister). 2653 // TODO(wasm): This causes significant overhead during compilation; try to 2654 // avoid this, maybe by passing in scratch registers. 2655 if (dynamic_tiering()) { 2656 LiftoffAssembler::CacheState old_cache_state; 2657 old_cache_state.Split(*__ cache_state()); 2658 BrOrRetImpl(decoder, depth); 2659 __ cache_state()->Steal(old_cache_state); 2660 } else { 2661 BrOrRetImpl(decoder, depth); 2662 } 2663 2664 __ bind(&cont_false); 2665 } 2666 2667 // Generate a branch table case, potentially reusing previously generated 2668 // stack transfer code. 2669 void GenerateBrCase(FullDecoder* decoder, uint32_t br_depth, 2670 std::map<uint32_t, MovableLabel>* br_targets) { 2671 MovableLabel& label = (*br_targets)[br_depth]; 2672 if (label.get()->is_bound()) { 2673 __ jmp(label.get()); 2674 } else { 2675 __ bind(label.get()); 2676 BrOrRet(decoder, br_depth, 0); 2677 } 2678 } 2679 2680 // Generate a branch table for input in [min, max). 2681 // TODO(wasm): Generate a real branch table (like TF TableSwitch). 2682 void GenerateBrTable(FullDecoder* decoder, LiftoffRegister tmp, 2683 LiftoffRegister value, uint32_t min, uint32_t max, 2684 BranchTableIterator<validate>* table_iterator, 2685 std::map<uint32_t, MovableLabel>* br_targets) { 2686 DCHECK_LT(min, max); 2687 // Check base case. 2688 if (max == min + 1) { 2689 DCHECK_EQ(min, table_iterator->cur_index()); 2690 GenerateBrCase(decoder, table_iterator->next(), br_targets); 2691 return; 2692 } 2693 2694 uint32_t split = min + (max - min) / 2; 2695 Label upper_half; 2696 __ LoadConstant(tmp, WasmValue(split)); 2697 __ emit_cond_jump(kUnsignedGreaterEqual, &upper_half, kI32, value.gp(), 2698 tmp.gp()); 2699 // Emit br table for lower half: 2700 GenerateBrTable(decoder, tmp, value, min, split, table_iterator, 2701 br_targets); 2702 __ bind(&upper_half); 2703 // table_iterator will trigger a DCHECK if we don't stop decoding now. 2704 if (did_bailout()) return; 2705 // Emit br table for upper half: 2706 GenerateBrTable(decoder, tmp, value, split, max, table_iterator, 2707 br_targets); 2708 } 2709 2710 void BrTable(FullDecoder* decoder, const BranchTableImmediate<validate>& imm, 2711 const Value& key) { 2712 LiftoffRegList pinned; 2713 LiftoffRegister value = pinned.set(__ PopToRegister()); 2714 BranchTableIterator<validate> table_iterator(decoder, imm); 2715 std::map<uint32_t, MovableLabel> br_targets; 2716 2717 if (imm.table_count > 0) { 2718 LiftoffRegister tmp = __ GetUnusedRegister(kGpReg, pinned); 2719 __ LoadConstant(tmp, WasmValue(uint32_t{imm.table_count})); 2720 Label case_default; 2721 __ emit_cond_jump(kUnsignedGreaterEqual, &case_default, kI32, value.gp(), 2722 tmp.gp()); 2723 2724 GenerateBrTable(decoder, tmp, value, 0, imm.table_count, &table_iterator, 2725 &br_targets); 2726 2727 __ bind(&case_default); 2728 // table_iterator will trigger a DCHECK if we don't stop decoding now. 2729 if (did_bailout()) return; 2730 } 2731 2732 // Generate the default case. 2733 GenerateBrCase(decoder, table_iterator.next(), &br_targets); 2734 DCHECK(!table_iterator.has_next()); 2735 } 2736 2737 void Else(FullDecoder* decoder, Control* c) { 2738 if (c->reachable()) { 2739 if (!c->end_merge.reached) { 2740 c->label_state.InitMerge(*__ cache_state(), __ num_locals(), 2741 c->end_merge.arity, 2742 c->stack_depth + c->num_exceptions); 2743 } 2744 __ MergeFullStackWith(c->label_state, *__ cache_state()); 2745 __ emit_jump(c->label.get()); 2746 } 2747 __ bind(c->else_state->label.get()); 2748 __ cache_state()->Steal(c->else_state->state); 2749 } 2750 2751 SpilledRegistersForInspection* GetSpilledRegistersForInspection() { 2752 DCHECK(for_debugging_); 2753 // If we are generating debugging code, we really need to spill all 2754 // registers to make them inspectable when stopping at the trap. 2755 auto* spilled = compilation_zone_->New<SpilledRegistersForInspection>( 2756 compilation_zone_); 2757 for (uint32_t i = 0, e = __ cache_state()->stack_height(); i < e; ++i) { 2758 auto& slot = __ cache_state()->stack_state[i]; 2759 if (!slot.is_reg()) continue; 2760 spilled->entries.push_back(SpilledRegistersForInspection::Entry{ 2761 slot.offset(), slot.reg(), slot.kind()}); 2762 __ RecordUsedSpillOffset(slot.offset()); 2763 } 2764 return spilled; 2765 } 2766 2767 Label* AddOutOfLineTrap(FullDecoder* decoder, WasmCode::RuntimeStubId stub, 2768 uint32_t pc = 0) { 2769 // Only memory OOB traps need a {pc}. 2770 DCHECK_IMPLIES(stub != WasmCode::kThrowWasmTrapMemOutOfBounds, pc == 0); 2771 DCHECK(FLAG_wasm_bounds_checks); 2772 OutOfLineSafepointInfo* safepoint_info = nullptr; 2773 if (V8_UNLIKELY(for_debugging_)) { 2774 // Execution does not return after a trap. Therefore we don't have to 2775 // define a safepoint for traps that would preserve references on the 2776 // stack. However, if this is debug code, then we have to preserve the 2777 // references so that they can be inspected. 2778 safepoint_info = 2779 compilation_zone_->New<OutOfLineSafepointInfo>(compilation_zone_); 2780 __ cache_state()->GetTaggedSlotsForOOLCode( 2781 &safepoint_info->slots, &safepoint_info->spills, 2782 LiftoffAssembler::CacheState::SpillLocation::kStackSlots); 2783 } 2784 out_of_line_code_.push_back(OutOfLineCode::Trap( 2785 stub, decoder->position(), 2786 V8_UNLIKELY(for_debugging_) ? GetSpilledRegistersForInspection() 2787 : nullptr, 2788 safepoint_info, pc, RegisterOOLDebugSideTableEntry(decoder))); 2789 return out_of_line_code_.back().label.get(); 2790 } 2791 2792 enum ForceCheck : bool { kDoForceCheck = true, kDontForceCheck = false }; 2793 2794 // Returns {no_reg} if the memory access is statically known to be out of 2795 // bounds (a jump to the trap was generated then); return the GP {index} 2796 // register otherwise (holding the ptrsized index). 2797 Register BoundsCheckMem(FullDecoder* decoder, uint32_t access_size, 2798 uint64_t offset, LiftoffRegister index, 2799 LiftoffRegList pinned, ForceCheck force_check) { 2800 const bool statically_oob = 2801 !base::IsInBounds<uintptr_t>(offset, access_size, 2802 env_->max_memory_size); 2803 2804 // After bounds checking, we know that the index must be ptrsize, hence only 2805 // look at the lower word on 32-bit systems (the high word is bounds-checked 2806 // further down). 2807 Register index_ptrsize = 2808 kNeedI64RegPair && index.is_gp_pair() ? index.low_gp() : index.gp(); 2809 2810 // Without bounds checks (testing only), just return the ptrsize index. 2811 if (V8_UNLIKELY(env_->bounds_checks == kNoBoundsChecks)) { 2812 return index_ptrsize; 2813 } 2814 2815 // Early return for trap handler. 2816 DCHECK_IMPLIES(env_->module->is_memory64, 2817 env_->bounds_checks == kExplicitBoundsChecks); 2818 if (!force_check && !statically_oob && 2819 env_->bounds_checks == kTrapHandler) { 2820 // With trap handlers we should not have a register pair as input (we 2821 // would only return the lower half). 2822 DCHECK(index.is_gp()); 2823 return index_ptrsize; 2824 } 2825 2826 CODE_COMMENT("bounds check memory"); 2827 2828 // Set {pc} of the OOL code to {0} to avoid generation of protected 2829 // instruction information (see {GenerateOutOfLineCode}. 2830 Label* trap_label = 2831 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 0); 2832 2833 if (V8_UNLIKELY(statically_oob)) { 2834 __ emit_jump(trap_label); 2835 decoder->SetSucceedingCodeDynamicallyUnreachable(); 2836 return no_reg; 2837 } 2838 2839 // Convert the index to ptrsize, bounds-checking the high word on 32-bit 2840 // systems for memory64. 2841 if (!env_->module->is_memory64) { 2842 __ emit_u32_to_uintptr(index_ptrsize, index_ptrsize); 2843 } else if (kSystemPointerSize == kInt32Size) { 2844 DCHECK_GE(kMaxUInt32, env_->max_memory_size); 2845 __ emit_cond_jump(kNotEqualZero, trap_label, kI32, index.high_gp()); 2846 } 2847 2848 uintptr_t end_offset = offset + access_size - 1u; 2849 2850 pinned.set(index_ptrsize); 2851 LiftoffRegister end_offset_reg = 2852 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2853 LiftoffRegister mem_size = __ GetUnusedRegister(kGpReg, pinned); 2854 LOAD_INSTANCE_FIELD(mem_size.gp(), MemorySize, kSystemPointerSize, pinned); 2855 2856 __ LoadConstant(end_offset_reg, WasmValue::ForUintPtr(end_offset)); 2857 2858 // If the end offset is larger than the smallest memory, dynamically check 2859 // the end offset against the actual memory size, which is not known at 2860 // compile time. Otherwise, only one check is required (see below). 2861 if (end_offset > env_->min_memory_size) { 2862 __ emit_cond_jump(kUnsignedGreaterEqual, trap_label, kPointerKind, 2863 end_offset_reg.gp(), mem_size.gp()); 2864 } 2865 2866 // Just reuse the end_offset register for computing the effective size 2867 // (which is >= 0 because of the check above). 2868 LiftoffRegister effective_size_reg = end_offset_reg; 2869 __ emit_ptrsize_sub(effective_size_reg.gp(), mem_size.gp(), 2870 end_offset_reg.gp()); 2871 2872 __ emit_cond_jump(kUnsignedGreaterEqual, trap_label, kPointerKind, 2873 index_ptrsize, effective_size_reg.gp()); 2874 return index_ptrsize; 2875 } 2876 2877 void AlignmentCheckMem(FullDecoder* decoder, uint32_t access_size, 2878 uintptr_t offset, Register index, 2879 LiftoffRegList pinned) { 2880 CODE_COMMENT("alignment check"); 2881 Label* trap_label = 2882 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapUnalignedAccess, 0); 2883 Register address = __ GetUnusedRegister(kGpReg, pinned).gp(); 2884 2885 const uint32_t align_mask = access_size - 1; 2886 if ((offset & align_mask) == 0) { 2887 // If {offset} is aligned, we can produce faster code. 2888 2889 // TODO(ahaas): On Intel, the "test" instruction implicitly computes the 2890 // AND of two operands. We could introduce a new variant of 2891 // {emit_cond_jump} to use the "test" instruction without the "and" here. 2892 // Then we can also avoid using the temp register here. 2893 __ emit_i32_andi(address, index, align_mask); 2894 __ emit_cond_jump(kUnequal, trap_label, kI32, address); 2895 } else { 2896 // For alignment checks we only look at the lower 32-bits in {offset}. 2897 __ emit_i32_addi(address, index, static_cast<uint32_t>(offset)); 2898 __ emit_i32_andi(address, address, align_mask); 2899 __ emit_cond_jump(kUnequal, trap_label, kI32, address); 2900 } 2901 } 2902 2903 void TraceMemoryOperation(bool is_store, MachineRepresentation rep, 2904 Register index, uintptr_t offset, 2905 WasmCodePosition position) { 2906 // Before making the runtime call, spill all cache registers. 2907 __ SpillAllRegisters(); 2908 2909 LiftoffRegList pinned; 2910 if (index != no_reg) pinned.set(index); 2911 // Get one register for computing the effective offset (offset + index). 2912 LiftoffRegister effective_offset = 2913 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2914 DCHECK_GE(kMaxUInt32, offset); 2915 __ LoadConstant(effective_offset, WasmValue(static_cast<uint32_t>(offset))); 2916 if (index != no_reg) { 2917 // TODO(clemensb): Do a 64-bit addition here if memory64 is used. 2918 __ emit_i32_add(effective_offset.gp(), effective_offset.gp(), index); 2919 } 2920 2921 // Get a register to hold the stack slot for MemoryTracingInfo. 2922 LiftoffRegister info = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 2923 // Allocate stack slot for MemoryTracingInfo. 2924 __ AllocateStackSlot(info.gp(), sizeof(MemoryTracingInfo)); 2925 2926 // Reuse the {effective_offset} register for all information to be stored in 2927 // the MemoryTracingInfo struct. 2928 LiftoffRegister data = effective_offset; 2929 2930 // Now store all information into the MemoryTracingInfo struct. 2931 if (kSystemPointerSize == 8) { 2932 // Zero-extend the effective offset to u64. 2933 CHECK(__ emit_type_conversion(kExprI64UConvertI32, data, effective_offset, 2934 nullptr)); 2935 } 2936 __ Store( 2937 info.gp(), no_reg, offsetof(MemoryTracingInfo, offset), data, 2938 kSystemPointerSize == 8 ? StoreType::kI64Store : StoreType::kI32Store, 2939 pinned); 2940 __ LoadConstant(data, WasmValue(is_store ? 1 : 0)); 2941 __ Store(info.gp(), no_reg, offsetof(MemoryTracingInfo, is_store), data, 2942 StoreType::kI32Store8, pinned); 2943 __ LoadConstant(data, WasmValue(static_cast<int>(rep))); 2944 __ Store(info.gp(), no_reg, offsetof(MemoryTracingInfo, mem_rep), data, 2945 StoreType::kI32Store8, pinned); 2946 2947 WasmTraceMemoryDescriptor descriptor; 2948 DCHECK_EQ(0, descriptor.GetStackParameterCount()); 2949 DCHECK_EQ(1, descriptor.GetRegisterParameterCount()); 2950 Register param_reg = descriptor.GetRegisterParameter(0); 2951 if (info.gp() != param_reg) { 2952 __ Move(param_reg, info.gp(), kPointerKind); 2953 } 2954 2955 source_position_table_builder_.AddPosition(__ pc_offset(), 2956 SourcePosition(position), false); 2957 __ CallRuntimeStub(WasmCode::kWasmTraceMemory); 2958 DefineSafepoint(); 2959 2960 __ DeallocateStackSlot(sizeof(MemoryTracingInfo)); 2961 } 2962 2963 bool IndexStaticallyInBounds(const LiftoffAssembler::VarState& index_slot, 2964 int access_size, uintptr_t* offset) { 2965 if (!index_slot.is_const()) return false; 2966 2967 // Potentially zero extend index (which is a 32-bit constant). 2968 const uintptr_t index = static_cast<uint32_t>(index_slot.i32_const()); 2969 const uintptr_t effective_offset = index + *offset; 2970 2971 if (effective_offset < index // overflow 2972 || !base::IsInBounds<uintptr_t>(effective_offset, access_size, 2973 env_->min_memory_size)) { 2974 return false; 2975 } 2976 2977 *offset = effective_offset; 2978 return true; 2979 } 2980 2981 Register GetMemoryStart(LiftoffRegList pinned) { 2982 Register memory_start = __ cache_state()->cached_mem_start; 2983 if (memory_start == no_reg) { 2984 memory_start = __ GetUnusedRegister(kGpReg, pinned).gp(); 2985 LOAD_INSTANCE_FIELD(memory_start, MemoryStart, kSystemPointerSize, 2986 pinned); 2987#ifdef V8_SANDBOXED_POINTERS 2988 __ DecodeSandboxedPointer(memory_start); 2989#endif 2990 __ cache_state()->SetMemStartCacheRegister(memory_start); 2991 } 2992 return memory_start; 2993 } 2994 2995 void LoadMem(FullDecoder* decoder, LoadType type, 2996 const MemoryAccessImmediate<validate>& imm, 2997 const Value& index_val, Value* result) { 2998 ValueKind kind = type.value_type().kind(); 2999 RegClass rc = reg_class_for(kind); 3000 if (!CheckSupportedType(decoder, kind, "load")) return; 3001 3002 uintptr_t offset = imm.offset; 3003 Register index = no_reg; 3004 3005 // Only look at the slot, do not pop it yet (will happen in PopToRegister 3006 // below, if this is not a statically-in-bounds index). 3007 auto& index_slot = __ cache_state()->stack_state.back(); 3008 bool i64_offset = index_val.type == kWasmI64; 3009 if (IndexStaticallyInBounds(index_slot, type.size(), &offset)) { 3010 __ cache_state()->stack_state.pop_back(); 3011 CODE_COMMENT("load from memory (constant offset)"); 3012 LiftoffRegList pinned; 3013 Register mem = pinned.set(GetMemoryStart(pinned)); 3014 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); 3015 __ Load(value, mem, no_reg, offset, type, pinned, nullptr, true, 3016 i64_offset); 3017 __ PushRegister(kind, value); 3018 } else { 3019 LiftoffRegister full_index = __ PopToRegister(); 3020 index = BoundsCheckMem(decoder, type.size(), offset, full_index, {}, 3021 kDontForceCheck); 3022 if (index == no_reg) return; 3023 3024 CODE_COMMENT("load from memory"); 3025 LiftoffRegList pinned = {index}; 3026 3027 // Load the memory start address only now to reduce register pressure 3028 // (important on ia32). 3029 Register mem = pinned.set(GetMemoryStart(pinned)); 3030 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); 3031 3032 uint32_t protected_load_pc = 0; 3033 __ Load(value, mem, index, offset, type, pinned, &protected_load_pc, true, 3034 i64_offset); 3035 if (env_->bounds_checks == kTrapHandler) { 3036 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 3037 protected_load_pc); 3038 } 3039 __ PushRegister(kind, value); 3040 } 3041 3042 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 3043 TraceMemoryOperation(false, type.mem_type().representation(), index, 3044 offset, decoder->position()); 3045 } 3046 } 3047 3048 void LoadTransform(FullDecoder* decoder, LoadType type, 3049 LoadTransformationKind transform, 3050 const MemoryAccessImmediate<validate>& imm, 3051 const Value& index_val, Value* result) { 3052 // LoadTransform requires SIMD support, so check for it here. If 3053 // unsupported, bailout and let TurboFan lower the code. 3054 if (!CheckSupportedType(decoder, kS128, "LoadTransform")) { 3055 return; 3056 } 3057 3058 LiftoffRegister full_index = __ PopToRegister(); 3059 // For load splats and load zero, LoadType is the size of the load, and for 3060 // load extends, LoadType is the size of the lane, and it always loads 8 3061 // bytes. 3062 uint32_t access_size = 3063 transform == LoadTransformationKind::kExtend ? 8 : type.size(); 3064 Register index = BoundsCheckMem(decoder, access_size, imm.offset, 3065 full_index, {}, kDontForceCheck); 3066 if (index == no_reg) return; 3067 3068 uintptr_t offset = imm.offset; 3069 LiftoffRegList pinned = {index}; 3070 CODE_COMMENT("load with transformation"); 3071 Register addr = GetMemoryStart(pinned); 3072 LiftoffRegister value = __ GetUnusedRegister(reg_class_for(kS128), {}); 3073 uint32_t protected_load_pc = 0; 3074 __ LoadTransform(value, addr, index, offset, type, transform, 3075 &protected_load_pc); 3076 3077 if (env_->bounds_checks == kTrapHandler) { 3078 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 3079 protected_load_pc); 3080 } 3081 __ PushRegister(kS128, value); 3082 3083 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 3084 // Again load extend is different. 3085 MachineRepresentation mem_rep = 3086 transform == LoadTransformationKind::kExtend 3087 ? MachineRepresentation::kWord64 3088 : type.mem_type().representation(); 3089 TraceMemoryOperation(false, mem_rep, index, offset, decoder->position()); 3090 } 3091 } 3092 3093 void LoadLane(FullDecoder* decoder, LoadType type, const Value& _value, 3094 const Value& _index, const MemoryAccessImmediate<validate>& imm, 3095 const uint8_t laneidx, Value* _result) { 3096 if (!CheckSupportedType(decoder, kS128, "LoadLane")) { 3097 return; 3098 } 3099 3100 LiftoffRegList pinned; 3101 LiftoffRegister value = pinned.set(__ PopToRegister()); 3102 LiftoffRegister full_index = __ PopToRegister(); 3103 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 3104 full_index, pinned, kDontForceCheck); 3105 if (index == no_reg) return; 3106 3107 uintptr_t offset = imm.offset; 3108 pinned.set(index); 3109 CODE_COMMENT("load lane"); 3110 Register addr = GetMemoryStart(pinned); 3111 LiftoffRegister result = __ GetUnusedRegister(reg_class_for(kS128), {}); 3112 uint32_t protected_load_pc = 0; 3113 3114 __ LoadLane(result, value, addr, index, offset, type, laneidx, 3115 &protected_load_pc); 3116 if (env_->bounds_checks == kTrapHandler) { 3117 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 3118 protected_load_pc); 3119 } 3120 3121 __ PushRegister(kS128, result); 3122 3123 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 3124 TraceMemoryOperation(false, type.mem_type().representation(), index, 3125 offset, decoder->position()); 3126 } 3127 } 3128 3129 void StoreMem(FullDecoder* decoder, StoreType type, 3130 const MemoryAccessImmediate<validate>& imm, 3131 const Value& index_val, const Value& value_val) { 3132 ValueKind kind = type.value_type().kind(); 3133 if (!CheckSupportedType(decoder, kind, "store")) return; 3134 3135 LiftoffRegList pinned; 3136 LiftoffRegister value = pinned.set(__ PopToRegister()); 3137 3138 uintptr_t offset = imm.offset; 3139 Register index = no_reg; 3140 3141 auto& index_slot = __ cache_state()->stack_state.back(); 3142 if (IndexStaticallyInBounds(index_slot, type.size(), &offset)) { 3143 __ cache_state()->stack_state.pop_back(); 3144 CODE_COMMENT("store to memory (constant offset)"); 3145 Register mem = pinned.set(GetMemoryStart(pinned)); 3146 __ Store(mem, no_reg, offset, value, type, pinned, nullptr, true); 3147 } else { 3148 LiftoffRegister full_index = __ PopToRegister(pinned); 3149 index = BoundsCheckMem(decoder, type.size(), imm.offset, full_index, 3150 pinned, kDontForceCheck); 3151 if (index == no_reg) return; 3152 3153 pinned.set(index); 3154 CODE_COMMENT("store to memory"); 3155 uint32_t protected_store_pc = 0; 3156 // Load the memory start address only now to reduce register pressure 3157 // (important on ia32). 3158 Register mem = pinned.set(GetMemoryStart(pinned)); 3159 LiftoffRegList outer_pinned; 3160 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) outer_pinned.set(index); 3161 __ Store(mem, index, offset, value, type, outer_pinned, 3162 &protected_store_pc, true); 3163 if (env_->bounds_checks == kTrapHandler) { 3164 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 3165 protected_store_pc); 3166 } 3167 } 3168 3169 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 3170 TraceMemoryOperation(true, type.mem_rep(), index, offset, 3171 decoder->position()); 3172 } 3173 } 3174 3175 void StoreLane(FullDecoder* decoder, StoreType type, 3176 const MemoryAccessImmediate<validate>& imm, 3177 const Value& _index, const Value& _value, const uint8_t lane) { 3178 if (!CheckSupportedType(decoder, kS128, "StoreLane")) return; 3179 LiftoffRegList pinned; 3180 LiftoffRegister value = pinned.set(__ PopToRegister()); 3181 LiftoffRegister full_index = __ PopToRegister(pinned); 3182 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 3183 full_index, pinned, kDontForceCheck); 3184 if (index == no_reg) return; 3185 3186 uintptr_t offset = imm.offset; 3187 pinned.set(index); 3188 CODE_COMMENT("store lane to memory"); 3189 Register addr = pinned.set(GetMemoryStart(pinned)); 3190 uint32_t protected_store_pc = 0; 3191 __ StoreLane(addr, index, offset, value, type, lane, &protected_store_pc); 3192 if (env_->bounds_checks == kTrapHandler) { 3193 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds, 3194 protected_store_pc); 3195 } 3196 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 3197 TraceMemoryOperation(true, type.mem_rep(), index, offset, 3198 decoder->position()); 3199 } 3200 } 3201 3202 void CurrentMemoryPages(FullDecoder* /* decoder */, Value* /* result */) { 3203 Register mem_size = __ GetUnusedRegister(kGpReg, {}).gp(); 3204 LOAD_INSTANCE_FIELD(mem_size, MemorySize, kSystemPointerSize, {}); 3205 __ emit_ptrsize_shri(mem_size, mem_size, kWasmPageSizeLog2); 3206 LiftoffRegister result{mem_size}; 3207 if (env_->module->is_memory64 && kNeedI64RegPair) { 3208 LiftoffRegister high_word = 3209 __ GetUnusedRegister(kGpReg, LiftoffRegList{mem_size}); 3210 // The high word is always 0 on 32-bit systems. 3211 __ LoadConstant(high_word, WasmValue{uint32_t{0}}); 3212 result = LiftoffRegister::ForPair(mem_size, high_word.gp()); 3213 } 3214 __ PushRegister(env_->module->is_memory64 ? kI64 : kI32, result); 3215 } 3216 3217 void MemoryGrow(FullDecoder* decoder, const Value& value, Value* result_val) { 3218 // Pop the input, then spill all cache registers to make the runtime call. 3219 LiftoffRegList pinned; 3220 LiftoffRegister input = pinned.set(__ PopToRegister()); 3221 __ SpillAllRegisters(); 3222 3223 LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 3224 3225 Label done; 3226 3227 if (env_->module->is_memory64) { 3228 // If the high word is not 0, this will always fail (would grow by 3229 // >=256TB). The int32_t value will be sign-extended below. 3230 __ LoadConstant(result, WasmValue(int32_t{-1})); 3231 if (kNeedI64RegPair) { 3232 __ emit_cond_jump(kUnequal /* neq */, &done, kI32, input.high_gp()); 3233 input = input.low(); 3234 } else { 3235 LiftoffRegister high_word = __ GetUnusedRegister(kGpReg, pinned); 3236 __ emit_i64_shri(high_word, input, 32); 3237 __ emit_cond_jump(kUnequal /* neq */, &done, kI32, high_word.gp()); 3238 } 3239 } 3240 3241 WasmMemoryGrowDescriptor descriptor; 3242 DCHECK_EQ(0, descriptor.GetStackParameterCount()); 3243 DCHECK_EQ(1, descriptor.GetRegisterParameterCount()); 3244 DCHECK_EQ(machine_type(kI32), descriptor.GetParameterType(0)); 3245 3246 Register param_reg = descriptor.GetRegisterParameter(0); 3247 if (input.gp() != param_reg) __ Move(param_reg, input.gp(), kI32); 3248 3249 __ CallRuntimeStub(WasmCode::kWasmMemoryGrow); 3250 DefineSafepoint(); 3251 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 3252 3253 if (kReturnRegister0 != result.gp()) { 3254 __ Move(result.gp(), kReturnRegister0, kI32); 3255 } 3256 3257 __ bind(&done); 3258 3259 if (env_->module->is_memory64) { 3260 LiftoffRegister result64 = result; 3261 if (kNeedI64RegPair) result64 = __ GetUnusedRegister(kGpRegPair, pinned); 3262 __ emit_type_conversion(kExprI64SConvertI32, result64, result, nullptr); 3263 __ PushRegister(kI64, result64); 3264 } else { 3265 __ PushRegister(kI32, result); 3266 } 3267 } 3268 3269 base::OwnedVector<DebugSideTable::Entry::Value> 3270 GetCurrentDebugSideTableEntries( 3271 FullDecoder* decoder, 3272 DebugSideTableBuilder::AssumeSpilling assume_spilling) { 3273 auto& stack_state = __ cache_state()->stack_state; 3274 auto values = 3275 base::OwnedVector<DebugSideTable::Entry::Value>::NewForOverwrite( 3276 stack_state.size()); 3277 3278 // For function calls, the decoder still has the arguments on the stack, but 3279 // Liftoff already popped them. Hence {decoder->stack_size()} can be bigger 3280 // than expected. Just ignore that and use the lower part only. 3281 DCHECK_LE(stack_state.size() - num_exceptions_, 3282 decoder->num_locals() + decoder->stack_size()); 3283 int index = 0; 3284 int decoder_stack_index = decoder->stack_size(); 3285 // Iterate the operand stack control block by control block, so that we can 3286 // handle the implicit exception value for try blocks. 3287 for (int j = decoder->control_depth() - 1; j >= 0; j--) { 3288 Control* control = decoder->control_at(j); 3289 Control* next_control = j > 0 ? decoder->control_at(j - 1) : nullptr; 3290 int end_index = next_control 3291 ? next_control->stack_depth + __ num_locals() + 3292 next_control->num_exceptions 3293 : __ cache_state()->stack_height(); 3294 bool exception = control->is_try_catch() || control->is_try_catchall(); 3295 for (; index < end_index; ++index) { 3296 auto& slot = stack_state[index]; 3297 auto& value = values[index]; 3298 value.index = index; 3299 ValueType type = 3300 index < static_cast<int>(__ num_locals()) 3301 ? decoder->local_type(index) 3302 : exception ? ValueType::Ref(HeapType::kAny, kNonNullable) 3303 : decoder->stack_value(decoder_stack_index--)->type; 3304 DCHECK(CheckCompatibleStackSlotTypes(slot.kind(), type.kind())); 3305 value.type = type; 3306 switch (slot.loc()) { 3307 case kIntConst: 3308 value.storage = DebugSideTable::Entry::kConstant; 3309 value.i32_const = slot.i32_const(); 3310 break; 3311 case kRegister: 3312 DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling); 3313 if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) { 3314 value.storage = DebugSideTable::Entry::kRegister; 3315 value.reg_code = slot.reg().liftoff_code(); 3316 break; 3317 } 3318 DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling); 3319 V8_FALLTHROUGH; 3320 case kStack: 3321 value.storage = DebugSideTable::Entry::kStack; 3322 value.stack_offset = slot.offset(); 3323 break; 3324 } 3325 exception = false; 3326 } 3327 } 3328 DCHECK_EQ(values.size(), index); 3329 return values; 3330 } 3331 3332 void RegisterDebugSideTableEntry( 3333 FullDecoder* decoder, 3334 DebugSideTableBuilder::AssumeSpilling assume_spilling) { 3335 if (V8_LIKELY(!debug_sidetable_builder_)) return; 3336 debug_sidetable_builder_->NewEntry( 3337 __ pc_offset(), 3338 GetCurrentDebugSideTableEntries(decoder, assume_spilling).as_vector()); 3339 } 3340 3341 DebugSideTableBuilder::EntryBuilder* RegisterOOLDebugSideTableEntry( 3342 FullDecoder* decoder) { 3343 if (V8_LIKELY(!debug_sidetable_builder_)) return nullptr; 3344 return debug_sidetable_builder_->NewOOLEntry( 3345 GetCurrentDebugSideTableEntries(decoder, 3346 DebugSideTableBuilder::kAssumeSpilling) 3347 .as_vector()); 3348 } 3349 3350 enum TailCall : bool { kTailCall = true, kNoTailCall = false }; 3351 3352 void CallDirect(FullDecoder* decoder, 3353 const CallFunctionImmediate<validate>& imm, 3354 const Value args[], Value[]) { 3355 CallDirect(decoder, imm, args, nullptr, kNoTailCall); 3356 } 3357 3358 void CallIndirect(FullDecoder* decoder, const Value& index_val, 3359 const CallIndirectImmediate<validate>& imm, 3360 const Value args[], Value returns[]) { 3361 CallIndirect(decoder, index_val, imm, kNoTailCall); 3362 } 3363 3364 void CallRef(FullDecoder* decoder, const Value& func_ref, 3365 const FunctionSig* sig, uint32_t sig_index, const Value args[], 3366 Value returns[]) { 3367 CallRef(decoder, func_ref.type, sig, kNoTailCall); 3368 } 3369 3370 void ReturnCall(FullDecoder* decoder, 3371 const CallFunctionImmediate<validate>& imm, 3372 const Value args[]) { 3373 TierupCheckOnExit(decoder); 3374 CallDirect(decoder, imm, args, nullptr, kTailCall); 3375 } 3376 3377 void ReturnCallIndirect(FullDecoder* decoder, const Value& index_val, 3378 const CallIndirectImmediate<validate>& imm, 3379 const Value args[]) { 3380 TierupCheckOnExit(decoder); 3381 CallIndirect(decoder, index_val, imm, kTailCall); 3382 } 3383 3384 void ReturnCallRef(FullDecoder* decoder, const Value& func_ref, 3385 const FunctionSig* sig, uint32_t sig_index, 3386 const Value args[]) { 3387 TierupCheckOnExit(decoder); 3388 CallRef(decoder, func_ref.type, sig, kTailCall); 3389 } 3390 3391 void BrOnNull(FullDecoder* decoder, const Value& ref_object, uint32_t depth, 3392 bool pass_null_along_branch, 3393 Value* /* result_on_fallthrough */) { 3394 // Before branching, materialize all constants. This avoids repeatedly 3395 // materializing them for each conditional branch. 3396 if (depth != decoder->control_depth() - 1) { 3397 __ MaterializeMergedConstants( 3398 decoder->control_at(depth)->br_merge()->arity); 3399 } 3400 3401 Label cont_false; 3402 LiftoffRegList pinned; 3403 LiftoffRegister ref = pinned.set(__ PopToRegister(pinned)); 3404 Register null = __ GetUnusedRegister(kGpReg, pinned).gp(); 3405 LoadNullValue(null, pinned); 3406 __ emit_cond_jump(kUnequal, &cont_false, ref_object.type.kind(), ref.gp(), 3407 null); 3408 if (pass_null_along_branch) LoadNullValue(null, pinned); 3409 BrOrRet(decoder, depth, 0); 3410 __ bind(&cont_false); 3411 __ PushRegister(kRef, ref); 3412 } 3413 3414 void BrOnNonNull(FullDecoder* decoder, const Value& ref_object, 3415 uint32_t depth) { 3416 // Before branching, materialize all constants. This avoids repeatedly 3417 // materializing them for each conditional branch. 3418 if (depth != decoder->control_depth() - 1) { 3419 __ MaterializeMergedConstants( 3420 decoder->control_at(depth)->br_merge()->arity); 3421 } 3422 3423 Label cont_false; 3424 LiftoffRegList pinned; 3425 LiftoffRegister ref = pinned.set(__ PopToRegister(pinned)); 3426 // Put the reference back onto the stack for the branch. 3427 __ PushRegister(kRef, ref); 3428 3429 Register null = __ GetUnusedRegister(kGpReg, pinned).gp(); 3430 LoadNullValue(null, pinned); 3431 __ emit_cond_jump(kEqual, &cont_false, ref_object.type.kind(), ref.gp(), 3432 null); 3433 3434 BrOrRet(decoder, depth, 0); 3435 // Drop the reference if we are not branching. 3436 __ DropValues(1); 3437 __ bind(&cont_false); 3438 } 3439 3440 template <ValueKind src_kind, ValueKind result_kind, 3441 ValueKind result_lane_kind = kVoid, typename EmitFn> 3442 void EmitTerOp(EmitFn fn) { 3443 static constexpr RegClass src_rc = reg_class_for(src_kind); 3444 static constexpr RegClass result_rc = reg_class_for(result_kind); 3445 LiftoffRegister src3 = __ PopToRegister(); 3446 LiftoffRegister src2 = __ PopToRegister(LiftoffRegList{src3}); 3447 LiftoffRegister src1 = __ PopToRegister(LiftoffRegList{src3, src2}); 3448 // Reusing src1 and src2 will complicate codegen for select for some 3449 // backend, so we allow only reusing src3 (the mask), and pin src1 and src2. 3450 LiftoffRegister dst = src_rc == result_rc 3451 ? __ GetUnusedRegister(result_rc, {src3}, 3452 LiftoffRegList{src1, src2}) 3453 : __ GetUnusedRegister(result_rc, {}); 3454 CallEmitFn(fn, dst, src1, src2, src3); 3455 if (V8_UNLIKELY(nondeterminism_)) { 3456 LiftoffRegList pinned = {dst}; 3457 if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) { 3458 CheckNan(dst, pinned, result_kind); 3459 } else if (result_kind == ValueKind::kS128 && 3460 (result_lane_kind == kF32 || result_lane_kind == kF64)) { 3461 CheckS128Nan(dst, LiftoffRegList{src1, src2, src3, dst}, 3462 result_lane_kind); 3463 } 3464 } 3465 __ PushRegister(result_kind, dst); 3466 } 3467 3468 template <typename EmitFn, typename EmitFnImm> 3469 void EmitSimdShiftOp(EmitFn fn, EmitFnImm fnImm) { 3470 static constexpr RegClass result_rc = reg_class_for(kS128); 3471 3472 LiftoffAssembler::VarState rhs_slot = __ cache_state()->stack_state.back(); 3473 // Check if the RHS is an immediate. 3474 if (rhs_slot.is_const()) { 3475 __ cache_state()->stack_state.pop_back(); 3476 int32_t imm = rhs_slot.i32_const(); 3477 3478 LiftoffRegister operand = __ PopToRegister(); 3479 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {operand}, {}); 3480 3481 CallEmitFn(fnImm, dst, operand, imm); 3482 __ PushRegister(kS128, dst); 3483 } else { 3484 LiftoffRegister count = __ PopToRegister(); 3485 LiftoffRegister operand = __ PopToRegister(); 3486 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {operand}, {}); 3487 3488 CallEmitFn(fn, dst, operand, count); 3489 __ PushRegister(kS128, dst); 3490 } 3491 } 3492 3493 template <ValueKind result_lane_kind> 3494 void EmitSimdFloatRoundingOpWithCFallback( 3495 bool (LiftoffAssembler::*emit_fn)(LiftoffRegister, LiftoffRegister), 3496 ExternalReference (*ext_ref)()) { 3497 static constexpr RegClass rc = reg_class_for(kS128); 3498 LiftoffRegister src = __ PopToRegister(); 3499 LiftoffRegister dst = __ GetUnusedRegister(rc, {src}, {}); 3500 if (!(asm_.*emit_fn)(dst, src)) { 3501 // Return v128 via stack for ARM. 3502 auto sig_v_s = MakeSig::Params(kS128); 3503 GenerateCCall(&dst, &sig_v_s, kS128, &src, ext_ref()); 3504 } 3505 if (V8_UNLIKELY(nondeterminism_)) { 3506 LiftoffRegList pinned = {dst}; 3507 CheckS128Nan(dst, pinned, result_lane_kind); 3508 } 3509 __ PushRegister(kS128, dst); 3510 } 3511 3512 void SimdOp(FullDecoder* decoder, WasmOpcode opcode, base::Vector<Value> args, 3513 Value* result) { 3514 if (!CpuFeatures::SupportsWasmSimd128()) { 3515 return unsupported(decoder, kSimd, "simd"); 3516 } 3517 switch (opcode) { 3518 case wasm::kExprI8x16Swizzle: 3519 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_swizzle); 3520 case wasm::kExprI8x16Popcnt: 3521 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_popcnt); 3522 case wasm::kExprI8x16Splat: 3523 return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i8x16_splat); 3524 case wasm::kExprI16x8Splat: 3525 return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i16x8_splat); 3526 case wasm::kExprI32x4Splat: 3527 return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i32x4_splat); 3528 case wasm::kExprI64x2Splat: 3529 return EmitUnOp<kI64, kS128>(&LiftoffAssembler::emit_i64x2_splat); 3530 case wasm::kExprF32x4Splat: 3531 return EmitUnOp<kF32, kS128, kF32>(&LiftoffAssembler::emit_f32x4_splat); 3532 case wasm::kExprF64x2Splat: 3533 return EmitUnOp<kF64, kS128, kF64>(&LiftoffAssembler::emit_f64x2_splat); 3534 case wasm::kExprI8x16Eq: 3535 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_eq); 3536 case wasm::kExprI8x16Ne: 3537 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ne); 3538 case wasm::kExprI8x16LtS: 3539 return EmitBinOp<kS128, kS128, true>( 3540 &LiftoffAssembler::emit_i8x16_gt_s); 3541 case wasm::kExprI8x16LtU: 3542 return EmitBinOp<kS128, kS128, true>( 3543 &LiftoffAssembler::emit_i8x16_gt_u); 3544 case wasm::kExprI8x16GtS: 3545 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_gt_s); 3546 case wasm::kExprI8x16GtU: 3547 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_gt_u); 3548 case wasm::kExprI8x16LeS: 3549 return EmitBinOp<kS128, kS128, true>( 3550 &LiftoffAssembler::emit_i8x16_ge_s); 3551 case wasm::kExprI8x16LeU: 3552 return EmitBinOp<kS128, kS128, true>( 3553 &LiftoffAssembler::emit_i8x16_ge_u); 3554 case wasm::kExprI8x16GeS: 3555 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ge_s); 3556 case wasm::kExprI8x16GeU: 3557 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ge_u); 3558 case wasm::kExprI16x8Eq: 3559 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_eq); 3560 case wasm::kExprI16x8Ne: 3561 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ne); 3562 case wasm::kExprI16x8LtS: 3563 return EmitBinOp<kS128, kS128, true>( 3564 &LiftoffAssembler::emit_i16x8_gt_s); 3565 case wasm::kExprI16x8LtU: 3566 return EmitBinOp<kS128, kS128, true>( 3567 &LiftoffAssembler::emit_i16x8_gt_u); 3568 case wasm::kExprI16x8GtS: 3569 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_gt_s); 3570 case wasm::kExprI16x8GtU: 3571 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_gt_u); 3572 case wasm::kExprI16x8LeS: 3573 return EmitBinOp<kS128, kS128, true>( 3574 &LiftoffAssembler::emit_i16x8_ge_s); 3575 case wasm::kExprI16x8LeU: 3576 return EmitBinOp<kS128, kS128, true>( 3577 &LiftoffAssembler::emit_i16x8_ge_u); 3578 case wasm::kExprI16x8GeS: 3579 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ge_s); 3580 case wasm::kExprI16x8GeU: 3581 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ge_u); 3582 case wasm::kExprI32x4Eq: 3583 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_eq); 3584 case wasm::kExprI32x4Ne: 3585 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ne); 3586 case wasm::kExprI32x4LtS: 3587 return EmitBinOp<kS128, kS128, true>( 3588 &LiftoffAssembler::emit_i32x4_gt_s); 3589 case wasm::kExprI32x4LtU: 3590 return EmitBinOp<kS128, kS128, true>( 3591 &LiftoffAssembler::emit_i32x4_gt_u); 3592 case wasm::kExprI32x4GtS: 3593 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_gt_s); 3594 case wasm::kExprI32x4GtU: 3595 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_gt_u); 3596 case wasm::kExprI32x4LeS: 3597 return EmitBinOp<kS128, kS128, true>( 3598 &LiftoffAssembler::emit_i32x4_ge_s); 3599 case wasm::kExprI32x4LeU: 3600 return EmitBinOp<kS128, kS128, true>( 3601 &LiftoffAssembler::emit_i32x4_ge_u); 3602 case wasm::kExprI32x4GeS: 3603 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ge_s); 3604 case wasm::kExprI32x4GeU: 3605 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ge_u); 3606 case wasm::kExprI64x2Eq: 3607 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_eq); 3608 case wasm::kExprI64x2Ne: 3609 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_ne); 3610 case wasm::kExprI64x2LtS: 3611 return EmitBinOp<kS128, kS128, true>( 3612 &LiftoffAssembler::emit_i64x2_gt_s); 3613 case wasm::kExprI64x2GtS: 3614 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_gt_s); 3615 case wasm::kExprI64x2LeS: 3616 return EmitBinOp<kS128, kS128, true>( 3617 &LiftoffAssembler::emit_i64x2_ge_s); 3618 case wasm::kExprI64x2GeS: 3619 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_ge_s); 3620 case wasm::kExprF32x4Eq: 3621 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_eq); 3622 case wasm::kExprF32x4Ne: 3623 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_ne); 3624 case wasm::kExprF32x4Lt: 3625 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_lt); 3626 case wasm::kExprF32x4Gt: 3627 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f32x4_lt); 3628 case wasm::kExprF32x4Le: 3629 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_le); 3630 case wasm::kExprF32x4Ge: 3631 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f32x4_le); 3632 case wasm::kExprF64x2Eq: 3633 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_eq); 3634 case wasm::kExprF64x2Ne: 3635 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_ne); 3636 case wasm::kExprF64x2Lt: 3637 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_lt); 3638 case wasm::kExprF64x2Gt: 3639 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f64x2_lt); 3640 case wasm::kExprF64x2Le: 3641 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_le); 3642 case wasm::kExprF64x2Ge: 3643 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f64x2_le); 3644 case wasm::kExprS128Not: 3645 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_s128_not); 3646 case wasm::kExprS128And: 3647 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_and); 3648 case wasm::kExprS128Or: 3649 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_or); 3650 case wasm::kExprS128Xor: 3651 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_xor); 3652 case wasm::kExprS128Select: 3653 return EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_select); 3654 case wasm::kExprI8x16Neg: 3655 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_neg); 3656 case wasm::kExprV128AnyTrue: 3657 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_v128_anytrue); 3658 case wasm::kExprI8x16AllTrue: 3659 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i8x16_alltrue); 3660 case wasm::kExprI8x16BitMask: 3661 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i8x16_bitmask); 3662 case wasm::kExprI8x16Shl: 3663 return EmitSimdShiftOp(&LiftoffAssembler::emit_i8x16_shl, 3664 &LiftoffAssembler::emit_i8x16_shli); 3665 case wasm::kExprI8x16ShrS: 3666 return EmitSimdShiftOp(&LiftoffAssembler::emit_i8x16_shr_s, 3667 &LiftoffAssembler::emit_i8x16_shri_s); 3668 case wasm::kExprI8x16ShrU: 3669 return EmitSimdShiftOp(&LiftoffAssembler::emit_i8x16_shr_u, 3670 &LiftoffAssembler::emit_i8x16_shri_u); 3671 case wasm::kExprI8x16Add: 3672 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add); 3673 case wasm::kExprI8x16AddSatS: 3674 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add_sat_s); 3675 case wasm::kExprI8x16AddSatU: 3676 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add_sat_u); 3677 case wasm::kExprI8x16Sub: 3678 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub); 3679 case wasm::kExprI8x16SubSatS: 3680 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub_sat_s); 3681 case wasm::kExprI8x16SubSatU: 3682 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub_sat_u); 3683 case wasm::kExprI8x16MinS: 3684 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_min_s); 3685 case wasm::kExprI8x16MinU: 3686 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_min_u); 3687 case wasm::kExprI8x16MaxS: 3688 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_max_s); 3689 case wasm::kExprI8x16MaxU: 3690 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_max_u); 3691 case wasm::kExprI16x8Neg: 3692 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_neg); 3693 case wasm::kExprI16x8AllTrue: 3694 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i16x8_alltrue); 3695 case wasm::kExprI16x8BitMask: 3696 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i16x8_bitmask); 3697 case wasm::kExprI16x8Shl: 3698 return EmitSimdShiftOp(&LiftoffAssembler::emit_i16x8_shl, 3699 &LiftoffAssembler::emit_i16x8_shli); 3700 case wasm::kExprI16x8ShrS: 3701 return EmitSimdShiftOp(&LiftoffAssembler::emit_i16x8_shr_s, 3702 &LiftoffAssembler::emit_i16x8_shri_s); 3703 case wasm::kExprI16x8ShrU: 3704 return EmitSimdShiftOp(&LiftoffAssembler::emit_i16x8_shr_u, 3705 &LiftoffAssembler::emit_i16x8_shri_u); 3706 case wasm::kExprI16x8Add: 3707 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add); 3708 case wasm::kExprI16x8AddSatS: 3709 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add_sat_s); 3710 case wasm::kExprI16x8AddSatU: 3711 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add_sat_u); 3712 case wasm::kExprI16x8Sub: 3713 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub); 3714 case wasm::kExprI16x8SubSatS: 3715 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub_sat_s); 3716 case wasm::kExprI16x8SubSatU: 3717 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub_sat_u); 3718 case wasm::kExprI16x8Mul: 3719 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_mul); 3720 case wasm::kExprI16x8MinS: 3721 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_min_s); 3722 case wasm::kExprI16x8MinU: 3723 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_min_u); 3724 case wasm::kExprI16x8MaxS: 3725 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_max_s); 3726 case wasm::kExprI16x8MaxU: 3727 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_max_u); 3728 case wasm::kExprI16x8ExtAddPairwiseI8x16S: 3729 return EmitUnOp<kS128, kS128>( 3730 &LiftoffAssembler::emit_i16x8_extadd_pairwise_i8x16_s); 3731 case wasm::kExprI16x8ExtAddPairwiseI8x16U: 3732 return EmitUnOp<kS128, kS128>( 3733 &LiftoffAssembler::emit_i16x8_extadd_pairwise_i8x16_u); 3734 case wasm::kExprI16x8ExtMulLowI8x16S: 3735 return EmitBinOp<kS128, kS128>( 3736 &LiftoffAssembler::emit_i16x8_extmul_low_i8x16_s); 3737 case wasm::kExprI16x8ExtMulLowI8x16U: 3738 return EmitBinOp<kS128, kS128>( 3739 &LiftoffAssembler::emit_i16x8_extmul_low_i8x16_u); 3740 case wasm::kExprI16x8ExtMulHighI8x16S: 3741 return EmitBinOp<kS128, kS128>( 3742 &LiftoffAssembler::emit_i16x8_extmul_high_i8x16_s); 3743 case wasm::kExprI16x8ExtMulHighI8x16U: 3744 return EmitBinOp<kS128, kS128>( 3745 &LiftoffAssembler::emit_i16x8_extmul_high_i8x16_u); 3746 case wasm::kExprI16x8Q15MulRSatS: 3747 return EmitBinOp<kS128, kS128>( 3748 &LiftoffAssembler::emit_i16x8_q15mulr_sat_s); 3749 case wasm::kExprI32x4Neg: 3750 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_neg); 3751 case wasm::kExprI32x4AllTrue: 3752 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i32x4_alltrue); 3753 case wasm::kExprI32x4BitMask: 3754 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i32x4_bitmask); 3755 case wasm::kExprI32x4Shl: 3756 return EmitSimdShiftOp(&LiftoffAssembler::emit_i32x4_shl, 3757 &LiftoffAssembler::emit_i32x4_shli); 3758 case wasm::kExprI32x4ShrS: 3759 return EmitSimdShiftOp(&LiftoffAssembler::emit_i32x4_shr_s, 3760 &LiftoffAssembler::emit_i32x4_shri_s); 3761 case wasm::kExprI32x4ShrU: 3762 return EmitSimdShiftOp(&LiftoffAssembler::emit_i32x4_shr_u, 3763 &LiftoffAssembler::emit_i32x4_shri_u); 3764 case wasm::kExprI32x4Add: 3765 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_add); 3766 case wasm::kExprI32x4Sub: 3767 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_sub); 3768 case wasm::kExprI32x4Mul: 3769 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_mul); 3770 case wasm::kExprI32x4MinS: 3771 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_min_s); 3772 case wasm::kExprI32x4MinU: 3773 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_min_u); 3774 case wasm::kExprI32x4MaxS: 3775 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_max_s); 3776 case wasm::kExprI32x4MaxU: 3777 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_max_u); 3778 case wasm::kExprI32x4DotI16x8S: 3779 return EmitBinOp<kS128, kS128>( 3780 &LiftoffAssembler::emit_i32x4_dot_i16x8_s); 3781 case wasm::kExprI32x4ExtAddPairwiseI16x8S: 3782 return EmitUnOp<kS128, kS128>( 3783 &LiftoffAssembler::emit_i32x4_extadd_pairwise_i16x8_s); 3784 case wasm::kExprI32x4ExtAddPairwiseI16x8U: 3785 return EmitUnOp<kS128, kS128>( 3786 &LiftoffAssembler::emit_i32x4_extadd_pairwise_i16x8_u); 3787 case wasm::kExprI32x4ExtMulLowI16x8S: 3788 return EmitBinOp<kS128, kS128>( 3789 &LiftoffAssembler::emit_i32x4_extmul_low_i16x8_s); 3790 case wasm::kExprI32x4ExtMulLowI16x8U: 3791 return EmitBinOp<kS128, kS128>( 3792 &LiftoffAssembler::emit_i32x4_extmul_low_i16x8_u); 3793 case wasm::kExprI32x4ExtMulHighI16x8S: 3794 return EmitBinOp<kS128, kS128>( 3795 &LiftoffAssembler::emit_i32x4_extmul_high_i16x8_s); 3796 case wasm::kExprI32x4ExtMulHighI16x8U: 3797 return EmitBinOp<kS128, kS128>( 3798 &LiftoffAssembler::emit_i32x4_extmul_high_i16x8_u); 3799 case wasm::kExprI64x2Neg: 3800 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_neg); 3801 case wasm::kExprI64x2AllTrue: 3802 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i64x2_alltrue); 3803 case wasm::kExprI64x2Shl: 3804 return EmitSimdShiftOp(&LiftoffAssembler::emit_i64x2_shl, 3805 &LiftoffAssembler::emit_i64x2_shli); 3806 case wasm::kExprI64x2ShrS: 3807 return EmitSimdShiftOp(&LiftoffAssembler::emit_i64x2_shr_s, 3808 &LiftoffAssembler::emit_i64x2_shri_s); 3809 case wasm::kExprI64x2ShrU: 3810 return EmitSimdShiftOp(&LiftoffAssembler::emit_i64x2_shr_u, 3811 &LiftoffAssembler::emit_i64x2_shri_u); 3812 case wasm::kExprI64x2Add: 3813 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_add); 3814 case wasm::kExprI64x2Sub: 3815 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_sub); 3816 case wasm::kExprI64x2Mul: 3817 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_mul); 3818 case wasm::kExprI64x2ExtMulLowI32x4S: 3819 return EmitBinOp<kS128, kS128>( 3820 &LiftoffAssembler::emit_i64x2_extmul_low_i32x4_s); 3821 case wasm::kExprI64x2ExtMulLowI32x4U: 3822 return EmitBinOp<kS128, kS128>( 3823 &LiftoffAssembler::emit_i64x2_extmul_low_i32x4_u); 3824 case wasm::kExprI64x2ExtMulHighI32x4S: 3825 return EmitBinOp<kS128, kS128>( 3826 &LiftoffAssembler::emit_i64x2_extmul_high_i32x4_s); 3827 case wasm::kExprI64x2ExtMulHighI32x4U: 3828 return EmitBinOp<kS128, kS128>( 3829 &LiftoffAssembler::emit_i64x2_extmul_high_i32x4_u); 3830 case wasm::kExprI64x2BitMask: 3831 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i64x2_bitmask); 3832 case wasm::kExprI64x2SConvertI32x4Low: 3833 return EmitUnOp<kS128, kS128>( 3834 &LiftoffAssembler::emit_i64x2_sconvert_i32x4_low); 3835 case wasm::kExprI64x2SConvertI32x4High: 3836 return EmitUnOp<kS128, kS128>( 3837 &LiftoffAssembler::emit_i64x2_sconvert_i32x4_high); 3838 case wasm::kExprI64x2UConvertI32x4Low: 3839 return EmitUnOp<kS128, kS128>( 3840 &LiftoffAssembler::emit_i64x2_uconvert_i32x4_low); 3841 case wasm::kExprI64x2UConvertI32x4High: 3842 return EmitUnOp<kS128, kS128>( 3843 &LiftoffAssembler::emit_i64x2_uconvert_i32x4_high); 3844 case wasm::kExprF32x4Abs: 3845 return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_abs); 3846 case wasm::kExprF32x4Neg: 3847 return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_neg); 3848 case wasm::kExprF32x4Sqrt: 3849 return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_sqrt); 3850 case wasm::kExprF32x4Ceil: 3851 return EmitSimdFloatRoundingOpWithCFallback<kF32>( 3852 &LiftoffAssembler::emit_f32x4_ceil, 3853 &ExternalReference::wasm_f32x4_ceil); 3854 case wasm::kExprF32x4Floor: 3855 return EmitSimdFloatRoundingOpWithCFallback<kF32>( 3856 &LiftoffAssembler::emit_f32x4_floor, 3857 ExternalReference::wasm_f32x4_floor); 3858 case wasm::kExprF32x4Trunc: 3859 return EmitSimdFloatRoundingOpWithCFallback<kF32>( 3860 &LiftoffAssembler::emit_f32x4_trunc, 3861 ExternalReference::wasm_f32x4_trunc); 3862 case wasm::kExprF32x4NearestInt: 3863 return EmitSimdFloatRoundingOpWithCFallback<kF32>( 3864 &LiftoffAssembler::emit_f32x4_nearest_int, 3865 ExternalReference::wasm_f32x4_nearest_int); 3866 case wasm::kExprF32x4Add: 3867 return EmitBinOp<kS128, kS128, false, kF32>( 3868 &LiftoffAssembler::emit_f32x4_add); 3869 case wasm::kExprF32x4Sub: 3870 return EmitBinOp<kS128, kS128, false, kF32>( 3871 &LiftoffAssembler::emit_f32x4_sub); 3872 case wasm::kExprF32x4Mul: 3873 return EmitBinOp<kS128, kS128, false, kF32>( 3874 &LiftoffAssembler::emit_f32x4_mul); 3875 case wasm::kExprF32x4Div: 3876 return EmitBinOp<kS128, kS128, false, kF32>( 3877 &LiftoffAssembler::emit_f32x4_div); 3878 case wasm::kExprF32x4Min: 3879 return EmitBinOp<kS128, kS128, false, kF32>( 3880 &LiftoffAssembler::emit_f32x4_min); 3881 case wasm::kExprF32x4Max: 3882 return EmitBinOp<kS128, kS128, false, kF32>( 3883 &LiftoffAssembler::emit_f32x4_max); 3884 case wasm::kExprF32x4Pmin: 3885 return EmitBinOp<kS128, kS128, false, kF32>( 3886 &LiftoffAssembler::emit_f32x4_pmin); 3887 case wasm::kExprF32x4Pmax: 3888 return EmitBinOp<kS128, kS128, false, kF32>( 3889 &LiftoffAssembler::emit_f32x4_pmax); 3890 case wasm::kExprF64x2Abs: 3891 return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_abs); 3892 case wasm::kExprF64x2Neg: 3893 return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_neg); 3894 case wasm::kExprF64x2Sqrt: 3895 return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_sqrt); 3896 case wasm::kExprF64x2Ceil: 3897 return EmitSimdFloatRoundingOpWithCFallback<kF64>( 3898 &LiftoffAssembler::emit_f64x2_ceil, 3899 &ExternalReference::wasm_f64x2_ceil); 3900 case wasm::kExprF64x2Floor: 3901 return EmitSimdFloatRoundingOpWithCFallback<kF64>( 3902 &LiftoffAssembler::emit_f64x2_floor, 3903 ExternalReference::wasm_f64x2_floor); 3904 case wasm::kExprF64x2Trunc: 3905 return EmitSimdFloatRoundingOpWithCFallback<kF64>( 3906 &LiftoffAssembler::emit_f64x2_trunc, 3907 ExternalReference::wasm_f64x2_trunc); 3908 case wasm::kExprF64x2NearestInt: 3909 return EmitSimdFloatRoundingOpWithCFallback<kF64>( 3910 &LiftoffAssembler::emit_f64x2_nearest_int, 3911 ExternalReference::wasm_f64x2_nearest_int); 3912 case wasm::kExprF64x2Add: 3913 return EmitBinOp<kS128, kS128, false, kF64>( 3914 &LiftoffAssembler::emit_f64x2_add); 3915 case wasm::kExprF64x2Sub: 3916 return EmitBinOp<kS128, kS128, false, kF64>( 3917 &LiftoffAssembler::emit_f64x2_sub); 3918 case wasm::kExprF64x2Mul: 3919 return EmitBinOp<kS128, kS128, false, kF64>( 3920 &LiftoffAssembler::emit_f64x2_mul); 3921 case wasm::kExprF64x2Div: 3922 return EmitBinOp<kS128, kS128, false, kF64>( 3923 &LiftoffAssembler::emit_f64x2_div); 3924 case wasm::kExprF64x2Min: 3925 return EmitBinOp<kS128, kS128, false, kF64>( 3926 &LiftoffAssembler::emit_f64x2_min); 3927 case wasm::kExprF64x2Max: 3928 return EmitBinOp<kS128, kS128, false, kF64>( 3929 &LiftoffAssembler::emit_f64x2_max); 3930 case wasm::kExprF64x2Pmin: 3931 return EmitBinOp<kS128, kS128, false, kF64>( 3932 &LiftoffAssembler::emit_f64x2_pmin); 3933 case wasm::kExprF64x2Pmax: 3934 return EmitBinOp<kS128, kS128, false, kF64>( 3935 &LiftoffAssembler::emit_f64x2_pmax); 3936 case wasm::kExprI32x4SConvertF32x4: 3937 return EmitUnOp<kS128, kS128, kF32>( 3938 &LiftoffAssembler::emit_i32x4_sconvert_f32x4); 3939 case wasm::kExprI32x4UConvertF32x4: 3940 return EmitUnOp<kS128, kS128, kF32>( 3941 &LiftoffAssembler::emit_i32x4_uconvert_f32x4); 3942 case wasm::kExprF32x4SConvertI32x4: 3943 return EmitUnOp<kS128, kS128, kF32>( 3944 &LiftoffAssembler::emit_f32x4_sconvert_i32x4); 3945 case wasm::kExprF32x4UConvertI32x4: 3946 return EmitUnOp<kS128, kS128, kF32>( 3947 &LiftoffAssembler::emit_f32x4_uconvert_i32x4); 3948 case wasm::kExprI8x16SConvertI16x8: 3949 return EmitBinOp<kS128, kS128>( 3950 &LiftoffAssembler::emit_i8x16_sconvert_i16x8); 3951 case wasm::kExprI8x16UConvertI16x8: 3952 return EmitBinOp<kS128, kS128>( 3953 &LiftoffAssembler::emit_i8x16_uconvert_i16x8); 3954 case wasm::kExprI16x8SConvertI32x4: 3955 return EmitBinOp<kS128, kS128>( 3956 &LiftoffAssembler::emit_i16x8_sconvert_i32x4); 3957 case wasm::kExprI16x8UConvertI32x4: 3958 return EmitBinOp<kS128, kS128>( 3959 &LiftoffAssembler::emit_i16x8_uconvert_i32x4); 3960 case wasm::kExprI16x8SConvertI8x16Low: 3961 return EmitUnOp<kS128, kS128>( 3962 &LiftoffAssembler::emit_i16x8_sconvert_i8x16_low); 3963 case wasm::kExprI16x8SConvertI8x16High: 3964 return EmitUnOp<kS128, kS128>( 3965 &LiftoffAssembler::emit_i16x8_sconvert_i8x16_high); 3966 case wasm::kExprI16x8UConvertI8x16Low: 3967 return EmitUnOp<kS128, kS128>( 3968 &LiftoffAssembler::emit_i16x8_uconvert_i8x16_low); 3969 case wasm::kExprI16x8UConvertI8x16High: 3970 return EmitUnOp<kS128, kS128>( 3971 &LiftoffAssembler::emit_i16x8_uconvert_i8x16_high); 3972 case wasm::kExprI32x4SConvertI16x8Low: 3973 return EmitUnOp<kS128, kS128>( 3974 &LiftoffAssembler::emit_i32x4_sconvert_i16x8_low); 3975 case wasm::kExprI32x4SConvertI16x8High: 3976 return EmitUnOp<kS128, kS128>( 3977 &LiftoffAssembler::emit_i32x4_sconvert_i16x8_high); 3978 case wasm::kExprI32x4UConvertI16x8Low: 3979 return EmitUnOp<kS128, kS128>( 3980 &LiftoffAssembler::emit_i32x4_uconvert_i16x8_low); 3981 case wasm::kExprI32x4UConvertI16x8High: 3982 return EmitUnOp<kS128, kS128>( 3983 &LiftoffAssembler::emit_i32x4_uconvert_i16x8_high); 3984 case wasm::kExprS128AndNot: 3985 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_and_not); 3986 case wasm::kExprI8x16RoundingAverageU: 3987 return EmitBinOp<kS128, kS128>( 3988 &LiftoffAssembler::emit_i8x16_rounding_average_u); 3989 case wasm::kExprI16x8RoundingAverageU: 3990 return EmitBinOp<kS128, kS128>( 3991 &LiftoffAssembler::emit_i16x8_rounding_average_u); 3992 case wasm::kExprI8x16Abs: 3993 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_abs); 3994 case wasm::kExprI16x8Abs: 3995 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_abs); 3996 case wasm::kExprI32x4Abs: 3997 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_abs); 3998 case wasm::kExprI64x2Abs: 3999 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_abs); 4000 case wasm::kExprF64x2ConvertLowI32x4S: 4001 return EmitUnOp<kS128, kS128, kF64>( 4002 &LiftoffAssembler::emit_f64x2_convert_low_i32x4_s); 4003 case wasm::kExprF64x2ConvertLowI32x4U: 4004 return EmitUnOp<kS128, kS128, kF64>( 4005 &LiftoffAssembler::emit_f64x2_convert_low_i32x4_u); 4006 case wasm::kExprF64x2PromoteLowF32x4: 4007 return EmitUnOp<kS128, kS128, kF64>( 4008 &LiftoffAssembler::emit_f64x2_promote_low_f32x4); 4009 case wasm::kExprF32x4DemoteF64x2Zero: 4010 return EmitUnOp<kS128, kS128, kF32>( 4011 &LiftoffAssembler::emit_f32x4_demote_f64x2_zero); 4012 case wasm::kExprI32x4TruncSatF64x2SZero: 4013 return EmitUnOp<kS128, kS128>( 4014 &LiftoffAssembler::emit_i32x4_trunc_sat_f64x2_s_zero); 4015 case wasm::kExprI32x4TruncSatF64x2UZero: 4016 return EmitUnOp<kS128, kS128>( 4017 &LiftoffAssembler::emit_i32x4_trunc_sat_f64x2_u_zero); 4018 default: 4019 unsupported(decoder, kSimd, "simd"); 4020 } 4021 } 4022 4023 template <ValueKind src_kind, ValueKind result_kind, typename EmitFn> 4024 void EmitSimdExtractLaneOp(EmitFn fn, 4025 const SimdLaneImmediate<validate>& imm) { 4026 static constexpr RegClass src_rc = reg_class_for(src_kind); 4027 static constexpr RegClass result_rc = reg_class_for(result_kind); 4028 LiftoffRegister lhs = __ PopToRegister(); 4029 LiftoffRegister dst = src_rc == result_rc 4030 ? __ GetUnusedRegister(result_rc, {lhs}, {}) 4031 : __ GetUnusedRegister(result_rc, {}); 4032 fn(dst, lhs, imm.lane); 4033 __ PushRegister(result_kind, dst); 4034 } 4035 4036 template <ValueKind src2_kind, typename EmitFn> 4037 void EmitSimdReplaceLaneOp(EmitFn fn, 4038 const SimdLaneImmediate<validate>& imm) { 4039 static constexpr RegClass src1_rc = reg_class_for(kS128); 4040 static constexpr RegClass src2_rc = reg_class_for(src2_kind); 4041 static constexpr RegClass result_rc = reg_class_for(kS128); 4042 // On backends which need fp pair, src1_rc and result_rc end up being 4043 // kFpRegPair, which is != kFpReg, but we still want to pin src2 when it is 4044 // kFpReg, since it can overlap with those pairs. 4045 static constexpr bool pin_src2 = kNeedS128RegPair && src2_rc == kFpReg; 4046 4047 // Does not work for arm 4048 LiftoffRegister src2 = __ PopToRegister(); 4049 LiftoffRegister src1 = (src1_rc == src2_rc || pin_src2) 4050 ? __ PopToRegister(LiftoffRegList{src2}) 4051 : __ 4052 PopToRegister(); 4053 LiftoffRegister dst = 4054 (src2_rc == result_rc || pin_src2) 4055 ? __ GetUnusedRegister(result_rc, {src1}, LiftoffRegList{src2}) 4056 : __ GetUnusedRegister(result_rc, {src1}, {}); 4057 fn(dst, src1, src2, imm.lane); 4058 __ PushRegister(kS128, dst); 4059 } 4060 4061 void SimdLaneOp(FullDecoder* decoder, WasmOpcode opcode, 4062 const SimdLaneImmediate<validate>& imm, 4063 const base::Vector<Value> inputs, Value* result) { 4064 if (!CpuFeatures::SupportsWasmSimd128()) { 4065 return unsupported(decoder, kSimd, "simd"); 4066 } 4067 switch (opcode) { 4068#define CASE_SIMD_EXTRACT_LANE_OP(opcode, kind, fn) \ 4069 case wasm::kExpr##opcode: \ 4070 EmitSimdExtractLaneOp<kS128, k##kind>( \ 4071 [=](LiftoffRegister dst, LiftoffRegister lhs, uint8_t imm_lane_idx) { \ 4072 __ emit_##fn(dst, lhs, imm_lane_idx); \ 4073 }, \ 4074 imm); \ 4075 break; 4076 CASE_SIMD_EXTRACT_LANE_OP(I8x16ExtractLaneS, I32, i8x16_extract_lane_s) 4077 CASE_SIMD_EXTRACT_LANE_OP(I8x16ExtractLaneU, I32, i8x16_extract_lane_u) 4078 CASE_SIMD_EXTRACT_LANE_OP(I16x8ExtractLaneS, I32, i16x8_extract_lane_s) 4079 CASE_SIMD_EXTRACT_LANE_OP(I16x8ExtractLaneU, I32, i16x8_extract_lane_u) 4080 CASE_SIMD_EXTRACT_LANE_OP(I32x4ExtractLane, I32, i32x4_extract_lane) 4081 CASE_SIMD_EXTRACT_LANE_OP(I64x2ExtractLane, I64, i64x2_extract_lane) 4082 CASE_SIMD_EXTRACT_LANE_OP(F32x4ExtractLane, F32, f32x4_extract_lane) 4083 CASE_SIMD_EXTRACT_LANE_OP(F64x2ExtractLane, F64, f64x2_extract_lane) 4084#undef CASE_SIMD_EXTRACT_LANE_OP 4085#define CASE_SIMD_REPLACE_LANE_OP(opcode, kind, fn) \ 4086 case wasm::kExpr##opcode: \ 4087 EmitSimdReplaceLaneOp<k##kind>( \ 4088 [=](LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, \ 4089 uint8_t imm_lane_idx) { \ 4090 __ emit_##fn(dst, src1, src2, imm_lane_idx); \ 4091 }, \ 4092 imm); \ 4093 break; 4094 CASE_SIMD_REPLACE_LANE_OP(I8x16ReplaceLane, I32, i8x16_replace_lane) 4095 CASE_SIMD_REPLACE_LANE_OP(I16x8ReplaceLane, I32, i16x8_replace_lane) 4096 CASE_SIMD_REPLACE_LANE_OP(I32x4ReplaceLane, I32, i32x4_replace_lane) 4097 CASE_SIMD_REPLACE_LANE_OP(I64x2ReplaceLane, I64, i64x2_replace_lane) 4098 CASE_SIMD_REPLACE_LANE_OP(F32x4ReplaceLane, F32, f32x4_replace_lane) 4099 CASE_SIMD_REPLACE_LANE_OP(F64x2ReplaceLane, F64, f64x2_replace_lane) 4100#undef CASE_SIMD_REPLACE_LANE_OP 4101 default: 4102 unsupported(decoder, kSimd, "simd"); 4103 } 4104 } 4105 4106 void S128Const(FullDecoder* decoder, const Simd128Immediate<validate>& imm, 4107 Value* result) { 4108 if (!CpuFeatures::SupportsWasmSimd128()) { 4109 return unsupported(decoder, kSimd, "simd"); 4110 } 4111 constexpr RegClass result_rc = reg_class_for(kS128); 4112 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {}); 4113 bool all_zeroes = std::all_of(std::begin(imm.value), std::end(imm.value), 4114 [](uint8_t v) { return v == 0; }); 4115 bool all_ones = std::all_of(std::begin(imm.value), std::end(imm.value), 4116 [](uint8_t v) { return v == 0xff; }); 4117 if (all_zeroes) { 4118 __ LiftoffAssembler::emit_s128_xor(dst, dst, dst); 4119 } else if (all_ones) { 4120 // Any SIMD eq will work, i32x4 is efficient on all archs. 4121 __ LiftoffAssembler::emit_i32x4_eq(dst, dst, dst); 4122 } else { 4123 __ LiftoffAssembler::emit_s128_const(dst, imm.value); 4124 } 4125 __ PushRegister(kS128, dst); 4126 } 4127 4128 void Simd8x16ShuffleOp(FullDecoder* decoder, 4129 const Simd128Immediate<validate>& imm, 4130 const Value& input0, const Value& input1, 4131 Value* result) { 4132 if (!CpuFeatures::SupportsWasmSimd128()) { 4133 return unsupported(decoder, kSimd, "simd"); 4134 } 4135 static constexpr RegClass result_rc = reg_class_for(kS128); 4136 LiftoffRegister rhs = __ PopToRegister(); 4137 LiftoffRegister lhs = __ PopToRegister(LiftoffRegList{rhs}); 4138 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {lhs, rhs}, {}); 4139 4140 uint8_t shuffle[kSimd128Size]; 4141 memcpy(shuffle, imm.value, sizeof(shuffle)); 4142 bool is_swizzle; 4143 bool needs_swap; 4144 wasm::SimdShuffle::CanonicalizeShuffle(lhs == rhs, shuffle, &needs_swap, 4145 &is_swizzle); 4146 if (needs_swap) { 4147 std::swap(lhs, rhs); 4148 } 4149 __ LiftoffAssembler::emit_i8x16_shuffle(dst, lhs, rhs, shuffle, is_swizzle); 4150 __ PushRegister(kS128, dst); 4151 } 4152 4153 void ToSmi(Register reg) { 4154 if (COMPRESS_POINTERS_BOOL || kSystemPointerSize == 4) { 4155 __ emit_i32_shli(reg, reg, kSmiShiftSize + kSmiTagSize); 4156 } else { 4157 __ emit_i64_shli(LiftoffRegister{reg}, LiftoffRegister{reg}, 4158 kSmiShiftSize + kSmiTagSize); 4159 } 4160 } 4161 4162 void Store32BitExceptionValue(Register values_array, int* index_in_array, 4163 Register value, LiftoffRegList pinned) { 4164 LiftoffRegister tmp_reg = __ GetUnusedRegister(kGpReg, pinned); 4165 // Get the lower half word into tmp_reg and extend to a Smi. 4166 --*index_in_array; 4167 __ emit_i32_andi(tmp_reg.gp(), value, 0xffff); 4168 ToSmi(tmp_reg.gp()); 4169 __ StoreTaggedPointer( 4170 values_array, no_reg, 4171 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index_in_array), 4172 tmp_reg, pinned, LiftoffAssembler::kSkipWriteBarrier); 4173 4174 // Get the upper half word into tmp_reg and extend to a Smi. 4175 --*index_in_array; 4176 __ emit_i32_shri(tmp_reg.gp(), value, 16); 4177 ToSmi(tmp_reg.gp()); 4178 __ StoreTaggedPointer( 4179 values_array, no_reg, 4180 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index_in_array), 4181 tmp_reg, pinned, LiftoffAssembler::kSkipWriteBarrier); 4182 } 4183 4184 void Store64BitExceptionValue(Register values_array, int* index_in_array, 4185 LiftoffRegister value, LiftoffRegList pinned) { 4186 if (kNeedI64RegPair) { 4187 Store32BitExceptionValue(values_array, index_in_array, value.low_gp(), 4188 pinned); 4189 Store32BitExceptionValue(values_array, index_in_array, value.high_gp(), 4190 pinned); 4191 } else { 4192 Store32BitExceptionValue(values_array, index_in_array, value.gp(), 4193 pinned); 4194 __ emit_i64_shri(value, value, 32); 4195 Store32BitExceptionValue(values_array, index_in_array, value.gp(), 4196 pinned); 4197 } 4198 } 4199 4200 void Load16BitExceptionValue(LiftoffRegister dst, 4201 LiftoffRegister values_array, uint32_t* index, 4202 LiftoffRegList pinned) { 4203 __ LoadSmiAsInt32( 4204 dst, values_array.gp(), 4205 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index), pinned); 4206 (*index)++; 4207 } 4208 4209 void Load32BitExceptionValue(Register dst, LiftoffRegister values_array, 4210 uint32_t* index, LiftoffRegList pinned) { 4211 LiftoffRegister upper = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4212 Load16BitExceptionValue(upper, values_array, index, pinned); 4213 __ emit_i32_shli(upper.gp(), upper.gp(), 16); 4214 Load16BitExceptionValue(LiftoffRegister(dst), values_array, index, pinned); 4215 __ emit_i32_or(dst, upper.gp(), dst); 4216 } 4217 4218 void Load64BitExceptionValue(LiftoffRegister dst, 4219 LiftoffRegister values_array, uint32_t* index, 4220 LiftoffRegList pinned) { 4221 if (kNeedI64RegPair) { 4222 Load32BitExceptionValue(dst.high_gp(), values_array, index, pinned); 4223 Load32BitExceptionValue(dst.low_gp(), values_array, index, pinned); 4224 } else { 4225 Load16BitExceptionValue(dst, values_array, index, pinned); 4226 __ emit_i64_shli(dst, dst, 48); 4227 LiftoffRegister tmp_reg = 4228 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4229 Load16BitExceptionValue(tmp_reg, values_array, index, pinned); 4230 __ emit_i64_shli(tmp_reg, tmp_reg, 32); 4231 __ emit_i64_or(dst, tmp_reg, dst); 4232 Load16BitExceptionValue(tmp_reg, values_array, index, pinned); 4233 __ emit_i64_shli(tmp_reg, tmp_reg, 16); 4234 __ emit_i64_or(dst, tmp_reg, dst); 4235 Load16BitExceptionValue(tmp_reg, values_array, index, pinned); 4236 __ emit_i64_or(dst, tmp_reg, dst); 4237 } 4238 } 4239 4240 void StoreExceptionValue(ValueType type, Register values_array, 4241 int* index_in_array, LiftoffRegList pinned) { 4242 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 4243 switch (type.kind()) { 4244 case kI32: 4245 Store32BitExceptionValue(values_array, index_in_array, value.gp(), 4246 pinned); 4247 break; 4248 case kF32: { 4249 LiftoffRegister gp_reg = 4250 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4251 __ emit_type_conversion(kExprI32ReinterpretF32, gp_reg, value, nullptr); 4252 Store32BitExceptionValue(values_array, index_in_array, gp_reg.gp(), 4253 pinned); 4254 break; 4255 } 4256 case kI64: 4257 Store64BitExceptionValue(values_array, index_in_array, value, pinned); 4258 break; 4259 case kF64: { 4260 LiftoffRegister tmp_reg = 4261 pinned.set(__ GetUnusedRegister(reg_class_for(kI64), pinned)); 4262 __ emit_type_conversion(kExprI64ReinterpretF64, tmp_reg, value, 4263 nullptr); 4264 Store64BitExceptionValue(values_array, index_in_array, tmp_reg, pinned); 4265 break; 4266 } 4267 case kS128: { 4268 LiftoffRegister tmp_reg = 4269 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4270 for (int i : {3, 2, 1, 0}) { 4271 __ emit_i32x4_extract_lane(tmp_reg, value, i); 4272 Store32BitExceptionValue(values_array, index_in_array, tmp_reg.gp(), 4273 pinned); 4274 } 4275 break; 4276 } 4277 case wasm::kRef: 4278 case wasm::kOptRef: 4279 case wasm::kRtt: { 4280 --(*index_in_array); 4281 __ StoreTaggedPointer( 4282 values_array, no_reg, 4283 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( 4284 *index_in_array), 4285 value, pinned); 4286 break; 4287 } 4288 case wasm::kI8: 4289 case wasm::kI16: 4290 case wasm::kVoid: 4291 case wasm::kBottom: 4292 UNREACHABLE(); 4293 } 4294 } 4295 4296 void LoadExceptionValue(ValueKind kind, LiftoffRegister values_array, 4297 uint32_t* index, LiftoffRegList pinned) { 4298 RegClass rc = reg_class_for(kind); 4299 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); 4300 switch (kind) { 4301 case kI32: 4302 Load32BitExceptionValue(value.gp(), values_array, index, pinned); 4303 break; 4304 case kF32: { 4305 LiftoffRegister tmp_reg = 4306 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4307 Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned); 4308 __ emit_type_conversion(kExprF32ReinterpretI32, value, tmp_reg, 4309 nullptr); 4310 break; 4311 } 4312 case kI64: 4313 Load64BitExceptionValue(value, values_array, index, pinned); 4314 break; 4315 case kF64: { 4316 RegClass rc_i64 = reg_class_for(kI64); 4317 LiftoffRegister tmp_reg = 4318 pinned.set(__ GetUnusedRegister(rc_i64, pinned)); 4319 Load64BitExceptionValue(tmp_reg, values_array, index, pinned); 4320 __ emit_type_conversion(kExprF64ReinterpretI64, value, tmp_reg, 4321 nullptr); 4322 break; 4323 } 4324 case kS128: { 4325 LiftoffRegister tmp_reg = 4326 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4327 Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned); 4328 __ emit_i32x4_splat(value, tmp_reg); 4329 for (int lane : {1, 2, 3}) { 4330 Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned); 4331 __ emit_i32x4_replace_lane(value, value, tmp_reg, lane); 4332 } 4333 break; 4334 } 4335 case wasm::kRef: 4336 case wasm::kOptRef: 4337 case wasm::kRtt: { 4338 __ LoadTaggedPointer( 4339 value.gp(), values_array.gp(), no_reg, 4340 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(*index), 4341 pinned); 4342 (*index)++; 4343 break; 4344 } 4345 case wasm::kI8: 4346 case wasm::kI16: 4347 case wasm::kVoid: 4348 case wasm::kBottom: 4349 UNREACHABLE(); 4350 } 4351 __ PushRegister(kind, value); 4352 } 4353 4354 void GetExceptionValues(FullDecoder* decoder, 4355 LiftoffAssembler::VarState& exception_var, 4356 const WasmTag* tag) { 4357 LiftoffRegList pinned; 4358 CODE_COMMENT("get exception values"); 4359 LiftoffRegister values_array = GetExceptionProperty( 4360 exception_var, RootIndex::kwasm_exception_values_symbol); 4361 pinned.set(values_array); 4362 uint32_t index = 0; 4363 const WasmTagSig* sig = tag->sig; 4364 for (ValueType param : sig->parameters()) { 4365 LoadExceptionValue(param.kind(), values_array, &index, pinned); 4366 } 4367 DCHECK_EQ(index, WasmExceptionPackage::GetEncodedSize(tag)); 4368 } 4369 4370 void EmitLandingPad(FullDecoder* decoder, int handler_offset) { 4371 if (decoder->current_catch() == -1) return; 4372 MovableLabel handler; 4373 4374 // If we return from the throwing code normally, just skip over the handler. 4375 Label skip_handler; 4376 __ emit_jump(&skip_handler); 4377 4378 // Handler: merge into the catch state, and jump to the catch body. 4379 CODE_COMMENT("-- landing pad --"); 4380 __ bind(handler.get()); 4381 __ ExceptionHandler(); 4382 __ PushException(); 4383 handlers_.push_back({std::move(handler), handler_offset}); 4384 Control* current_try = 4385 decoder->control_at(decoder->control_depth_of_current_catch()); 4386 DCHECK_NOT_NULL(current_try->try_info); 4387 if (!current_try->try_info->catch_reached) { 4388 current_try->try_info->catch_state.InitMerge( 4389 *__ cache_state(), __ num_locals(), 1, 4390 current_try->stack_depth + current_try->num_exceptions); 4391 current_try->try_info->catch_reached = true; 4392 } 4393 __ MergeStackWith(current_try->try_info->catch_state, 1, 4394 LiftoffAssembler::kForwardJump); 4395 __ emit_jump(¤t_try->try_info->catch_label); 4396 4397 __ bind(&skip_handler); 4398 // Drop the exception. 4399 __ DropValues(1); 4400 } 4401 4402 void Throw(FullDecoder* decoder, const TagIndexImmediate<validate>& imm, 4403 const base::Vector<Value>& /* args */) { 4404 LiftoffRegList pinned; 4405 4406 // Load the encoded size in a register for the builtin call. 4407 int encoded_size = WasmExceptionPackage::GetEncodedSize(imm.tag); 4408 LiftoffRegister encoded_size_reg = 4409 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4410 __ LoadConstant(encoded_size_reg, WasmValue(encoded_size)); 4411 4412 // Call the WasmAllocateFixedArray builtin to create the values array. 4413 CallRuntimeStub(WasmCode::kWasmAllocateFixedArray, 4414 MakeSig::Returns(kPointerKind).Params(kPointerKind), 4415 {LiftoffAssembler::VarState{ 4416 kSmiKind, LiftoffRegister{encoded_size_reg}, 0}}, 4417 decoder->position()); 4418 MaybeOSR(); 4419 4420 // The FixedArray for the exception values is now in the first gp return 4421 // register. 4422 LiftoffRegister values_array{kReturnRegister0}; 4423 pinned.set(values_array); 4424 4425 // Now store the exception values in the FixedArray. Do this from last to 4426 // first value, such that we can just pop them from the value stack. 4427 CODE_COMMENT("fill values array"); 4428 int index = encoded_size; 4429 auto* sig = imm.tag->sig; 4430 for (size_t param_idx = sig->parameter_count(); param_idx > 0; 4431 --param_idx) { 4432 ValueType type = sig->GetParam(param_idx - 1); 4433 StoreExceptionValue(type, values_array.gp(), &index, pinned); 4434 } 4435 DCHECK_EQ(0, index); 4436 4437 // Load the exception tag. 4438 CODE_COMMENT("load exception tag"); 4439 LiftoffRegister exception_tag = 4440 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4441 LOAD_TAGGED_PTR_INSTANCE_FIELD(exception_tag.gp(), TagsTable, pinned); 4442 __ LoadTaggedPointer( 4443 exception_tag.gp(), exception_tag.gp(), no_reg, 4444 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index), {}); 4445 4446 // Finally, call WasmThrow. 4447 CallRuntimeStub(WasmCode::kWasmThrow, 4448 MakeSig::Params(kPointerKind, kPointerKind), 4449 {LiftoffAssembler::VarState{kPointerKind, exception_tag, 0}, 4450 LiftoffAssembler::VarState{kPointerKind, values_array, 0}}, 4451 decoder->position()); 4452 4453 int pc_offset = __ pc_offset(); 4454 MaybeOSR(); 4455 EmitLandingPad(decoder, pc_offset); 4456 } 4457 4458 void AtomicStoreMem(FullDecoder* decoder, StoreType type, 4459 const MemoryAccessImmediate<validate>& imm) { 4460 LiftoffRegList pinned; 4461 LiftoffRegister value = pinned.set(__ PopToRegister()); 4462 LiftoffRegister full_index = __ PopToRegister(pinned); 4463 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 4464 full_index, pinned, kDoForceCheck); 4465 if (index == no_reg) return; 4466 4467 pinned.set(index); 4468 AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); 4469 uintptr_t offset = imm.offset; 4470 CODE_COMMENT("atomic store to memory"); 4471 Register addr = pinned.set(GetMemoryStart(pinned)); 4472 LiftoffRegList outer_pinned; 4473 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) outer_pinned.set(index); 4474 __ AtomicStore(addr, index, offset, value, type, outer_pinned); 4475 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 4476 TraceMemoryOperation(true, type.mem_rep(), index, offset, 4477 decoder->position()); 4478 } 4479 } 4480 4481 void AtomicLoadMem(FullDecoder* decoder, LoadType type, 4482 const MemoryAccessImmediate<validate>& imm) { 4483 ValueKind kind = type.value_type().kind(); 4484 LiftoffRegister full_index = __ PopToRegister(); 4485 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 4486 full_index, {}, kDoForceCheck); 4487 if (index == no_reg) return; 4488 4489 LiftoffRegList pinned = {index}; 4490 AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); 4491 uintptr_t offset = imm.offset; 4492 CODE_COMMENT("atomic load from memory"); 4493 Register addr = pinned.set(GetMemoryStart(pinned)); 4494 RegClass rc = reg_class_for(kind); 4495 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned)); 4496 __ AtomicLoad(value, addr, index, offset, type, pinned); 4497 __ PushRegister(kind, value); 4498 4499 if (V8_UNLIKELY(FLAG_trace_wasm_memory)) { 4500 TraceMemoryOperation(false, type.mem_type().representation(), index, 4501 offset, decoder->position()); 4502 } 4503 } 4504 4505 void AtomicBinop(FullDecoder* decoder, StoreType type, 4506 const MemoryAccessImmediate<validate>& imm, 4507 void (LiftoffAssembler::*emit_fn)(Register, Register, 4508 uintptr_t, LiftoffRegister, 4509 LiftoffRegister, 4510 StoreType)) { 4511 ValueKind result_kind = type.value_type().kind(); 4512 LiftoffRegList pinned; 4513 LiftoffRegister value = pinned.set(__ PopToRegister()); 4514#ifdef V8_TARGET_ARCH_IA32 4515 // We have to reuse the value register as the result register so that we 4516 // don't run out of registers on ia32. For this we use the value register as 4517 // the result register if it has no other uses. Otherwise we allocate a new 4518 // register and let go of the value register to get spilled. 4519 LiftoffRegister result = value; 4520 if (__ cache_state()->is_used(value)) { 4521 result = pinned.set(__ GetUnusedRegister(value.reg_class(), pinned)); 4522 __ Move(result, value, result_kind); 4523 pinned.clear(value); 4524 value = result; 4525 } 4526#else 4527 LiftoffRegister result = 4528 pinned.set(__ GetUnusedRegister(value.reg_class(), pinned)); 4529#endif 4530 LiftoffRegister full_index = __ PopToRegister(pinned); 4531 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 4532 full_index, pinned, kDoForceCheck); 4533 if (index == no_reg) return; 4534 4535 pinned.set(index); 4536 AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); 4537 4538 CODE_COMMENT("atomic binop"); 4539 uintptr_t offset = imm.offset; 4540 Register addr = pinned.set(GetMemoryStart(pinned)); 4541 4542 (asm_.*emit_fn)(addr, index, offset, value, result, type); 4543 __ PushRegister(result_kind, result); 4544 } 4545 4546 void AtomicCompareExchange(FullDecoder* decoder, StoreType type, 4547 const MemoryAccessImmediate<validate>& imm) { 4548#ifdef V8_TARGET_ARCH_IA32 4549 // On ia32 we don't have enough registers to first pop all the values off 4550 // the stack and then start with the code generation. Instead we do the 4551 // complete address calculation first, so that the address only needs a 4552 // single register. Afterwards we load all remaining values into the 4553 // other registers. 4554 LiftoffRegister full_index = __ PeekToRegister(2, {}); 4555 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 4556 full_index, {}, kDoForceCheck); 4557 if (index == no_reg) return; 4558 LiftoffRegList pinned = {index}; 4559 AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); 4560 4561 uintptr_t offset = imm.offset; 4562 Register addr = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 4563 LOAD_INSTANCE_FIELD(addr, MemoryStart, kSystemPointerSize, pinned); 4564#ifdef V8_SANDBOXED_POINTERS 4565 __ DecodeSandboxedPointer(addr); 4566#endif 4567 __ emit_i32_add(addr, addr, index); 4568 pinned.clear(LiftoffRegister(index)); 4569 LiftoffRegister new_value = pinned.set(__ PopToRegister(pinned)); 4570 LiftoffRegister expected = pinned.set(__ PopToRegister(pinned)); 4571 4572 // Pop the index from the stack. 4573 __ DropValues(1); 4574 4575 LiftoffRegister result = expected; 4576 if (__ cache_state()->is_used(result)) __ SpillRegister(result); 4577 4578 // We already added the index to addr, so we can just pass no_reg to the 4579 // assembler now. 4580 __ AtomicCompareExchange(addr, no_reg, offset, expected, new_value, result, 4581 type); 4582 __ PushRegister(type.value_type().kind(), result); 4583 return; 4584#else 4585 ValueKind result_kind = type.value_type().kind(); 4586 LiftoffRegList pinned; 4587 LiftoffRegister new_value = pinned.set(__ PopToRegister()); 4588 LiftoffRegister expected = pinned.set(__ PopToRegister(pinned)); 4589 LiftoffRegister full_index = __ PopToRegister(pinned); 4590 Register index = BoundsCheckMem(decoder, type.size(), imm.offset, 4591 full_index, pinned, kDoForceCheck); 4592 if (index == no_reg) return; 4593 pinned.set(index); 4594 AlignmentCheckMem(decoder, type.size(), imm.offset, index, pinned); 4595 4596 uintptr_t offset = imm.offset; 4597 Register addr = pinned.set(GetMemoryStart(pinned)); 4598 LiftoffRegister result = 4599 pinned.set(__ GetUnusedRegister(reg_class_for(result_kind), pinned)); 4600 4601 __ AtomicCompareExchange(addr, index, offset, expected, new_value, result, 4602 type); 4603 __ PushRegister(result_kind, result); 4604#endif 4605 } 4606 4607 void CallRuntimeStub(WasmCode::RuntimeStubId stub_id, const ValueKindSig& sig, 4608 std::initializer_list<LiftoffAssembler::VarState> params, 4609 int position) { 4610 CODE_COMMENT( 4611 (std::string{"call builtin: "} + GetRuntimeStubName(stub_id)).c_str()); 4612 auto interface_descriptor = Builtins::CallInterfaceDescriptorFor( 4613 RuntimeStubIdToBuiltinName(stub_id)); 4614 auto* call_descriptor = compiler::Linkage::GetStubCallDescriptor( 4615 compilation_zone_, // zone 4616 interface_descriptor, // descriptor 4617 interface_descriptor.GetStackParameterCount(), // stack parameter count 4618 compiler::CallDescriptor::kNoFlags, // flags 4619 compiler::Operator::kNoProperties, // properties 4620 StubCallMode::kCallWasmRuntimeStub); // stub call mode 4621 4622 __ PrepareBuiltinCall(&sig, call_descriptor, params); 4623 if (position != kNoSourcePosition) { 4624 source_position_table_builder_.AddPosition( 4625 __ pc_offset(), SourcePosition(position), true); 4626 } 4627 __ CallRuntimeStub(stub_id); 4628 DefineSafepoint(); 4629 } 4630 4631 void AtomicWait(FullDecoder* decoder, ValueKind kind, 4632 const MemoryAccessImmediate<validate>& imm) { 4633 LiftoffRegister full_index = __ PeekToRegister(2, {}); 4634 Register index_reg = 4635 BoundsCheckMem(decoder, value_kind_size(kind), imm.offset, full_index, 4636 {}, kDoForceCheck); 4637 if (index_reg == no_reg) return; 4638 LiftoffRegList pinned = {index_reg}; 4639 AlignmentCheckMem(decoder, value_kind_size(kind), imm.offset, index_reg, 4640 pinned); 4641 4642 uintptr_t offset = imm.offset; 4643 Register index_plus_offset = 4644 __ cache_state()->is_used(LiftoffRegister(index_reg)) 4645 ? pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp() 4646 : index_reg; 4647 // TODO(clemensb): Skip this if memory is 64 bit. 4648 __ emit_ptrsize_zeroextend_i32(index_plus_offset, index_reg); 4649 if (offset) { 4650 __ emit_ptrsize_addi(index_plus_offset, index_plus_offset, offset); 4651 } 4652 4653 LiftoffAssembler::VarState timeout = 4654 __ cache_state()->stack_state.end()[-1]; 4655 LiftoffAssembler::VarState expected_value = 4656 __ cache_state()->stack_state.end()[-2]; 4657 LiftoffAssembler::VarState index = __ cache_state()->stack_state.end()[-3]; 4658 4659 // We have to set the correct register for the index. 4660 index.MakeRegister(LiftoffRegister(index_plus_offset)); 4661 4662 static constexpr WasmCode::RuntimeStubId kTargets[2][2]{ 4663 // 64 bit systems (kNeedI64RegPair == false): 4664 {WasmCode::kWasmI64AtomicWait64, WasmCode::kWasmI32AtomicWait64}, 4665 // 32 bit systems (kNeedI64RegPair == true): 4666 {WasmCode::kWasmI64AtomicWait32, WasmCode::kWasmI32AtomicWait32}}; 4667 auto target = kTargets[kNeedI64RegPair][kind == kI32]; 4668 4669 CallRuntimeStub(target, MakeSig::Params(kPointerKind, kind, kI64), 4670 {index, expected_value, timeout}, decoder->position()); 4671 // Pop parameters from the value stack. 4672 __ DropValues(3); 4673 4674 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 4675 4676 __ PushRegister(kI32, LiftoffRegister(kReturnRegister0)); 4677 } 4678 4679 void AtomicNotify(FullDecoder* decoder, 4680 const MemoryAccessImmediate<validate>& imm) { 4681 LiftoffRegister full_index = __ PeekToRegister(1, {}); 4682 Register index_reg = BoundsCheckMem(decoder, kInt32Size, imm.offset, 4683 full_index, {}, kDoForceCheck); 4684 if (index_reg == no_reg) return; 4685 LiftoffRegList pinned = {index_reg}; 4686 AlignmentCheckMem(decoder, kInt32Size, imm.offset, index_reg, pinned); 4687 4688 uintptr_t offset = imm.offset; 4689 Register index_plus_offset = 4690 __ cache_state()->is_used(LiftoffRegister(index_reg)) 4691 ? pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp() 4692 : index_reg; 4693 // TODO(clemensb): Skip this if memory is 64 bit. 4694 __ emit_ptrsize_zeroextend_i32(index_plus_offset, index_reg); 4695 if (offset) { 4696 __ emit_ptrsize_addi(index_plus_offset, index_plus_offset, offset); 4697 } 4698 4699 LiftoffAssembler::VarState count = __ cache_state()->stack_state.end()[-1]; 4700 LiftoffAssembler::VarState index = __ cache_state()->stack_state.end()[-2]; 4701 index.MakeRegister(LiftoffRegister(index_plus_offset)); 4702 4703 CallRuntimeStub(WasmCode::kWasmAtomicNotify, 4704 MakeSig::Returns(kI32).Params(kPointerKind, kI32), 4705 {index, count}, decoder->position()); 4706 // Pop parameters from the value stack. 4707 __ DropValues(2); 4708 4709 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 4710 4711 __ PushRegister(kI32, LiftoffRegister(kReturnRegister0)); 4712 } 4713 4714#define ATOMIC_STORE_LIST(V) \ 4715 V(I32AtomicStore, kI32Store) \ 4716 V(I64AtomicStore, kI64Store) \ 4717 V(I32AtomicStore8U, kI32Store8) \ 4718 V(I32AtomicStore16U, kI32Store16) \ 4719 V(I64AtomicStore8U, kI64Store8) \ 4720 V(I64AtomicStore16U, kI64Store16) \ 4721 V(I64AtomicStore32U, kI64Store32) 4722 4723#define ATOMIC_LOAD_LIST(V) \ 4724 V(I32AtomicLoad, kI32Load) \ 4725 V(I64AtomicLoad, kI64Load) \ 4726 V(I32AtomicLoad8U, kI32Load8U) \ 4727 V(I32AtomicLoad16U, kI32Load16U) \ 4728 V(I64AtomicLoad8U, kI64Load8U) \ 4729 V(I64AtomicLoad16U, kI64Load16U) \ 4730 V(I64AtomicLoad32U, kI64Load32U) 4731 4732#define ATOMIC_BINOP_INSTRUCTION_LIST(V) \ 4733 V(Add, I32AtomicAdd, kI32Store) \ 4734 V(Add, I64AtomicAdd, kI64Store) \ 4735 V(Add, I32AtomicAdd8U, kI32Store8) \ 4736 V(Add, I32AtomicAdd16U, kI32Store16) \ 4737 V(Add, I64AtomicAdd8U, kI64Store8) \ 4738 V(Add, I64AtomicAdd16U, kI64Store16) \ 4739 V(Add, I64AtomicAdd32U, kI64Store32) \ 4740 V(Sub, I32AtomicSub, kI32Store) \ 4741 V(Sub, I64AtomicSub, kI64Store) \ 4742 V(Sub, I32AtomicSub8U, kI32Store8) \ 4743 V(Sub, I32AtomicSub16U, kI32Store16) \ 4744 V(Sub, I64AtomicSub8U, kI64Store8) \ 4745 V(Sub, I64AtomicSub16U, kI64Store16) \ 4746 V(Sub, I64AtomicSub32U, kI64Store32) \ 4747 V(And, I32AtomicAnd, kI32Store) \ 4748 V(And, I64AtomicAnd, kI64Store) \ 4749 V(And, I32AtomicAnd8U, kI32Store8) \ 4750 V(And, I32AtomicAnd16U, kI32Store16) \ 4751 V(And, I64AtomicAnd8U, kI64Store8) \ 4752 V(And, I64AtomicAnd16U, kI64Store16) \ 4753 V(And, I64AtomicAnd32U, kI64Store32) \ 4754 V(Or, I32AtomicOr, kI32Store) \ 4755 V(Or, I64AtomicOr, kI64Store) \ 4756 V(Or, I32AtomicOr8U, kI32Store8) \ 4757 V(Or, I32AtomicOr16U, kI32Store16) \ 4758 V(Or, I64AtomicOr8U, kI64Store8) \ 4759 V(Or, I64AtomicOr16U, kI64Store16) \ 4760 V(Or, I64AtomicOr32U, kI64Store32) \ 4761 V(Xor, I32AtomicXor, kI32Store) \ 4762 V(Xor, I64AtomicXor, kI64Store) \ 4763 V(Xor, I32AtomicXor8U, kI32Store8) \ 4764 V(Xor, I32AtomicXor16U, kI32Store16) \ 4765 V(Xor, I64AtomicXor8U, kI64Store8) \ 4766 V(Xor, I64AtomicXor16U, kI64Store16) \ 4767 V(Xor, I64AtomicXor32U, kI64Store32) \ 4768 V(Exchange, I32AtomicExchange, kI32Store) \ 4769 V(Exchange, I64AtomicExchange, kI64Store) \ 4770 V(Exchange, I32AtomicExchange8U, kI32Store8) \ 4771 V(Exchange, I32AtomicExchange16U, kI32Store16) \ 4772 V(Exchange, I64AtomicExchange8U, kI64Store8) \ 4773 V(Exchange, I64AtomicExchange16U, kI64Store16) \ 4774 V(Exchange, I64AtomicExchange32U, kI64Store32) 4775 4776#define ATOMIC_COMPARE_EXCHANGE_LIST(V) \ 4777 V(I32AtomicCompareExchange, kI32Store) \ 4778 V(I64AtomicCompareExchange, kI64Store) \ 4779 V(I32AtomicCompareExchange8U, kI32Store8) \ 4780 V(I32AtomicCompareExchange16U, kI32Store16) \ 4781 V(I64AtomicCompareExchange8U, kI64Store8) \ 4782 V(I64AtomicCompareExchange16U, kI64Store16) \ 4783 V(I64AtomicCompareExchange32U, kI64Store32) 4784 4785 void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, 4786 base::Vector<Value> args, 4787 const MemoryAccessImmediate<validate>& imm, Value* result) { 4788 switch (opcode) { 4789#define ATOMIC_STORE_OP(name, type) \ 4790 case wasm::kExpr##name: \ 4791 AtomicStoreMem(decoder, StoreType::type, imm); \ 4792 break; 4793 4794 ATOMIC_STORE_LIST(ATOMIC_STORE_OP) 4795#undef ATOMIC_STORE_OP 4796 4797#define ATOMIC_LOAD_OP(name, type) \ 4798 case wasm::kExpr##name: \ 4799 AtomicLoadMem(decoder, LoadType::type, imm); \ 4800 break; 4801 4802 ATOMIC_LOAD_LIST(ATOMIC_LOAD_OP) 4803#undef ATOMIC_LOAD_OP 4804 4805#define ATOMIC_BINOP_OP(op, name, type) \ 4806 case wasm::kExpr##name: \ 4807 AtomicBinop(decoder, StoreType::type, imm, &LiftoffAssembler::Atomic##op); \ 4808 break; 4809 4810 ATOMIC_BINOP_INSTRUCTION_LIST(ATOMIC_BINOP_OP) 4811#undef ATOMIC_BINOP_OP 4812 4813#define ATOMIC_COMPARE_EXCHANGE_OP(name, type) \ 4814 case wasm::kExpr##name: \ 4815 AtomicCompareExchange(decoder, StoreType::type, imm); \ 4816 break; 4817 4818 ATOMIC_COMPARE_EXCHANGE_LIST(ATOMIC_COMPARE_EXCHANGE_OP) 4819#undef ATOMIC_COMPARE_EXCHANGE_OP 4820 4821 case kExprI32AtomicWait: 4822 AtomicWait(decoder, kI32, imm); 4823 break; 4824 case kExprI64AtomicWait: 4825 AtomicWait(decoder, kI64, imm); 4826 break; 4827 case kExprAtomicNotify: 4828 AtomicNotify(decoder, imm); 4829 break; 4830 default: 4831 unsupported(decoder, kAtomics, "atomicop"); 4832 } 4833 } 4834 4835#undef ATOMIC_STORE_LIST 4836#undef ATOMIC_LOAD_LIST 4837#undef ATOMIC_BINOP_INSTRUCTION_LIST 4838#undef ATOMIC_COMPARE_EXCHANGE_LIST 4839 4840 void AtomicFence(FullDecoder* decoder) { __ AtomicFence(); } 4841 4842 // Pop a memtype (i32 or i64 depending on {WasmModule::is_memory64}) to a 4843 // register, updating {*high_word} to contain the ORed combination of all 4844 // popped high words. Returns the ptrsized register holding the popped value. 4845 LiftoffRegister PopMemTypeToRegister(FullDecoder* decoder, 4846 Register* high_word, 4847 LiftoffRegList* pinned) { 4848 LiftoffRegister reg = __ PopToRegister(*pinned); 4849 LiftoffRegister intptr_reg = reg; 4850 // For memory32 on 64-bit hosts, zero-extend. 4851 if (kSystemPointerSize == kInt64Size && !env_->module->is_memory64) { 4852 // Only overwrite {reg} if it's not used otherwise. 4853 if (pinned->has(reg) || __ cache_state()->is_used(reg)) { 4854 intptr_reg = __ GetUnusedRegister(kGpReg, *pinned); 4855 } 4856 __ emit_u32_to_uintptr(intptr_reg.gp(), reg.gp()); 4857 } 4858 // For memory32 or memory64 on 64-bit, we are done here. 4859 if (kSystemPointerSize == kInt64Size || !env_->module->is_memory64) { 4860 pinned->set(intptr_reg); 4861 return intptr_reg; 4862 } 4863 4864 // For memory64 on 32-bit systems, combine all high words for a zero-check 4865 // and only use the low words afterwards. This keeps the register pressure 4866 // managable. 4867 DCHECK_GE(kMaxUInt32, env_->max_memory_size); 4868 pinned->set(reg.low()); 4869 if (*high_word == no_reg) { 4870 // Choose a register to hold the (combination of) high word(s). It cannot 4871 // be one of the pinned registers, and it cannot be used in the value 4872 // stack. 4873 *high_word = 4874 pinned->has(reg.high()) 4875 ? __ GetUnusedRegister(kGpReg, *pinned).gp() 4876 : __ GetUnusedRegister(kGpReg, {reg.high()}, *pinned).gp(); 4877 pinned->set(*high_word); 4878 if (*high_word != reg.high_gp()) { 4879 __ Move(*high_word, reg.high_gp(), kI32); 4880 } 4881 } else if (*high_word != reg.high_gp()) { 4882 // Combine the new high word into existing high words. 4883 __ emit_i32_or(*high_word, *high_word, reg.high_gp()); 4884 } 4885 return reg.low(); 4886 } 4887 4888 void MemoryInit(FullDecoder* decoder, 4889 const MemoryInitImmediate<validate>& imm, const Value&, 4890 const Value&, const Value&) { 4891 Register mem_offsets_high_word = no_reg; 4892 LiftoffRegList pinned; 4893 LiftoffRegister size = pinned.set(__ PopToRegister()); 4894 LiftoffRegister src = pinned.set(__ PopToRegister(pinned)); 4895 LiftoffRegister dst = 4896 PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned); 4897 4898 Register instance = __ cache_state()->cached_instance; 4899 if (instance == no_reg) { 4900 instance = __ GetUnusedRegister(kGpReg, pinned).gp(); 4901 __ LoadInstanceFromFrame(instance); 4902 } 4903 pinned.set(instance); 4904 4905 // Only allocate the OOB code now, so the state of the stack is reflected 4906 // correctly. 4907 Label* trap_label = 4908 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds); 4909 if (mem_offsets_high_word != no_reg) { 4910 // If any high word has bits set, jump to the OOB trap. 4911 __ emit_cond_jump(kNotEqualZero, trap_label, kI32, mem_offsets_high_word); 4912 pinned.clear(mem_offsets_high_word); 4913 } 4914 4915 LiftoffRegister segment_index = 4916 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4917 __ LoadConstant(segment_index, WasmValue(imm.data_segment.index)); 4918 4919 auto sig = MakeSig::Returns(kI32).Params(kPointerKind, kPointerKind, kI32, 4920 kI32, kI32); 4921 LiftoffRegister args[] = {LiftoffRegister(instance), dst, src, 4922 segment_index, size}; 4923 // We don't need the instance anymore after the call. We can use the 4924 // register for the result. 4925 LiftoffRegister result(instance); 4926 GenerateCCall(&result, &sig, kVoid, args, 4927 ExternalReference::wasm_memory_init()); 4928 __ emit_cond_jump(kEqual, trap_label, kI32, result.gp()); 4929 } 4930 4931 void DataDrop(FullDecoder* decoder, const IndexImmediate<validate>& imm) { 4932 LiftoffRegList pinned; 4933 4934 Register seg_size_array = 4935 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 4936 LOAD_INSTANCE_FIELD(seg_size_array, DataSegmentSizes, kSystemPointerSize, 4937 pinned); 4938 4939 LiftoffRegister seg_index = 4940 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4941 // Scale the seg_index for the array access. 4942 __ LoadConstant(seg_index, 4943 WasmValue(imm.index << value_kind_size_log2(kI32))); 4944 4945 // Set the length of the segment to '0' to drop it. 4946 LiftoffRegister null_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 4947 __ LoadConstant(null_reg, WasmValue(0)); 4948 __ Store(seg_size_array, seg_index.gp(), 0, null_reg, StoreType::kI32Store, 4949 pinned); 4950 } 4951 4952 void MemoryCopy(FullDecoder* decoder, 4953 const MemoryCopyImmediate<validate>& imm, const Value&, 4954 const Value&, const Value&) { 4955 Register mem_offsets_high_word = no_reg; 4956 LiftoffRegList pinned; 4957 LiftoffRegister size = pinned.set( 4958 PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); 4959 LiftoffRegister src = pinned.set( 4960 PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); 4961 LiftoffRegister dst = pinned.set( 4962 PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); 4963 4964 Register instance = __ cache_state()->cached_instance; 4965 if (instance == no_reg) { 4966 instance = __ GetUnusedRegister(kGpReg, pinned).gp(); 4967 __ LoadInstanceFromFrame(instance); 4968 } 4969 4970 // Only allocate the OOB code now, so the state of the stack is reflected 4971 // correctly. 4972 Label* trap_label = 4973 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds); 4974 if (mem_offsets_high_word != no_reg) { 4975 // If any high word has bits set, jump to the OOB trap. 4976 __ emit_cond_jump(kNotEqualZero, trap_label, kI32, mem_offsets_high_word); 4977 } 4978 4979 auto sig = MakeSig::Returns(kI32).Params(kPointerKind, kPointerKind, 4980 kPointerKind, kPointerKind); 4981 LiftoffRegister args[] = {LiftoffRegister(instance), dst, src, size}; 4982 // We don't need the instance anymore after the call. We can use the 4983 // register for the result. 4984 LiftoffRegister result(instance); 4985 GenerateCCall(&result, &sig, kVoid, args, 4986 ExternalReference::wasm_memory_copy()); 4987 __ emit_cond_jump(kEqual, trap_label, kI32, result.gp()); 4988 } 4989 4990 void MemoryFill(FullDecoder* decoder, 4991 const MemoryIndexImmediate<validate>& imm, const Value&, 4992 const Value&, const Value&) { 4993 Register mem_offsets_high_word = no_reg; 4994 LiftoffRegList pinned; 4995 LiftoffRegister size = pinned.set( 4996 PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); 4997 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 4998 LiftoffRegister dst = pinned.set( 4999 PopMemTypeToRegister(decoder, &mem_offsets_high_word, &pinned)); 5000 5001 Register instance = __ cache_state()->cached_instance; 5002 if (instance == no_reg) { 5003 instance = __ GetUnusedRegister(kGpReg, pinned).gp(); 5004 __ LoadInstanceFromFrame(instance); 5005 } 5006 5007 // Only allocate the OOB code now, so the state of the stack is reflected 5008 // correctly. 5009 Label* trap_label = 5010 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapMemOutOfBounds); 5011 if (mem_offsets_high_word != no_reg) { 5012 // If any high word has bits set, jump to the OOB trap. 5013 __ emit_cond_jump(kNotEqualZero, trap_label, kI32, mem_offsets_high_word); 5014 } 5015 5016 auto sig = MakeSig::Returns(kI32).Params(kPointerKind, kPointerKind, kI32, 5017 kPointerKind); 5018 LiftoffRegister args[] = {LiftoffRegister(instance), dst, value, size}; 5019 // We don't need the instance anymore after the call. We can use the 5020 // register for the result. 5021 LiftoffRegister result(instance); 5022 GenerateCCall(&result, &sig, kVoid, args, 5023 ExternalReference::wasm_memory_fill()); 5024 __ emit_cond_jump(kEqual, trap_label, kI32, result.gp()); 5025 } 5026 5027 void LoadSmi(LiftoffRegister reg, int value) { 5028 Address smi_value = Smi::FromInt(value).ptr(); 5029 using smi_type = std::conditional_t<kSmiKind == kI32, int32_t, int64_t>; 5030 __ LoadConstant(reg, WasmValue{static_cast<smi_type>(smi_value)}); 5031 } 5032 5033 void TableInit(FullDecoder* decoder, const TableInitImmediate<validate>& imm, 5034 base::Vector<Value> args) { 5035 LiftoffRegList pinned; 5036 LiftoffRegister table_index_reg = 5037 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5038 5039 LoadSmi(table_index_reg, imm.table.index); 5040 LiftoffAssembler::VarState table_index(kPointerKind, table_index_reg, 0); 5041 5042 LiftoffRegister segment_index_reg = 5043 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5044 LoadSmi(segment_index_reg, imm.element_segment.index); 5045 LiftoffAssembler::VarState segment_index(kPointerKind, segment_index_reg, 5046 0); 5047 5048 LiftoffAssembler::VarState size = __ cache_state()->stack_state.end()[-1]; 5049 LiftoffAssembler::VarState src = __ cache_state()->stack_state.end()[-2]; 5050 LiftoffAssembler::VarState dst = __ cache_state()->stack_state.end()[-3]; 5051 5052 CallRuntimeStub(WasmCode::kWasmTableInit, 5053 MakeSig::Params(kI32, kI32, kI32, kSmiKind, kSmiKind), 5054 {dst, src, size, table_index, segment_index}, 5055 decoder->position()); 5056 5057 // Pop parameters from the value stack. 5058 __ cache_state()->stack_state.pop_back(3); 5059 5060 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 5061 } 5062 5063 void ElemDrop(FullDecoder* decoder, const IndexImmediate<validate>& imm) { 5064 LiftoffRegList pinned; 5065 Register dropped_elem_segments = 5066 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 5067 LOAD_INSTANCE_FIELD(dropped_elem_segments, DroppedElemSegments, 5068 kSystemPointerSize, pinned); 5069 5070 LiftoffRegister seg_index = 5071 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5072 __ LoadConstant(seg_index, WasmValue(imm.index)); 5073 5074 // Mark the segment as dropped by setting its value in the dropped 5075 // segments list to 1. 5076 LiftoffRegister one_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5077 __ LoadConstant(one_reg, WasmValue(1)); 5078 __ Store(dropped_elem_segments, seg_index.gp(), 0, one_reg, 5079 StoreType::kI32Store8, pinned); 5080 } 5081 5082 void TableCopy(FullDecoder* decoder, const TableCopyImmediate<validate>& imm, 5083 base::Vector<Value> args) { 5084 LiftoffRegList pinned; 5085 5086 LiftoffRegister table_dst_index_reg = 5087 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5088 LoadSmi(table_dst_index_reg, imm.table_dst.index); 5089 LiftoffAssembler::VarState table_dst_index(kPointerKind, 5090 table_dst_index_reg, 0); 5091 5092 LiftoffRegister table_src_index_reg = 5093 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5094 LoadSmi(table_src_index_reg, imm.table_src.index); 5095 LiftoffAssembler::VarState table_src_index(kPointerKind, 5096 table_src_index_reg, 0); 5097 5098 LiftoffAssembler::VarState size = __ cache_state()->stack_state.end()[-1]; 5099 LiftoffAssembler::VarState src = __ cache_state()->stack_state.end()[-2]; 5100 LiftoffAssembler::VarState dst = __ cache_state()->stack_state.end()[-3]; 5101 5102 CallRuntimeStub(WasmCode::kWasmTableCopy, 5103 MakeSig::Params(kI32, kI32, kI32, kSmiKind, kSmiKind), 5104 {dst, src, size, table_dst_index, table_src_index}, 5105 decoder->position()); 5106 5107 // Pop parameters from the value stack. 5108 __ cache_state()->stack_state.pop_back(3); 5109 5110 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 5111 } 5112 5113 void TableGrow(FullDecoder* decoder, const IndexImmediate<validate>& imm, 5114 const Value&, const Value&, Value* result) { 5115 LiftoffRegList pinned; 5116 5117 LiftoffRegister table_index_reg = 5118 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5119 LoadSmi(table_index_reg, imm.index); 5120 LiftoffAssembler::VarState table_index(kPointerKind, table_index_reg, 0); 5121 5122 LiftoffAssembler::VarState delta = __ cache_state()->stack_state.end()[-1]; 5123 LiftoffAssembler::VarState value = __ cache_state()->stack_state.end()[-2]; 5124 5125 CallRuntimeStub( 5126 WasmCode::kWasmTableGrow, 5127 MakeSig::Returns(kSmiKind).Params(kSmiKind, kI32, kTaggedKind), 5128 {table_index, delta, value}, decoder->position()); 5129 5130 // Pop parameters from the value stack. 5131 __ cache_state()->stack_state.pop_back(2); 5132 5133 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 5134 __ SmiToInt32(kReturnRegister0); 5135 __ PushRegister(kI32, LiftoffRegister(kReturnRegister0)); 5136 } 5137 5138 void TableSize(FullDecoder* decoder, const IndexImmediate<validate>& imm, 5139 Value*) { 5140 // We have to look up instance->tables[table_index].length. 5141 5142 LiftoffRegList pinned; 5143 // Get the number of calls array address. 5144 Register tables = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 5145 LOAD_TAGGED_PTR_INSTANCE_FIELD(tables, Tables, pinned); 5146 5147 Register table = tables; 5148 __ LoadTaggedPointer( 5149 table, tables, no_reg, 5150 ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index), pinned); 5151 5152 int length_field_size = WasmTableObject::kCurrentLengthOffsetEnd - 5153 WasmTableObject::kCurrentLengthOffset + 1; 5154 5155 Register result = table; 5156 __ Load(LiftoffRegister(result), table, no_reg, 5157 wasm::ObjectAccess::ToTagged(WasmTableObject::kCurrentLengthOffset), 5158 length_field_size == 4 ? LoadType::kI32Load : LoadType::kI64Load, 5159 pinned); 5160 5161 __ SmiUntag(result); 5162 __ PushRegister(kI32, LiftoffRegister(result)); 5163 } 5164 5165 void TableFill(FullDecoder* decoder, const IndexImmediate<validate>& imm, 5166 const Value&, const Value&, const Value&) { 5167 LiftoffRegList pinned; 5168 5169 LiftoffRegister table_index_reg = 5170 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5171 LoadSmi(table_index_reg, imm.index); 5172 LiftoffAssembler::VarState table_index(kPointerKind, table_index_reg, 0); 5173 5174 LiftoffAssembler::VarState count = __ cache_state()->stack_state.end()[-1]; 5175 LiftoffAssembler::VarState value = __ cache_state()->stack_state.end()[-2]; 5176 LiftoffAssembler::VarState start = __ cache_state()->stack_state.end()[-3]; 5177 5178 CallRuntimeStub(WasmCode::kWasmTableFill, 5179 MakeSig::Params(kSmiKind, kI32, kI32, kTaggedKind), 5180 {table_index, start, count, value}, decoder->position()); 5181 5182 // Pop parameters from the value stack. 5183 __ cache_state()->stack_state.pop_back(3); 5184 5185 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 5186 } 5187 5188 void StructNew(FullDecoder* decoder, 5189 const StructIndexImmediate<validate>& imm, const Value& rtt, 5190 bool initial_values_on_stack) { 5191 LiftoffAssembler::VarState rtt_value = 5192 __ cache_state()->stack_state.end()[-1]; 5193 CallRuntimeStub(WasmCode::kWasmAllocateStructWithRtt, 5194 MakeSig::Returns(kRef).Params(rtt.type.kind()), {rtt_value}, 5195 decoder->position()); 5196 // Drop the RTT. 5197 __ cache_state()->stack_state.pop_back(1); 5198 5199 LiftoffRegister obj(kReturnRegister0); 5200 LiftoffRegList pinned = {obj}; 5201 for (uint32_t i = imm.struct_type->field_count(); i > 0;) { 5202 i--; 5203 int offset = StructFieldOffset(imm.struct_type, i); 5204 ValueKind field_kind = imm.struct_type->field(i).kind(); 5205 LiftoffRegister value = initial_values_on_stack 5206 ? pinned.set(__ PopToRegister(pinned)) 5207 : pinned.set(__ GetUnusedRegister( 5208 reg_class_for(field_kind), pinned)); 5209 if (!initial_values_on_stack) { 5210 if (!CheckSupportedType(decoder, field_kind, "default value")) return; 5211 SetDefaultValue(value, field_kind, pinned); 5212 } 5213 StoreObjectField(obj.gp(), no_reg, offset, value, pinned, field_kind); 5214 pinned.clear(value); 5215 } 5216 // If this assert fails then initialization of padding field might be 5217 // necessary. 5218 static_assert(Heap::kMinObjectSizeInTaggedWords == 2 && 5219 WasmStruct::kHeaderSize == 2 * kTaggedSize, 5220 "empty struct might require initialization of padding field"); 5221 __ PushRegister(kRef, obj); 5222 } 5223 5224 void StructNewWithRtt(FullDecoder* decoder, 5225 const StructIndexImmediate<validate>& imm, 5226 const Value& rtt, const Value args[], Value* result) { 5227 StructNew(decoder, imm, rtt, true); 5228 } 5229 5230 void StructNewDefault(FullDecoder* decoder, 5231 const StructIndexImmediate<validate>& imm, 5232 const Value& rtt, Value* result) { 5233 StructNew(decoder, imm, rtt, false); 5234 } 5235 5236 void StructGet(FullDecoder* decoder, const Value& struct_obj, 5237 const FieldImmediate<validate>& field, bool is_signed, 5238 Value* result) { 5239 const StructType* struct_type = field.struct_imm.struct_type; 5240 ValueKind field_kind = struct_type->field(field.field_imm.index).kind(); 5241 if (!CheckSupportedType(decoder, field_kind, "field load")) return; 5242 int offset = StructFieldOffset(struct_type, field.field_imm.index); 5243 LiftoffRegList pinned; 5244 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); 5245 MaybeEmitNullCheck(decoder, obj.gp(), pinned, struct_obj.type); 5246 LiftoffRegister value = 5247 __ GetUnusedRegister(reg_class_for(field_kind), pinned); 5248 LoadObjectField(value, obj.gp(), no_reg, offset, field_kind, is_signed, 5249 pinned); 5250 __ PushRegister(unpacked(field_kind), value); 5251 } 5252 5253 void StructSet(FullDecoder* decoder, const Value& struct_obj, 5254 const FieldImmediate<validate>& field, 5255 const Value& field_value) { 5256 const StructType* struct_type = field.struct_imm.struct_type; 5257 ValueKind field_kind = struct_type->field(field.field_imm.index).kind(); 5258 int offset = StructFieldOffset(struct_type, field.field_imm.index); 5259 LiftoffRegList pinned; 5260 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 5261 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); 5262 MaybeEmitNullCheck(decoder, obj.gp(), pinned, struct_obj.type); 5263 StoreObjectField(obj.gp(), no_reg, offset, value, pinned, field_kind); 5264 } 5265 5266 void ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate<validate>& imm, 5267 ValueKind rtt_kind, bool initial_value_on_stack) { 5268 // Max length check. 5269 { 5270 LiftoffRegister length = 5271 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], {}); 5272 Label* trap_label = 5273 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayTooLarge); 5274 __ emit_i32_cond_jumpi(kUnsignedGreaterThan, trap_label, length.gp(), 5275 WasmArray::MaxLength(imm.array_type)); 5276 } 5277 ValueKind elem_kind = imm.array_type->element_type().kind(); 5278 int elem_size = value_kind_size(elem_kind); 5279 // Allocate the array. 5280 { 5281 LiftoffRegister elem_size_reg = __ GetUnusedRegister(kGpReg, {}); 5282 LiftoffAssembler::VarState rtt_var = 5283 __ cache_state()->stack_state.end()[-1]; 5284 LiftoffAssembler::VarState length_var = 5285 __ cache_state()->stack_state.end()[-2]; 5286 __ LoadConstant(elem_size_reg, WasmValue(elem_size)); 5287 LiftoffAssembler::VarState elem_size_var(kI32, elem_size_reg, 0); 5288 5289 WasmCode::RuntimeStubId stub_id = 5290 initial_value_on_stack 5291 ? WasmCode::kWasmAllocateArray_Uninitialized 5292 : is_reference(elem_kind) ? WasmCode::kWasmAllocateArray_InitNull 5293 : WasmCode::kWasmAllocateArray_InitZero; 5294 CallRuntimeStub( 5295 stub_id, MakeSig::Returns(kRef).Params(rtt_kind, kI32, kI32), 5296 {rtt_var, length_var, elem_size_var}, decoder->position()); 5297 // Drop the RTT. 5298 __ cache_state()->stack_state.pop_back(1); 5299 } 5300 5301 LiftoffRegister obj(kReturnRegister0); 5302 if (initial_value_on_stack) { 5303 LiftoffRegList pinned = {obj}; 5304 LiftoffRegister length = pinned.set(__ PopToModifiableRegister(pinned)); 5305 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 5306 5307 // Initialize the array's elements. 5308 LiftoffRegister offset = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5309 __ LoadConstant( 5310 offset, 5311 WasmValue(wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize))); 5312 LiftoffRegister end_offset = length; 5313 if (value_kind_size_log2(elem_kind) != 0) { 5314 __ emit_i32_shli(end_offset.gp(), length.gp(), 5315 value_kind_size_log2(elem_kind)); 5316 } 5317 __ emit_i32_add(end_offset.gp(), end_offset.gp(), offset.gp()); 5318 Label loop, done; 5319 __ bind(&loop); 5320 __ emit_cond_jump(kUnsignedGreaterEqual, &done, kI32, offset.gp(), 5321 end_offset.gp()); 5322 StoreObjectField(obj.gp(), offset.gp(), 0, value, pinned, elem_kind); 5323 __ emit_i32_addi(offset.gp(), offset.gp(), elem_size); 5324 __ emit_jump(&loop); 5325 5326 __ bind(&done); 5327 } else { 5328 if (!CheckSupportedType(decoder, elem_kind, "default value")) return; 5329 // Drop the length. 5330 __ cache_state()->stack_state.pop_back(1); 5331 } 5332 __ PushRegister(kRef, obj); 5333 } 5334 5335 void ArrayNewWithRtt(FullDecoder* decoder, 5336 const ArrayIndexImmediate<validate>& imm, 5337 const Value& length_value, const Value& initial_value, 5338 const Value& rtt, Value* result) { 5339 ArrayNew(decoder, imm, rtt.type.kind(), true); 5340 } 5341 5342 void ArrayNewDefault(FullDecoder* decoder, 5343 const ArrayIndexImmediate<validate>& imm, 5344 const Value& length, const Value& rtt, Value* result) { 5345 ArrayNew(decoder, imm, rtt.type.kind(), false); 5346 } 5347 5348 void ArrayGet(FullDecoder* decoder, const Value& array_obj, 5349 const ArrayIndexImmediate<validate>& imm, 5350 const Value& index_val, bool is_signed, Value* result) { 5351 LiftoffRegList pinned; 5352 LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned)); 5353 LiftoffRegister array = pinned.set(__ PopToRegister(pinned)); 5354 MaybeEmitNullCheck(decoder, array.gp(), pinned, array_obj.type); 5355 BoundsCheckArray(decoder, array, index, pinned); 5356 ValueKind elem_kind = imm.array_type->element_type().kind(); 5357 if (!CheckSupportedType(decoder, elem_kind, "array load")) return; 5358 int elem_size_shift = value_kind_size_log2(elem_kind); 5359 if (elem_size_shift != 0) { 5360 __ emit_i32_shli(index.gp(), index.gp(), elem_size_shift); 5361 } 5362 LiftoffRegister value = 5363 __ GetUnusedRegister(reg_class_for(elem_kind), pinned); 5364 LoadObjectField(value, array.gp(), index.gp(), 5365 wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize), 5366 elem_kind, is_signed, pinned); 5367 __ PushRegister(unpacked(elem_kind), value); 5368 } 5369 5370 void ArraySet(FullDecoder* decoder, const Value& array_obj, 5371 const ArrayIndexImmediate<validate>& imm, 5372 const Value& index_val, const Value& value_val) { 5373 LiftoffRegList pinned; 5374 LiftoffRegister value = pinned.set(__ PopToRegister(pinned)); 5375 DCHECK_EQ(reg_class_for(imm.array_type->element_type().kind()), 5376 value.reg_class()); 5377 LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned)); 5378 LiftoffRegister array = pinned.set(__ PopToRegister(pinned)); 5379 MaybeEmitNullCheck(decoder, array.gp(), pinned, array_obj.type); 5380 BoundsCheckArray(decoder, array, index, pinned); 5381 ValueKind elem_kind = imm.array_type->element_type().kind(); 5382 int elem_size_shift = value_kind_size_log2(elem_kind); 5383 if (elem_size_shift != 0) { 5384 __ emit_i32_shli(index.gp(), index.gp(), elem_size_shift); 5385 } 5386 StoreObjectField(array.gp(), index.gp(), 5387 wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize), 5388 value, pinned, elem_kind); 5389 } 5390 5391 void ArrayLen(FullDecoder* decoder, const Value& array_obj, Value* result) { 5392 LiftoffRegList pinned; 5393 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); 5394 MaybeEmitNullCheck(decoder, obj.gp(), pinned, array_obj.type); 5395 LiftoffRegister len = __ GetUnusedRegister(kGpReg, pinned); 5396 int kLengthOffset = wasm::ObjectAccess::ToTagged(WasmArray::kLengthOffset); 5397 LoadObjectField(len, obj.gp(), no_reg, kLengthOffset, kI32, false, pinned); 5398 __ PushRegister(kI32, len); 5399 } 5400 5401 void ArrayCopy(FullDecoder* decoder, const Value& dst, const Value& dst_index, 5402 const Value& src, const Value& src_index, 5403 const Value& length) { 5404 // TODO(7748): Unify implementation with TF: Implement this with 5405 // GenerateCCall. Remove runtime function and builtin in wasm.tq. 5406 CallRuntimeStub(FLAG_experimental_wasm_skip_bounds_checks 5407 ? WasmCode::kWasmArrayCopy 5408 : WasmCode::kWasmArrayCopyWithChecks, 5409 MakeSig::Params(kI32, kI32, kI32, kOptRef, kOptRef), 5410 // Builtin parameter order: 5411 // [dst_index, src_index, length, dst, src]. 5412 {__ cache_state()->stack_state.end()[-4], 5413 __ cache_state()->stack_state.end()[-2], 5414 __ cache_state()->stack_state.end()[-1], 5415 __ cache_state()->stack_state.end()[-5], 5416 __ cache_state()->stack_state.end()[-3]}, 5417 decoder->position()); 5418 __ cache_state()->stack_state.pop_back(5); 5419 } 5420 5421 void ArrayInit(FullDecoder* decoder, const ArrayIndexImmediate<validate>& imm, 5422 const base::Vector<Value>& elements, const Value& rtt, 5423 Value* result) { 5424 ValueKind rtt_kind = rtt.type.kind(); 5425 ValueKind elem_kind = imm.array_type->element_type().kind(); 5426 // Allocate the array. 5427 { 5428 LiftoffRegList pinned; 5429 LiftoffRegister elem_size_reg = 5430 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5431 5432 __ LoadConstant(elem_size_reg, WasmValue(value_kind_size(elem_kind))); 5433 LiftoffAssembler::VarState elem_size_var(kI32, elem_size_reg, 0); 5434 5435 LiftoffRegister length_reg = 5436 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5437 __ LoadConstant(length_reg, 5438 WasmValue(static_cast<int32_t>(elements.size()))); 5439 LiftoffAssembler::VarState length_var(kI32, length_reg, 0); 5440 5441 LiftoffAssembler::VarState rtt_var = 5442 __ cache_state()->stack_state.end()[-1]; 5443 5444 CallRuntimeStub(WasmCode::kWasmAllocateArray_Uninitialized, 5445 MakeSig::Returns(kRef).Params(rtt_kind, kI32, kI32), 5446 {rtt_var, length_var, elem_size_var}, 5447 decoder->position()); 5448 // Drop the RTT. 5449 __ DropValues(1); 5450 } 5451 5452 // Initialize the array with stack arguments. 5453 LiftoffRegister array(kReturnRegister0); 5454 if (!CheckSupportedType(decoder, elem_kind, "array.init")) return; 5455 for (int i = static_cast<int>(elements.size()) - 1; i >= 0; i--) { 5456 LiftoffRegList pinned = {array}; 5457 LiftoffRegister element = pinned.set(__ PopToRegister(pinned)); 5458 LiftoffRegister offset_reg = 5459 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5460 __ LoadConstant(offset_reg, 5461 WasmValue(i << value_kind_size_log2(elem_kind))); 5462 StoreObjectField(array.gp(), offset_reg.gp(), 5463 wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize), 5464 element, pinned, elem_kind); 5465 } 5466 5467 // Push the array onto the stack. 5468 __ PushRegister(kRef, array); 5469 } 5470 5471 void ArrayInitFromData(FullDecoder* decoder, 5472 const ArrayIndexImmediate<validate>& array_imm, 5473 const IndexImmediate<validate>& data_segment, 5474 const Value& /* offset */, const Value& /* length */, 5475 const Value& /* rtt */, Value* /* result */) { 5476 LiftoffRegList pinned; 5477 LiftoffRegister data_segment_reg = 5478 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5479 __ LoadConstant(data_segment_reg, 5480 WasmValue(static_cast<int32_t>(data_segment.index))); 5481 LiftoffAssembler::VarState data_segment_var(kI32, data_segment_reg, 0); 5482 5483 CallRuntimeStub(WasmCode::kWasmArrayInitFromData, 5484 MakeSig::Returns(kRef).Params(kI32, kI32, kI32, kRtt), 5485 { 5486 data_segment_var, 5487 __ cache_state()->stack_state.end()[-3], // offset 5488 __ cache_state()->stack_state.end()[-2], // length 5489 __ cache_state()->stack_state.end()[-1] // rtt 5490 }, 5491 decoder->position()); 5492 5493 LiftoffRegister result(kReturnRegister0); 5494 // Reuse the data segment register for error handling. 5495 LiftoffRegister error_smi = data_segment_reg; 5496 LoadSmi(error_smi, kArrayInitFromDataArrayTooLargeErrorCode); 5497 Label* trap_label_array_too_large = 5498 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayTooLarge); 5499 __ emit_cond_jump(kEqual, trap_label_array_too_large, kRef, result.gp(), 5500 error_smi.gp()); 5501 LoadSmi(error_smi, kArrayInitFromDataSegmentOutOfBoundsErrorCode); 5502 Label* trap_label_segment_out_of_bounds = AddOutOfLineTrap( 5503 decoder, WasmCode::kThrowWasmTrapDataSegmentOutOfBounds); 5504 __ emit_cond_jump(kEqual, trap_label_segment_out_of_bounds, kRef, 5505 result.gp(), error_smi.gp()); 5506 5507 __ PushRegister(kRef, result); 5508 } 5509 5510 // 1 bit Smi tag, 31 bits Smi shift, 1 bit i31ref high-bit truncation. 5511 constexpr static int kI31To32BitSmiShift = 33; 5512 5513 void I31New(FullDecoder* decoder, const Value& input, Value* result) { 5514 LiftoffRegister src = __ PopToRegister(); 5515 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {}); 5516 if (SmiValuesAre31Bits()) { 5517 STATIC_ASSERT(kSmiTag == 0); 5518 __ emit_i32_shli(dst.gp(), src.gp(), kSmiTagSize); 5519 } else { 5520 DCHECK(SmiValuesAre32Bits()); 5521 __ emit_i64_shli(dst, src, kI31To32BitSmiShift); 5522 } 5523 __ PushRegister(kRef, dst); 5524 } 5525 5526 void I31GetS(FullDecoder* decoder, const Value& input, Value* result) { 5527 LiftoffRegister src = __ PopToRegister(); 5528 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {}); 5529 if (SmiValuesAre31Bits()) { 5530 __ emit_i32_sari(dst.gp(), src.gp(), kSmiTagSize); 5531 } else { 5532 DCHECK(SmiValuesAre32Bits()); 5533 __ emit_i64_sari(dst, src, kI31To32BitSmiShift); 5534 } 5535 __ PushRegister(kI32, dst); 5536 } 5537 5538 void I31GetU(FullDecoder* decoder, const Value& input, Value* result) { 5539 LiftoffRegister src = __ PopToRegister(); 5540 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {}); 5541 if (SmiValuesAre31Bits()) { 5542 __ emit_i32_shri(dst.gp(), src.gp(), kSmiTagSize); 5543 } else { 5544 DCHECK(SmiValuesAre32Bits()); 5545 __ emit_i64_shri(dst, src, kI31To32BitSmiShift); 5546 } 5547 __ PushRegister(kI32, dst); 5548 } 5549 5550 void RttCanon(FullDecoder* decoder, uint32_t type_index, Value* result) { 5551 LiftoffRegister rtt = __ GetUnusedRegister(kGpReg, {}); 5552 LOAD_TAGGED_PTR_INSTANCE_FIELD(rtt.gp(), ManagedObjectMaps, {}); 5553 __ LoadTaggedPointer( 5554 rtt.gp(), rtt.gp(), no_reg, 5555 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(type_index), {}); 5556 __ PushRegister(kRtt, rtt); 5557 } 5558 5559 enum NullSucceeds : bool { // -- 5560 kNullSucceeds = true, 5561 kNullFails = false 5562 }; 5563 5564 // Falls through on match (=successful type check). 5565 // Returns the register containing the object. 5566 LiftoffRegister SubtypeCheck(FullDecoder* decoder, const Value& obj, 5567 const Value& rtt, Label* no_match, 5568 NullSucceeds null_succeeds, 5569 LiftoffRegList pinned = {}, 5570 Register opt_scratch = no_reg) { 5571 Label match; 5572 LiftoffRegister rtt_reg = pinned.set(__ PopToRegister(pinned)); 5573 LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned)); 5574 5575 // Reserve all temporary registers up front, so that the cache state 5576 // tracking doesn't get confused by the following conditional jumps. 5577 LiftoffRegister tmp1 = 5578 opt_scratch != no_reg 5579 ? LiftoffRegister(opt_scratch) 5580 : pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5581 LiftoffRegister tmp2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 5582 if (obj.type.is_nullable()) { 5583 LoadNullValue(tmp1.gp(), pinned); 5584 __ emit_cond_jump(kEqual, null_succeeds ? &match : no_match, 5585 obj.type.kind(), obj_reg.gp(), tmp1.gp()); 5586 } 5587 5588 __ LoadMap(tmp1.gp(), obj_reg.gp()); 5589 // {tmp1} now holds the object's map. 5590 5591 // Check for rtt equality, and if not, check if the rtt is a struct/array 5592 // rtt. 5593 __ emit_cond_jump(kEqual, &match, rtt.type.kind(), tmp1.gp(), rtt_reg.gp()); 5594 5595 // Constant-time subtyping check: load exactly one candidate RTT from the 5596 // supertypes list. 5597 // Step 1: load the WasmTypeInfo into {tmp1}. 5598 constexpr int kTypeInfoOffset = wasm::ObjectAccess::ToTagged( 5599 Map::kConstructorOrBackPointerOrNativeContextOffset); 5600 __ LoadTaggedPointer(tmp1.gp(), tmp1.gp(), no_reg, kTypeInfoOffset, pinned); 5601 // Step 2: load the supertypes list into {tmp1}. 5602 constexpr int kSuperTypesOffset = 5603 wasm::ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset); 5604 __ LoadTaggedPointer(tmp1.gp(), tmp1.gp(), no_reg, kSuperTypesOffset, 5605 pinned); 5606 // Step 3: check the list's length if needed. 5607 uint32_t rtt_depth = 5608 GetSubtypingDepth(decoder->module_, rtt.type.ref_index()); 5609 if (rtt_depth >= kMinimumSupertypeArraySize) { 5610 LiftoffRegister list_length = tmp2; 5611 __ LoadFixedArrayLengthAsInt32(list_length, tmp1.gp(), pinned); 5612 __ emit_i32_cond_jumpi(kUnsignedLessEqual, no_match, list_length.gp(), 5613 rtt_depth); 5614 } 5615 // Step 4: load the candidate list slot into {tmp1}, and compare it. 5616 __ LoadTaggedPointer( 5617 tmp1.gp(), tmp1.gp(), no_reg, 5618 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray(rtt_depth), pinned); 5619 __ emit_cond_jump(kUnequal, no_match, rtt.type.kind(), tmp1.gp(), 5620 rtt_reg.gp()); 5621 5622 // Fall through to {match}. 5623 __ bind(&match); 5624 return obj_reg; 5625 } 5626 5627 void RefTest(FullDecoder* decoder, const Value& obj, const Value& rtt, 5628 Value* /* result_val */) { 5629 Label return_false, done; 5630 LiftoffRegList pinned; 5631 LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, {})); 5632 5633 SubtypeCheck(decoder, obj, rtt, &return_false, kNullFails, pinned, 5634 result.gp()); 5635 5636 __ LoadConstant(result, WasmValue(1)); 5637 // TODO(jkummerow): Emit near jumps on platforms where it's more efficient. 5638 __ emit_jump(&done); 5639 5640 __ bind(&return_false); 5641 __ LoadConstant(result, WasmValue(0)); 5642 __ bind(&done); 5643 __ PushRegister(kI32, result); 5644 } 5645 5646 void RefCast(FullDecoder* decoder, const Value& obj, const Value& rtt, 5647 Value* result) { 5648 if (FLAG_experimental_wasm_assume_ref_cast_succeeds) { 5649 // Just drop the rtt. 5650 __ DropValues(1); 5651 } else { 5652 Label* trap_label = 5653 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast); 5654 LiftoffRegister obj_reg = 5655 SubtypeCheck(decoder, obj, rtt, trap_label, kNullSucceeds); 5656 __ PushRegister(obj.type.kind(), obj_reg); 5657 } 5658 } 5659 5660 void BrOnCast(FullDecoder* decoder, const Value& obj, const Value& rtt, 5661 Value* /* result_on_branch */, uint32_t depth) { 5662 // Before branching, materialize all constants. This avoids repeatedly 5663 // materializing them for each conditional branch. 5664 if (depth != decoder->control_depth() - 1) { 5665 __ MaterializeMergedConstants( 5666 decoder->control_at(depth)->br_merge()->arity); 5667 } 5668 5669 Label cont_false; 5670 LiftoffRegister obj_reg = 5671 SubtypeCheck(decoder, obj, rtt, &cont_false, kNullFails); 5672 5673 __ PushRegister(rtt.type.is_bottom() ? kBottom : obj.type.kind(), obj_reg); 5674 BrOrRet(decoder, depth, 0); 5675 5676 __ bind(&cont_false); 5677 // Drop the branch's value, restore original value. 5678 Drop(decoder); 5679 __ PushRegister(obj.type.kind(), obj_reg); 5680 } 5681 5682 void BrOnCastFail(FullDecoder* decoder, const Value& obj, const Value& rtt, 5683 Value* /* result_on_fallthrough */, uint32_t depth) { 5684 // Before branching, materialize all constants. This avoids repeatedly 5685 // materializing them for each conditional branch. 5686 if (depth != decoder->control_depth() - 1) { 5687 __ MaterializeMergedConstants( 5688 decoder->control_at(depth)->br_merge()->arity); 5689 } 5690 5691 Label cont_branch, fallthrough; 5692 LiftoffRegister obj_reg = 5693 SubtypeCheck(decoder, obj, rtt, &cont_branch, kNullFails); 5694 __ PushRegister(obj.type.kind(), obj_reg); 5695 __ emit_jump(&fallthrough); 5696 5697 __ bind(&cont_branch); 5698 BrOrRet(decoder, depth, 0); 5699 5700 __ bind(&fallthrough); 5701 } 5702 5703 // Abstract type checkers. They all return the object register and fall 5704 // through to match. 5705 LiftoffRegister DataCheck(const Value& obj, Label* no_match, 5706 LiftoffRegList pinned, Register opt_scratch) { 5707 TypeCheckRegisters registers = 5708 TypeCheckPrelude(obj, no_match, pinned, opt_scratch); 5709 EmitDataRefCheck(registers.map_reg.gp(), no_match, registers.tmp_reg, 5710 pinned); 5711 return registers.obj_reg; 5712 } 5713 5714 LiftoffRegister ArrayCheck(const Value& obj, Label* no_match, 5715 LiftoffRegList pinned, Register opt_scratch) { 5716 TypeCheckRegisters registers = 5717 TypeCheckPrelude(obj, no_match, pinned, opt_scratch); 5718 __ Load(registers.map_reg, registers.map_reg.gp(), no_reg, 5719 wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset), 5720 LoadType::kI32Load16U, pinned); 5721 __ emit_i32_cond_jumpi(kUnequal, no_match, registers.map_reg.gp(), 5722 WASM_ARRAY_TYPE); 5723 return registers.obj_reg; 5724 } 5725 5726 LiftoffRegister FuncCheck(const Value& obj, Label* no_match, 5727 LiftoffRegList pinned, Register opt_scratch) { 5728 TypeCheckRegisters registers = 5729 TypeCheckPrelude(obj, no_match, pinned, opt_scratch); 5730 __ Load(registers.map_reg, registers.map_reg.gp(), no_reg, 5731 wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset), 5732 LoadType::kI32Load16U, pinned); 5733 __ emit_i32_cond_jumpi(kUnequal, no_match, registers.map_reg.gp(), 5734 WASM_INTERNAL_FUNCTION_TYPE); 5735 return registers.obj_reg; 5736 } 5737 5738 LiftoffRegister I31Check(const Value& object, Label* no_match, 5739 LiftoffRegList pinned, Register opt_scratch) { 5740 LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned)); 5741 5742 __ emit_smi_check(obj_reg.gp(), no_match, LiftoffAssembler::kJumpOnNotSmi); 5743 5744 return obj_reg; 5745 } 5746 5747 using TypeChecker = LiftoffRegister (LiftoffCompiler::*)( 5748 const Value& obj, Label* no_match, LiftoffRegList pinned, 5749 Register opt_scratch); 5750 5751 template <TypeChecker type_checker> 5752 void AbstractTypeCheck(const Value& object) { 5753 Label match, no_match, done; 5754 LiftoffRegList pinned; 5755 LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, {})); 5756 5757 (this->*type_checker)(object, &no_match, pinned, result.gp()); 5758 5759 __ bind(&match); 5760 __ LoadConstant(result, WasmValue(1)); 5761 // TODO(jkummerow): Emit near jumps on platforms where it's more efficient. 5762 __ emit_jump(&done); 5763 5764 __ bind(&no_match); 5765 __ LoadConstant(result, WasmValue(0)); 5766 __ bind(&done); 5767 __ PushRegister(kI32, result); 5768 } 5769 5770 void RefIsData(FullDecoder* /* decoder */, const Value& object, 5771 Value* /* result_val */) { 5772 return AbstractTypeCheck<&LiftoffCompiler::DataCheck>(object); 5773 } 5774 5775 void RefIsFunc(FullDecoder* /* decoder */, const Value& object, 5776 Value* /* result_val */) { 5777 return AbstractTypeCheck<&LiftoffCompiler::FuncCheck>(object); 5778 } 5779 5780 void RefIsArray(FullDecoder* /* decoder */, const Value& object, 5781 Value* /* result_val */) { 5782 return AbstractTypeCheck<&LiftoffCompiler::ArrayCheck>(object); 5783 } 5784 5785 void RefIsI31(FullDecoder* decoder, const Value& object, 5786 Value* /* result */) { 5787 return AbstractTypeCheck<&LiftoffCompiler::I31Check>(object); 5788 } 5789 5790 template <TypeChecker type_checker> 5791 void AbstractTypeCast(const Value& object, FullDecoder* decoder, 5792 ValueKind result_kind) { 5793 Label* trap_label = 5794 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapIllegalCast); 5795 Label match; 5796 LiftoffRegister obj_reg = 5797 (this->*type_checker)(object, trap_label, {}, no_reg); 5798 __ bind(&match); 5799 __ PushRegister(result_kind, obj_reg); 5800 } 5801 5802 void RefAsData(FullDecoder* decoder, const Value& object, 5803 Value* /* result */) { 5804 return AbstractTypeCast<&LiftoffCompiler::DataCheck>(object, decoder, kRef); 5805 } 5806 5807 void RefAsFunc(FullDecoder* decoder, const Value& object, 5808 Value* /* result */) { 5809 return AbstractTypeCast<&LiftoffCompiler::FuncCheck>(object, decoder, kRef); 5810 } 5811 5812 void RefAsI31(FullDecoder* decoder, const Value& object, Value* result) { 5813 return AbstractTypeCast<&LiftoffCompiler::I31Check>(object, decoder, kRef); 5814 } 5815 5816 void RefAsArray(FullDecoder* decoder, const Value& object, Value* result) { 5817 return AbstractTypeCast<&LiftoffCompiler::ArrayCheck>(object, decoder, 5818 kRef); 5819 } 5820 5821 template <TypeChecker type_checker> 5822 void BrOnAbstractType(const Value& object, FullDecoder* decoder, 5823 uint32_t br_depth) { 5824 // Before branching, materialize all constants. This avoids repeatedly 5825 // materializing them for each conditional branch. 5826 if (br_depth != decoder->control_depth() - 1) { 5827 __ MaterializeMergedConstants( 5828 decoder->control_at(br_depth)->br_merge()->arity); 5829 } 5830 5831 Label no_match; 5832 LiftoffRegister obj_reg = 5833 (this->*type_checker)(object, &no_match, {}, no_reg); 5834 5835 __ PushRegister(kRef, obj_reg); 5836 BrOrRet(decoder, br_depth, 0); 5837 5838 __ bind(&no_match); 5839 } 5840 5841 template <TypeChecker type_checker> 5842 void BrOnNonAbstractType(const Value& object, FullDecoder* decoder, 5843 uint32_t br_depth) { 5844 // Before branching, materialize all constants. This avoids repeatedly 5845 // materializing them for each conditional branch. 5846 if (br_depth != decoder->control_depth() - 1) { 5847 __ MaterializeMergedConstants( 5848 decoder->control_at(br_depth)->br_merge()->arity); 5849 } 5850 5851 Label no_match, end; 5852 LiftoffRegister obj_reg = 5853 (this->*type_checker)(object, &no_match, {}, no_reg); 5854 __ PushRegister(kRef, obj_reg); 5855 __ emit_jump(&end); 5856 5857 __ bind(&no_match); 5858 BrOrRet(decoder, br_depth, 0); 5859 5860 __ bind(&end); 5861 } 5862 5863 void BrOnData(FullDecoder* decoder, const Value& object, 5864 Value* /* value_on_branch */, uint32_t br_depth) { 5865 return BrOnAbstractType<&LiftoffCompiler::DataCheck>(object, decoder, 5866 br_depth); 5867 } 5868 5869 void BrOnFunc(FullDecoder* decoder, const Value& object, 5870 Value* /* value_on_branch */, uint32_t br_depth) { 5871 return BrOnAbstractType<&LiftoffCompiler::FuncCheck>(object, decoder, 5872 br_depth); 5873 } 5874 5875 void BrOnI31(FullDecoder* decoder, const Value& object, 5876 Value* /* value_on_branch */, uint32_t br_depth) { 5877 return BrOnAbstractType<&LiftoffCompiler::I31Check>(object, decoder, 5878 br_depth); 5879 } 5880 5881 void BrOnArray(FullDecoder* decoder, const Value& object, 5882 Value* /* value_on_branch */, uint32_t br_depth) { 5883 return BrOnAbstractType<&LiftoffCompiler::ArrayCheck>(object, decoder, 5884 br_depth); 5885 } 5886 5887 void BrOnNonData(FullDecoder* decoder, const Value& object, 5888 Value* /* value_on_branch */, uint32_t br_depth) { 5889 return BrOnNonAbstractType<&LiftoffCompiler::DataCheck>(object, decoder, 5890 br_depth); 5891 } 5892 5893 void BrOnNonFunc(FullDecoder* decoder, const Value& object, 5894 Value* /* value_on_branch */, uint32_t br_depth) { 5895 return BrOnNonAbstractType<&LiftoffCompiler::FuncCheck>(object, decoder, 5896 br_depth); 5897 } 5898 5899 void BrOnNonI31(FullDecoder* decoder, const Value& object, 5900 Value* /* value_on_branch */, uint32_t br_depth) { 5901 return BrOnNonAbstractType<&LiftoffCompiler::I31Check>(object, decoder, 5902 br_depth); 5903 } 5904 5905 void BrOnNonArray(FullDecoder* decoder, const Value& object, 5906 Value* /* value_on_branch */, uint32_t br_depth) { 5907 return BrOnNonAbstractType<&LiftoffCompiler::ArrayCheck>(object, decoder, 5908 br_depth); 5909 } 5910 5911 void Forward(FullDecoder* decoder, const Value& from, Value* to) { 5912 // Nothing to do here. 5913 } 5914 5915 private: 5916 void CallDirect(FullDecoder* decoder, 5917 const CallFunctionImmediate<validate>& imm, 5918 const Value args[], Value returns[], TailCall tail_call) { 5919 MostlySmallValueKindSig sig(compilation_zone_, imm.sig); 5920 for (ValueKind ret : sig.returns()) { 5921 if (!CheckSupportedType(decoder, ret, "return")) return; 5922 } 5923 5924 auto call_descriptor = 5925 compiler::GetWasmCallDescriptor(compilation_zone_, imm.sig); 5926 call_descriptor = 5927 GetLoweredCallDescriptor(compilation_zone_, call_descriptor); 5928 5929 // One slot would be enough for call_direct, but would make index 5930 // computations much more complicated. 5931 uintptr_t vector_slot = num_call_instructions_ * 2; 5932 if (FLAG_wasm_speculative_inlining) { 5933 base::MutexGuard mutex_guard(&decoder->module_->type_feedback.mutex); 5934 decoder->module_->type_feedback.feedback_for_function[func_index_] 5935 .positions[decoder->position()] = 5936 static_cast<int>(num_call_instructions_); 5937 num_call_instructions_++; 5938 } 5939 5940 if (imm.index < env_->module->num_imported_functions) { 5941 // A direct call to an imported function. 5942 LiftoffRegList pinned; 5943 Register tmp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 5944 Register target = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 5945 5946 Register imported_targets = tmp; 5947 LOAD_INSTANCE_FIELD(imported_targets, ImportedFunctionTargets, 5948 kSystemPointerSize, pinned); 5949 __ Load(LiftoffRegister(target), imported_targets, no_reg, 5950 imm.index * sizeof(Address), kPointerLoadType, pinned); 5951 5952 Register imported_function_refs = tmp; 5953 LOAD_TAGGED_PTR_INSTANCE_FIELD(imported_function_refs, 5954 ImportedFunctionRefs, pinned); 5955 Register imported_function_ref = tmp; 5956 __ LoadTaggedPointer( 5957 imported_function_ref, imported_function_refs, no_reg, 5958 ObjectAccess::ElementOffsetInTaggedFixedArray(imm.index), pinned); 5959 5960 Register* explicit_instance = &imported_function_ref; 5961 __ PrepareCall(&sig, call_descriptor, &target, explicit_instance); 5962 if (tail_call) { 5963 __ PrepareTailCall( 5964 static_cast<int>(call_descriptor->ParameterSlotCount()), 5965 static_cast<int>( 5966 call_descriptor->GetStackParameterDelta(descriptor_))); 5967 __ TailCallIndirect(target); 5968 } else { 5969 source_position_table_builder_.AddPosition( 5970 __ pc_offset(), SourcePosition(decoder->position()), true); 5971 __ CallIndirect(&sig, call_descriptor, target); 5972 FinishCall(decoder, &sig, call_descriptor); 5973 } 5974 } else { 5975 // Inlining direct calls isn't speculative, but existence of the 5976 // feedback vector currently depends on this flag. 5977 if (FLAG_wasm_speculative_inlining) { 5978 LiftoffRegister vector = __ GetUnusedRegister(kGpReg, {}); 5979 __ Fill(vector, liftoff::kFeedbackVectorOffset, kPointerKind); 5980 __ IncrementSmi(vector, 5981 wasm::ObjectAccess::ElementOffsetInTaggedFixedArray( 5982 static_cast<int>(vector_slot))); 5983 } 5984 // A direct call within this module just gets the current instance. 5985 __ PrepareCall(&sig, call_descriptor); 5986 // Just encode the function index. This will be patched at instantiation. 5987 Address addr = static_cast<Address>(imm.index); 5988 if (tail_call) { 5989 DCHECK(descriptor_->CanTailCall(call_descriptor)); 5990 __ PrepareTailCall( 5991 static_cast<int>(call_descriptor->ParameterSlotCount()), 5992 static_cast<int>( 5993 call_descriptor->GetStackParameterDelta(descriptor_))); 5994 __ TailCallNativeWasmCode(addr); 5995 } else { 5996 source_position_table_builder_.AddPosition( 5997 __ pc_offset(), SourcePosition(decoder->position()), true); 5998 __ CallNativeWasmCode(addr); 5999 FinishCall(decoder, &sig, call_descriptor); 6000 } 6001 } 6002 } 6003 6004 void CallIndirect(FullDecoder* decoder, const Value& index_val, 6005 const CallIndirectImmediate<validate>& imm, 6006 TailCall tail_call) { 6007 MostlySmallValueKindSig sig(compilation_zone_, imm.sig); 6008 for (ValueKind ret : sig.returns()) { 6009 if (!CheckSupportedType(decoder, ret, "return")) return; 6010 } 6011 6012 // Pop the index. We'll modify the register's contents later. 6013 Register index = __ PopToModifiableRegister().gp(); 6014 6015 LiftoffRegList pinned = {index}; 6016 // Get three temporary registers. 6017 Register table = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 6018 Register tmp_const = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 6019 Register scratch = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 6020 Register indirect_function_table = no_reg; 6021 if (imm.table_imm.index != 0) { 6022 Register indirect_function_tables = 6023 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp(); 6024 LOAD_TAGGED_PTR_INSTANCE_FIELD(indirect_function_tables, 6025 IndirectFunctionTables, pinned); 6026 6027 indirect_function_table = indirect_function_tables; 6028 __ LoadTaggedPointer( 6029 indirect_function_table, indirect_function_tables, no_reg, 6030 ObjectAccess::ElementOffsetInTaggedFixedArray(imm.table_imm.index), 6031 pinned); 6032 } 6033 6034 // Bounds check against the table size. 6035 Label* invalid_func_label = 6036 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapTableOutOfBounds); 6037 6038 uint32_t canonical_sig_num = 6039 env_->module->canonicalized_type_ids[imm.sig_imm.index]; 6040 DCHECK_GE(canonical_sig_num, 0); 6041 DCHECK_GE(kMaxInt, canonical_sig_num); 6042 6043 // Compare against table size stored in 6044 // {instance->indirect_function_table_size}. 6045 if (imm.table_imm.index == 0) { 6046 LOAD_INSTANCE_FIELD(tmp_const, IndirectFunctionTableSize, kUInt32Size, 6047 pinned); 6048 } else { 6049 __ Load( 6050 LiftoffRegister(tmp_const), indirect_function_table, no_reg, 6051 wasm::ObjectAccess::ToTagged(WasmIndirectFunctionTable::kSizeOffset), 6052 LoadType::kI32Load, pinned); 6053 } 6054 __ emit_cond_jump(kUnsignedGreaterEqual, invalid_func_label, kI32, index, 6055 tmp_const); 6056 6057 CODE_COMMENT("Check indirect call signature"); 6058 // Load the signature from {instance->ift_sig_ids[key]} 6059 if (imm.table_imm.index == 0) { 6060 LOAD_INSTANCE_FIELD(table, IndirectFunctionTableSigIds, 6061 kSystemPointerSize, pinned); 6062 } else { 6063 __ Load(LiftoffRegister(table), indirect_function_table, no_reg, 6064 wasm::ObjectAccess::ToTagged( 6065 WasmIndirectFunctionTable::kSigIdsOffset), 6066 kPointerLoadType, pinned); 6067 } 6068 // Shift {index} by 2 (multiply by 4) to represent kInt32Size items. 6069 STATIC_ASSERT((1 << 2) == kInt32Size); 6070 __ emit_i32_shli(index, index, 2); 6071 __ Load(LiftoffRegister(scratch), table, index, 0, LoadType::kI32Load, 6072 pinned); 6073 6074 // Compare against expected signature. 6075 __ LoadConstant(LiftoffRegister(tmp_const), WasmValue(canonical_sig_num)); 6076 6077 Label* sig_mismatch_label = 6078 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapFuncSigMismatch); 6079 __ emit_cond_jump(kUnequal, sig_mismatch_label, kPointerKind, scratch, 6080 tmp_const); 6081 6082 // At this point {index} has already been multiplied by 4. 6083 CODE_COMMENT("Execute indirect call"); 6084 if (kTaggedSize != kInt32Size) { 6085 DCHECK_EQ(kTaggedSize, kInt32Size * 2); 6086 // Multiply {index} by another 2 to represent kTaggedSize items. 6087 __ emit_i32_add(index, index, index); 6088 } 6089 // At this point {index} has already been multiplied by kTaggedSize. 6090 6091 // Load the instance from {instance->ift_instances[key]} 6092 if (imm.table_imm.index == 0) { 6093 LOAD_TAGGED_PTR_INSTANCE_FIELD(table, IndirectFunctionTableRefs, pinned); 6094 } else { 6095 __ LoadTaggedPointer( 6096 table, indirect_function_table, no_reg, 6097 wasm::ObjectAccess::ToTagged(WasmIndirectFunctionTable::kRefsOffset), 6098 pinned); 6099 } 6100 __ LoadTaggedPointer(tmp_const, table, index, 6101 ObjectAccess::ElementOffsetInTaggedFixedArray(0), 6102 pinned); 6103 6104 if (kTaggedSize != kSystemPointerSize) { 6105 DCHECK_EQ(kSystemPointerSize, kTaggedSize * 2); 6106 // Multiply {index} by another 2 to represent kSystemPointerSize items. 6107 __ emit_i32_add(index, index, index); 6108 } 6109 // At this point {index} has already been multiplied by kSystemPointerSize. 6110 6111 Register* explicit_instance = &tmp_const; 6112 6113 // Load the target from {instance->ift_targets[key]} 6114 if (imm.table_imm.index == 0) { 6115 LOAD_INSTANCE_FIELD(table, IndirectFunctionTableTargets, 6116 kSystemPointerSize, pinned); 6117 } else { 6118 __ Load(LiftoffRegister(table), indirect_function_table, no_reg, 6119 wasm::ObjectAccess::ToTagged( 6120 WasmIndirectFunctionTable::kTargetsOffset), 6121 kPointerLoadType, pinned); 6122 } 6123 __ Load(LiftoffRegister(scratch), table, index, 0, kPointerLoadType, 6124 pinned); 6125 6126 auto call_descriptor = 6127 compiler::GetWasmCallDescriptor(compilation_zone_, imm.sig); 6128 call_descriptor = 6129 GetLoweredCallDescriptor(compilation_zone_, call_descriptor); 6130 6131 Register target = scratch; 6132 __ PrepareCall(&sig, call_descriptor, &target, explicit_instance); 6133 if (tail_call) { 6134 __ PrepareTailCall( 6135 static_cast<int>(call_descriptor->ParameterSlotCount()), 6136 static_cast<int>( 6137 call_descriptor->GetStackParameterDelta(descriptor_))); 6138 __ TailCallIndirect(target); 6139 } else { 6140 source_position_table_builder_.AddPosition( 6141 __ pc_offset(), SourcePosition(decoder->position()), true); 6142 __ CallIndirect(&sig, call_descriptor, target); 6143 6144 FinishCall(decoder, &sig, call_descriptor); 6145 } 6146 } 6147 6148 void CallRef(FullDecoder* decoder, ValueType func_ref_type, 6149 const FunctionSig* type_sig, TailCall tail_call) { 6150 MostlySmallValueKindSig sig(compilation_zone_, type_sig); 6151 for (ValueKind ret : sig.returns()) { 6152 if (!CheckSupportedType(decoder, ret, "return")) return; 6153 } 6154 compiler::CallDescriptor* call_descriptor = 6155 compiler::GetWasmCallDescriptor(compilation_zone_, type_sig); 6156 call_descriptor = 6157 GetLoweredCallDescriptor(compilation_zone_, call_descriptor); 6158 6159 Register target_reg = no_reg, instance_reg = no_reg; 6160 6161 if (FLAG_wasm_speculative_inlining) { 6162 ValueKind kIntPtrKind = kPointerKind; 6163 6164 LiftoffRegList pinned; 6165 LiftoffRegister vector = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6166 LiftoffAssembler::VarState funcref = 6167 __ cache_state()->stack_state.end()[-1]; 6168 if (funcref.is_reg()) pinned.set(funcref.reg()); 6169 __ Fill(vector, liftoff::kFeedbackVectorOffset, kPointerKind); 6170 LiftoffAssembler::VarState vector_var(kPointerKind, vector, 0); 6171 LiftoffRegister index = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6172 uintptr_t vector_slot = num_call_instructions_ * 2; 6173 { 6174 base::MutexGuard mutex_guard(&decoder->module_->type_feedback.mutex); 6175 decoder->module_->type_feedback.feedback_for_function[func_index_] 6176 .positions[decoder->position()] = 6177 static_cast<int>(num_call_instructions_); 6178 } 6179 num_call_instructions_++; 6180 __ LoadConstant(index, WasmValue::ForUintPtr(vector_slot)); 6181 LiftoffAssembler::VarState index_var(kIntPtrKind, index, 0); 6182 6183 // CallRefIC(vector: FixedArray, index: intptr, 6184 // funcref: WasmInternalFunction) 6185 CallRuntimeStub(WasmCode::kCallRefIC, 6186 MakeSig::Returns(kPointerKind, kPointerKind) 6187 .Params(kPointerKind, kIntPtrKind, kPointerKind), 6188 {vector_var, index_var, funcref}, decoder->position()); 6189 6190 __ cache_state()->stack_state.pop_back(1); // Drop funcref. 6191 target_reg = LiftoffRegister(kReturnRegister0).gp(); 6192 instance_reg = LiftoffRegister(kReturnRegister1).gp(); 6193 6194 } else { // FLAG_wasm_speculative_inlining 6195 // Non-feedback-collecting version. 6196 // Executing a write barrier needs temp registers; doing this on a 6197 // conditional branch confuses the LiftoffAssembler's register management. 6198 // Spill everything up front to work around that. 6199 __ SpillAllRegisters(); 6200 6201 // We limit ourselves to four registers: 6202 // (1) func_data, initially reused for func_ref. 6203 // (2) instance, initially used as temp. 6204 // (3) target, initially used as temp. 6205 // (4) temp. 6206 LiftoffRegList pinned; 6207 LiftoffRegister func_ref = pinned.set(__ PopToModifiableRegister(pinned)); 6208 MaybeEmitNullCheck(decoder, func_ref.gp(), pinned, func_ref_type); 6209 LiftoffRegister instance = 6210 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6211 LiftoffRegister target = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6212 LiftoffRegister temp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6213 6214 // Load "ref" (instance or WasmApiFunctionRef) and target. 6215 __ LoadTaggedPointer( 6216 instance.gp(), func_ref.gp(), no_reg, 6217 wasm::ObjectAccess::ToTagged(WasmInternalFunction::kRefOffset), 6218 pinned); 6219 6220#ifdef V8_SANDBOXED_EXTERNAL_POINTERS 6221 LOAD_INSTANCE_FIELD(temp.gp(), IsolateRoot, kSystemPointerSize, pinned); 6222 __ LoadExternalPointer(target.gp(), func_ref.gp(), 6223 WasmInternalFunction::kForeignAddressOffset, 6224 kForeignForeignAddressTag, temp.gp()); 6225#else 6226 __ Load(target, func_ref.gp(), no_reg, 6227 wasm::ObjectAccess::ToTagged( 6228 WasmInternalFunction::kForeignAddressOffset), 6229 kPointerLoadType, pinned); 6230#endif 6231 6232 Label perform_call; 6233 6234 LiftoffRegister null_address = temp; 6235 __ LoadConstant(null_address, WasmValue::ForUintPtr(0)); 6236 __ emit_cond_jump(kUnequal, &perform_call, kRef, target.gp(), 6237 null_address.gp()); 6238 // The cached target can only be null for WasmJSFunctions. 6239 __ LoadTaggedPointer( 6240 target.gp(), func_ref.gp(), no_reg, 6241 wasm::ObjectAccess::ToTagged(WasmInternalFunction::kCodeOffset), 6242 pinned); 6243#ifdef V8_EXTERNAL_CODE_SPACE 6244 __ LoadCodeDataContainerEntry(target.gp(), target.gp()); 6245#else 6246 __ emit_ptrsize_addi(target.gp(), target.gp(), 6247 wasm::ObjectAccess::ToTagged(Code::kHeaderSize)); 6248#endif 6249 // Fall through to {perform_call}. 6250 6251 __ bind(&perform_call); 6252 // Now the call target is in {target}, and the right instance object 6253 // is in {instance}. 6254 target_reg = target.gp(); 6255 instance_reg = instance.gp(); 6256 } // FLAG_wasm_speculative_inlining 6257 6258 __ PrepareCall(&sig, call_descriptor, &target_reg, &instance_reg); 6259 if (tail_call) { 6260 __ PrepareTailCall( 6261 static_cast<int>(call_descriptor->ParameterSlotCount()), 6262 static_cast<int>( 6263 call_descriptor->GetStackParameterDelta(descriptor_))); 6264 __ TailCallIndirect(target_reg); 6265 } else { 6266 source_position_table_builder_.AddPosition( 6267 __ pc_offset(), SourcePosition(decoder->position()), true); 6268 __ CallIndirect(&sig, call_descriptor, target_reg); 6269 6270 FinishCall(decoder, &sig, call_descriptor); 6271 } 6272 } 6273 6274 void LoadNullValue(Register null, LiftoffRegList pinned) { 6275 LOAD_INSTANCE_FIELD(null, IsolateRoot, kSystemPointerSize, pinned); 6276 __ LoadFullPointer(null, null, 6277 IsolateData::root_slot_offset(RootIndex::kNullValue)); 6278 } 6279 6280 void LoadExceptionSymbol(Register dst, LiftoffRegList pinned, 6281 RootIndex root_index) { 6282 LOAD_INSTANCE_FIELD(dst, IsolateRoot, kSystemPointerSize, pinned); 6283 uint32_t offset_imm = IsolateData::root_slot_offset(root_index); 6284 __ LoadFullPointer(dst, dst, offset_imm); 6285 } 6286 6287 void MaybeEmitNullCheck(FullDecoder* decoder, Register object, 6288 LiftoffRegList pinned, ValueType type) { 6289 if (FLAG_experimental_wasm_skip_null_checks || !type.is_nullable()) return; 6290 Label* trap_label = 6291 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapNullDereference); 6292 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned); 6293 LoadNullValue(null.gp(), pinned); 6294 __ emit_cond_jump(LiftoffCondition::kEqual, trap_label, kOptRef, object, 6295 null.gp()); 6296 } 6297 6298 void BoundsCheckArray(FullDecoder* decoder, LiftoffRegister array, 6299 LiftoffRegister index, LiftoffRegList pinned) { 6300 if (V8_UNLIKELY(FLAG_experimental_wasm_skip_bounds_checks)) return; 6301 Label* trap_label = 6302 AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayOutOfBounds); 6303 LiftoffRegister length = __ GetUnusedRegister(kGpReg, pinned); 6304 constexpr int kLengthOffset = 6305 wasm::ObjectAccess::ToTagged(WasmArray::kLengthOffset); 6306 __ Load(length, array.gp(), no_reg, kLengthOffset, LoadType::kI32Load, 6307 pinned); 6308 __ emit_cond_jump(LiftoffCondition::kUnsignedGreaterEqual, trap_label, kI32, 6309 index.gp(), length.gp()); 6310 } 6311 6312 int StructFieldOffset(const StructType* struct_type, int field_index) { 6313 return wasm::ObjectAccess::ToTagged(WasmStruct::kHeaderSize + 6314 struct_type->field_offset(field_index)); 6315 } 6316 6317 void LoadObjectField(LiftoffRegister dst, Register src, Register offset_reg, 6318 int offset, ValueKind kind, bool is_signed, 6319 LiftoffRegList pinned) { 6320 if (is_reference(kind)) { 6321 __ LoadTaggedPointer(dst.gp(), src, offset_reg, offset, pinned); 6322 } else { 6323 // Primitive kind. 6324 LoadType load_type = LoadType::ForValueKind(kind, is_signed); 6325 __ Load(dst, src, offset_reg, offset, load_type, pinned); 6326 } 6327 } 6328 6329 void StoreObjectField(Register obj, Register offset_reg, int offset, 6330 LiftoffRegister value, LiftoffRegList pinned, 6331 ValueKind kind) { 6332 if (is_reference(kind)) { 6333 __ StoreTaggedPointer(obj, offset_reg, offset, value, pinned); 6334 } else { 6335 // Primitive kind. 6336 StoreType store_type = StoreType::ForValueKind(kind); 6337 __ Store(obj, offset_reg, offset, value, store_type, pinned); 6338 } 6339 } 6340 6341 void SetDefaultValue(LiftoffRegister reg, ValueKind kind, 6342 LiftoffRegList pinned) { 6343 DCHECK(is_defaultable(kind)); 6344 switch (kind) { 6345 case kI8: 6346 case kI16: 6347 case kI32: 6348 return __ LoadConstant(reg, WasmValue(int32_t{0})); 6349 case kI64: 6350 return __ LoadConstant(reg, WasmValue(int64_t{0})); 6351 case kF32: 6352 return __ LoadConstant(reg, WasmValue(float{0.0})); 6353 case kF64: 6354 return __ LoadConstant(reg, WasmValue(double{0.0})); 6355 case kS128: 6356 DCHECK(CpuFeatures::SupportsWasmSimd128()); 6357 return __ emit_s128_xor(reg, reg, reg); 6358 case kOptRef: 6359 return LoadNullValue(reg.gp(), pinned); 6360 case kRtt: 6361 case kVoid: 6362 case kBottom: 6363 case kRef: 6364 UNREACHABLE(); 6365 } 6366 } 6367 6368 struct TypeCheckRegisters { 6369 LiftoffRegister obj_reg, map_reg, tmp_reg; 6370 }; 6371 6372 TypeCheckRegisters TypeCheckPrelude(const Value& obj, Label* no_match, 6373 LiftoffRegList pinned, 6374 Register opt_scratch) { 6375 LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned)); 6376 6377 // Reserve all temporary registers up front, so that the cache state 6378 // tracking doesn't get confused by the following conditional jumps. 6379 LiftoffRegister map_reg = 6380 opt_scratch != no_reg 6381 ? LiftoffRegister(opt_scratch) 6382 : pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6383 LiftoffRegister tmp_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6384 6385 if (obj.type.is_nullable()) { 6386 LoadNullValue(map_reg.gp(), pinned); 6387 __ emit_cond_jump(kEqual, no_match, kOptRef, obj_reg.gp(), map_reg.gp()); 6388 } 6389 6390 __ emit_smi_check(obj_reg.gp(), no_match, LiftoffAssembler::kJumpOnSmi); 6391 6392 __ LoadMap(map_reg.gp(), obj_reg.gp()); 6393 6394 return {obj_reg, map_reg, tmp_reg}; 6395 } 6396 6397 void EmitDataRefCheck(Register map, Label* not_data_ref, LiftoffRegister tmp, 6398 LiftoffRegList pinned) { 6399 constexpr int kInstanceTypeOffset = 6400 wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset); 6401 __ Load(tmp, map, no_reg, kInstanceTypeOffset, LoadType::kI32Load16U, 6402 pinned); 6403 // We're going to test a range of WasmObject instance types with a single 6404 // unsigned comparison. 6405 __ emit_i32_subi(tmp.gp(), tmp.gp(), FIRST_WASM_OBJECT_TYPE); 6406 __ emit_i32_cond_jumpi(kUnsignedGreaterThan, not_data_ref, tmp.gp(), 6407 LAST_WASM_OBJECT_TYPE - FIRST_WASM_OBJECT_TYPE); 6408 } 6409 6410 void MaybeOSR() { 6411 if (V8_UNLIKELY(for_debugging_)) { 6412 __ MaybeOSR(); 6413 } 6414 } 6415 6416 void FinishCall(FullDecoder* decoder, ValueKindSig* sig, 6417 compiler::CallDescriptor* call_descriptor) { 6418 DefineSafepoint(); 6419 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); 6420 int pc_offset = __ pc_offset(); 6421 MaybeOSR(); 6422 EmitLandingPad(decoder, pc_offset); 6423 __ FinishCall(sig, call_descriptor); 6424 } 6425 6426 void CheckNan(LiftoffRegister src, LiftoffRegList pinned, ValueKind kind) { 6427 DCHECK(kind == ValueKind::kF32 || kind == ValueKind::kF64); 6428 auto nondeterminism_addr = __ GetUnusedRegister(kGpReg, pinned); 6429 __ LoadConstant( 6430 nondeterminism_addr, 6431 WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(nondeterminism_))); 6432 __ emit_set_if_nan(nondeterminism_addr.gp(), src.fp(), kind); 6433 } 6434 6435 void CheckS128Nan(LiftoffRegister dst, LiftoffRegList pinned, 6436 ValueKind lane_kind) { 6437 RegClass rc = reg_class_for(kS128); 6438 LiftoffRegister tmp_gp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6439 LiftoffRegister tmp_s128 = pinned.set(__ GetUnusedRegister(rc, pinned)); 6440 LiftoffRegister nondeterminism_addr = 6441 pinned.set(__ GetUnusedRegister(kGpReg, pinned)); 6442 __ LoadConstant( 6443 nondeterminism_addr, 6444 WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(nondeterminism_))); 6445 __ emit_s128_set_if_nan(nondeterminism_addr.gp(), dst, tmp_gp.gp(), 6446 tmp_s128, lane_kind); 6447 } 6448 6449 bool has_outstanding_op() const { 6450 return outstanding_op_ != kNoOutstandingOp; 6451 } 6452 6453 bool test_and_reset_outstanding_op(WasmOpcode opcode) { 6454 DCHECK_NE(kNoOutstandingOp, opcode); 6455 if (outstanding_op_ != opcode) return false; 6456 outstanding_op_ = kNoOutstandingOp; 6457 return true; 6458 } 6459 6460 void TraceCacheState(FullDecoder* decoder) const { 6461 if (!FLAG_trace_liftoff) return; 6462 StdoutStream os; 6463 for (int control_depth = decoder->control_depth() - 1; control_depth >= -1; 6464 --control_depth) { 6465 auto* cache_state = 6466 control_depth == -1 ? __ cache_state() 6467 : &decoder->control_at(control_depth) 6468 ->label_state; 6469 os << PrintCollection(cache_state->stack_state); 6470 if (control_depth != -1) PrintF("; "); 6471 } 6472 os << "\n"; 6473 } 6474 6475 void DefineSafepoint() { 6476 auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_); 6477 __ cache_state()->DefineSafepoint(safepoint); 6478 } 6479 6480 void DefineSafepointWithCalleeSavedRegisters() { 6481 auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_); 6482 __ cache_state()->DefineSafepointWithCalleeSavedRegisters(safepoint); 6483 } 6484 6485 Register LoadInstanceIntoRegister(LiftoffRegList pinned, Register fallback) { 6486 Register instance = __ cache_state()->cached_instance; 6487 if (instance == no_reg) { 6488 instance = __ cache_state()->TrySetCachedInstanceRegister( 6489 pinned | LiftoffRegList{fallback}); 6490 if (instance == no_reg) instance = fallback; 6491 __ LoadInstanceFromFrame(instance); 6492 } 6493 return instance; 6494 } 6495 6496 static constexpr WasmOpcode kNoOutstandingOp = kExprUnreachable; 6497 static constexpr base::EnumSet<ValueKind> kUnconditionallySupported{ 6498 // MVP: 6499 kI32, kI64, kF32, kF64, 6500 // Extern ref: 6501 kRef, kOptRef, kRtt, kI8, kI16}; 6502 6503 LiftoffAssembler asm_; 6504 6505 // Used for merging code generation of subsequent operations (via look-ahead). 6506 // Set by the first opcode, reset by the second. 6507 WasmOpcode outstanding_op_ = kNoOutstandingOp; 6508 6509 // {supported_types_} is updated in {MaybeBailoutForUnsupportedType}. 6510 base::EnumSet<ValueKind> supported_types_ = kUnconditionallySupported; 6511 compiler::CallDescriptor* const descriptor_; 6512 CompilationEnv* const env_; 6513 DebugSideTableBuilder* const debug_sidetable_builder_; 6514 const ForDebugging for_debugging_; 6515 LiftoffBailoutReason bailout_reason_ = kSuccess; 6516 const int func_index_; 6517 ZoneVector<OutOfLineCode> out_of_line_code_; 6518 SourcePositionTableBuilder source_position_table_builder_; 6519 ZoneVector<trap_handler::ProtectedInstructionData> protected_instructions_; 6520 // Zone used to store information during compilation. The result will be 6521 // stored independently, such that this zone can die together with the 6522 // LiftoffCompiler after compilation. 6523 Zone* compilation_zone_; 6524 SafepointTableBuilder safepoint_table_builder_; 6525 // The pc offset of the instructions to reserve the stack frame. Needed to 6526 // patch the actually needed stack size in the end. 6527 uint32_t pc_offset_stack_frame_construction_ = 0; 6528 // For emitting breakpoint, we store a pointer to the position of the next 6529 // breakpoint, and a pointer after the list of breakpoints as end marker. 6530 // A single breakpoint at offset 0 indicates that we should prepare the 6531 // function for stepping by flooding it with breakpoints. 6532 const int* next_breakpoint_ptr_ = nullptr; 6533 const int* next_breakpoint_end_ = nullptr; 6534 6535 // Introduce a dead breakpoint to ensure that the calculation of the return 6536 // address in OSR is correct. 6537 int dead_breakpoint_ = 0; 6538 6539 // Remember whether the did function-entry break checks (for "hook on function 6540 // call" and "break on entry" a.k.a. instrumentation breakpoint). This happens 6541 // at the first breakable opcode in the function (if compiling for debugging). 6542 bool did_function_entry_break_checks_ = false; 6543 6544 struct HandlerInfo { 6545 MovableLabel handler; 6546 int pc_offset; 6547 }; 6548 6549 ZoneVector<HandlerInfo> handlers_; 6550 int handler_table_offset_ = Assembler::kNoHandlerTable; 6551 6552 // Current number of exception refs on the stack. 6553 int num_exceptions_ = 0; 6554 6555 // Number of feedback-collecting call instructions encountered. While 6556 // compiling, also index of the next such instruction. Used for indexing type 6557 // feedback. 6558 uintptr_t num_call_instructions_ = 0; 6559 6560 int32_t* max_steps_; 6561 int32_t* nondeterminism_; 6562 6563 DISALLOW_IMPLICIT_CONSTRUCTORS(LiftoffCompiler); 6564}; 6565 6566// static 6567constexpr WasmOpcode LiftoffCompiler::kNoOutstandingOp; 6568// static 6569constexpr base::EnumSet<ValueKind> LiftoffCompiler::kUnconditionallySupported; 6570 6571} // namespace 6572 6573WasmCompilationResult ExecuteLiftoffCompilation( 6574 CompilationEnv* env, const FunctionBody& func_body, int func_index, 6575 ForDebugging for_debugging, const LiftoffOptions& compiler_options) { 6576 base::TimeTicks start_time; 6577 if (V8_UNLIKELY(FLAG_trace_wasm_compilation_times)) { 6578 start_time = base::TimeTicks::Now(); 6579 } 6580 int func_body_size = static_cast<int>(func_body.end - func_body.start); 6581 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), 6582 "wasm.CompileBaseline", "funcIndex", func_index, "bodySize", 6583 func_body_size); 6584 6585 Zone zone(GetWasmEngine()->allocator(), "LiftoffCompilationZone"); 6586 auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, func_body.sig); 6587 size_t code_size_estimate = 6588 WasmCodeManager::EstimateLiftoffCodeSize(func_body_size); 6589 // Allocate the initial buffer a bit bigger to avoid reallocation during code 6590 // generation. Overflows when casting to int are fine, as we will allocate at 6591 // least {AssemblerBase::kMinimalBufferSize} anyway, so in the worst case we 6592 // have to grow more often. 6593 int initial_buffer_size = static_cast<int>(128 + code_size_estimate * 4 / 3); 6594 std::unique_ptr<DebugSideTableBuilder> debug_sidetable_builder; 6595 if (compiler_options.debug_sidetable) { 6596 debug_sidetable_builder = std::make_unique<DebugSideTableBuilder>(); 6597 } 6598 DCHECK_IMPLIES(compiler_options.max_steps, for_debugging == kForDebugging); 6599 WasmFeatures unused_detected_features; 6600 WasmFullDecoder<Decoder::kBooleanValidation, LiftoffCompiler> decoder( 6601 &zone, env->module, env->enabled_features, 6602 compiler_options.detected_features ? compiler_options.detected_features 6603 : &unused_detected_features, 6604 func_body, call_descriptor, env, &zone, 6605 NewAssemblerBuffer(initial_buffer_size), debug_sidetable_builder.get(), 6606 for_debugging, func_index, compiler_options.breakpoints, 6607 compiler_options.dead_breakpoint, compiler_options.max_steps, 6608 compiler_options.nondeterminism); 6609 decoder.Decode(); 6610 LiftoffCompiler* compiler = &decoder.interface(); 6611 if (decoder.failed()) compiler->OnFirstError(&decoder); 6612 6613 if (auto* counters = compiler_options.counters) { 6614 // Check that the histogram for the bailout reasons has the correct size. 6615 DCHECK_EQ(0, counters->liftoff_bailout_reasons()->min()); 6616 DCHECK_EQ(kNumBailoutReasons - 1, 6617 counters->liftoff_bailout_reasons()->max()); 6618 DCHECK_EQ(kNumBailoutReasons, 6619 counters->liftoff_bailout_reasons()->num_buckets()); 6620 // Register the bailout reason (can also be {kSuccess}). 6621 counters->liftoff_bailout_reasons()->AddSample( 6622 static_cast<int>(compiler->bailout_reason())); 6623 } 6624 6625 if (compiler->did_bailout()) return WasmCompilationResult{}; 6626 6627 WasmCompilationResult result; 6628 compiler->GetCode(&result.code_desc); 6629 result.instr_buffer = compiler->ReleaseBuffer(); 6630 result.source_positions = compiler->GetSourcePositionTable(); 6631 result.protected_instructions_data = compiler->GetProtectedInstructionsData(); 6632 result.frame_slot_count = compiler->GetTotalFrameSlotCountForGC(); 6633 auto* lowered_call_desc = GetLoweredCallDescriptor(&zone, call_descriptor); 6634 result.tagged_parameter_slots = lowered_call_desc->GetTaggedParameterSlots(); 6635 result.func_index = func_index; 6636 result.result_tier = ExecutionTier::kLiftoff; 6637 result.for_debugging = for_debugging; 6638 if (auto* debug_sidetable = compiler_options.debug_sidetable) { 6639 *debug_sidetable = debug_sidetable_builder->GenerateDebugSideTable(); 6640 } 6641 result.feedback_vector_slots = compiler->GetFeedbackVectorSlots(); 6642 6643 if (V8_UNLIKELY(FLAG_trace_wasm_compilation_times)) { 6644 base::TimeDelta time = base::TimeTicks::Now() - start_time; 6645 int codesize = result.code_desc.body_size(); 6646 StdoutStream{} << "Compiled function " 6647 << reinterpret_cast<const void*>(env->module) << "#" 6648 << func_index << " using Liftoff, took " 6649 << time.InMilliseconds() << " ms and " 6650 << zone.allocation_size() << " bytes; bodysize " 6651 << func_body_size << " codesize " << codesize << std::endl; 6652 } 6653 6654 DCHECK(result.succeeded()); 6655 return result; 6656} 6657 6658std::unique_ptr<DebugSideTable> GenerateLiftoffDebugSideTable( 6659 const WasmCode* code) { 6660 auto* native_module = code->native_module(); 6661 auto* function = &native_module->module()->functions[code->index()]; 6662 ModuleWireBytes wire_bytes{native_module->wire_bytes()}; 6663 base::Vector<const byte> function_bytes = 6664 wire_bytes.GetFunctionBytes(function); 6665 CompilationEnv env = native_module->CreateCompilationEnv(); 6666 FunctionBody func_body{function->sig, 0, function_bytes.begin(), 6667 function_bytes.end()}; 6668 6669 Zone zone(GetWasmEngine()->allocator(), "LiftoffDebugSideTableZone"); 6670 auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, function->sig); 6671 DebugSideTableBuilder debug_sidetable_builder; 6672 WasmFeatures detected; 6673 constexpr int kSteppingBreakpoints[] = {0}; 6674 DCHECK(code->for_debugging() == kForDebugging || 6675 code->for_debugging() == kForStepping); 6676 base::Vector<const int> breakpoints = 6677 code->for_debugging() == kForStepping 6678 ? base::ArrayVector(kSteppingBreakpoints) 6679 : base::Vector<const int>{}; 6680 WasmFullDecoder<Decoder::kBooleanValidation, LiftoffCompiler> decoder( 6681 &zone, native_module->module(), env.enabled_features, &detected, 6682 func_body, call_descriptor, &env, &zone, 6683 NewAssemblerBuffer(AssemblerBase::kDefaultBufferSize), 6684 &debug_sidetable_builder, code->for_debugging(), code->index(), 6685 breakpoints); 6686 decoder.Decode(); 6687 DCHECK(decoder.ok()); 6688 DCHECK(!decoder.interface().did_bailout()); 6689 return debug_sidetable_builder.GenerateDebugSideTable(); 6690} 6691 6692#undef __ 6693#undef TRACE 6694#undef WASM_INSTANCE_OBJECT_FIELD_OFFSET 6695#undef WASM_INSTANCE_OBJECT_FIELD_SIZE 6696#undef LOAD_INSTANCE_FIELD 6697#undef LOAD_TAGGED_PTR_INSTANCE_FIELD 6698#undef CODE_COMMENT 6699 6700} // namespace wasm 6701} // namespace internal 6702} // namespace v8 6703