v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
liftoff-compiler.cc
Go to the documentation of this file.
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
6
7#include <optional>
8
9#include "src/base/enum-set.h"
11// TODO(clemensb): Remove dependences on compiler stuff.
20#include "src/logging/log.h"
22#include "src/objects/smi.h"
23#include "src/roots/roots.h"
25#include "src/utils/ostreams.h"
26#include "src/utils/utils.h"
35#include "src/wasm/wasm-debug.h"
40
41namespace v8::internal::wasm {
42
46constexpr auto kStack = VarState::kStack;
47
48namespace {
49
50#define __ asm_.
51
52// It's important that we don't modify the LiftoffAssembler's cache state
53// in conditionally-executed code paths. Creating these witnesses helps
54// enforce that (using DCHECKs in the cache state).
55// Conditional jump instructions require a witness to have been created (to
56// make sure we don't forget); the witness should stay alive until the label
57// is bound where regular control flow resumes. This implies that when we're
58// jumping to a trap, the live range of the witness isn't important.
59#define FREEZE_STATE(witness_name) FreezeCacheState witness_name(asm_)
60
61#define TRACE(...) \
62 do { \
63 if (v8_flags.trace_liftoff) PrintF("[liftoff] " __VA_ARGS__); \
64 } while (false)
65
66#define WASM_TRUSTED_INSTANCE_DATA_FIELD_OFFSET(name) \
67 ObjectAccess::ToTagged(WasmTrustedInstanceData::k##name##Offset)
68
69template <int expected_size, int actual_size>
70struct assert_field_size {
71 static_assert(expected_size == actual_size,
72 "field in WasmInstance does not have the expected size");
73 static constexpr int size = actual_size;
74};
75
76#define WASM_TRUSTED_INSTANCE_DATA_FIELD_SIZE(name) \
77 FIELD_SIZE(WasmTrustedInstanceData::k##name##Offset)
78
79#define LOAD_INSTANCE_FIELD(dst, name, load_size, pinned) \
80 __ LoadFromInstance( \
81 dst, LoadInstanceIntoRegister(pinned, dst), \
82 WASM_TRUSTED_INSTANCE_DATA_FIELD_OFFSET(name), \
83 assert_field_size<WASM_TRUSTED_INSTANCE_DATA_FIELD_SIZE(name), \
84 load_size>::size);
85
86#define LOAD_TAGGED_PTR_INSTANCE_FIELD(dst, name, pinned) \
87 static_assert( \
88 WASM_TRUSTED_INSTANCE_DATA_FIELD_SIZE(name) == kTaggedSize, \
89 "field in WasmTrustedInstanceData does not have the expected size"); \
90 __ LoadTaggedPointerFromInstance( \
91 dst, LoadInstanceIntoRegister(pinned, dst), \
92 WASM_TRUSTED_INSTANCE_DATA_FIELD_OFFSET(name));
93
94#define LOAD_PROTECTED_PTR_INSTANCE_FIELD(dst, name, pinned) \
95 static_assert( \
96 WASM_TRUSTED_INSTANCE_DATA_FIELD_SIZE(Protected##name) == kTaggedSize, \
97 "field in WasmTrustedInstanceData does not have the expected size"); \
98 __ LoadProtectedPointer( \
99 dst, LoadInstanceIntoRegister(pinned, dst), \
100 WASM_TRUSTED_INSTANCE_DATA_FIELD_OFFSET(Protected##name));
101
102// Liftoff's code comments are intentionally without source location to keep
103// readability up.
104#ifdef V8_CODE_COMMENTS
105#define CODE_COMMENT(str) __ RecordComment(str, SourceLocation{})
106#define SCOPED_CODE_COMMENT(str) \
107 AssemblerBase::CodeComment CONCAT(scoped_comment_, __LINE__)( \
108 &asm_, str, SourceLocation{})
109#else
110#define CODE_COMMENT(str) ((void)0)
111#define SCOPED_CODE_COMMENT(str) ((void)0)
112#endif
113
114// For fuzzing purposes, we count each instruction as one "step". Certain
115// "bulk" type instructions (dealing with memories, tables, strings, arrays)
116// can take much more time. For simplicity, we count them all as a fixed
117// large number of steps.
118constexpr int kHeavyInstructionSteps = 1000;
119
120constexpr ValueKind kIntPtrKind = LiftoffAssembler::kIntPtrKind;
121constexpr ValueKind kSmiKind = LiftoffAssembler::kSmiKind;
122
123// Used to construct fixed-size signatures: MakeSig::Returns(...).Params(...);
124using MakeSig = FixedSizeSignature<ValueKind>;
125
126#if V8_TARGET_ARCH_ARM64
127// On ARM64, the Assembler keeps track of pointers to Labels to resolve
128// branches to distant targets. Moving labels would confuse the Assembler,
129// thus store the label in the Zone.
130class MovableLabel {
131 public:
133 explicit MovableLabel(Zone* zone) : label_(zone->New<Label>()) {}
134
135 Label* get() { return label_; }
136
137 private:
138 Label* label_;
139};
140#else
141// On all other platforms, just store the Label directly.
142class MovableLabel {
143 public:
145 explicit MovableLabel(Zone*) {}
146
147 Label* get() { return &label_; }
148
149 private:
150 Label label_;
151};
152#endif
153
154compiler::CallDescriptor* GetLoweredCallDescriptor(
155 Zone* zone, compiler::CallDescriptor* call_desc) {
156 return kSystemPointerSize == 4
157 ? compiler::GetI32WasmCallDescriptor(zone, call_desc)
158 : call_desc;
159}
160
161constexpr Condition GetCompareCondition(WasmOpcode opcode) {
162 switch (opcode) {
163 case kExprI32Eq:
164 return kEqual;
165 case kExprI32Ne:
166 return kNotEqual;
167 case kExprI32LtS:
168 return kLessThan;
169 case kExprI32LtU:
170 return kUnsignedLessThan;
171 case kExprI32GtS:
172 return kGreaterThan;
173 case kExprI32GtU:
175 case kExprI32LeS:
176 return kLessThanEqual;
177 case kExprI32LeU:
179 case kExprI32GeS:
180 return kGreaterThanEqual;
181 case kExprI32GeU:
183 default:
184 UNREACHABLE();
185 }
186}
187
188// Builds a {DebugSideTable}.
189class DebugSideTableBuilder {
190 using Entry = DebugSideTable::Entry;
191 using Value = Entry::Value;
192
193 public:
194 enum AssumeSpilling {
195 // All register values will be spilled before the pc covered by the debug
196 // side table entry. Register slots will be marked as stack slots in the
197 // generated debug side table entry.
198 kAssumeSpilling,
199 // Register slots will be written out as they are.
200 kAllowRegisters,
201 // Register slots cannot appear since we already spilled.
202 kDidSpill
203 };
204
205 class EntryBuilder {
206 public:
207 explicit EntryBuilder(int pc_offset, int stack_height,
208 std::vector<Value> changed_values)
210 stack_height_(stack_height),
211 changed_values_(std::move(changed_values)) {}
212
213 Entry ToTableEntry() {
214 return Entry{pc_offset_, stack_height_, std::move(changed_values_)};
215 }
216
217 void MinimizeBasedOnPreviousStack(const std::vector<Value>& last_values) {
218 auto dst = changed_values_.begin();
219 auto end = changed_values_.end();
220 for (auto src = dst; src != end; ++src) {
221 if (src->index < static_cast<int>(last_values.size()) &&
222 *src == last_values[src->index]) {
223 continue;
224 }
225 if (dst != src) *dst = *src;
226 ++dst;
227 }
228 changed_values_.erase(dst, end);
229 }
230
231 int pc_offset() const { return pc_offset_; }
232 void set_pc_offset(int new_pc_offset) { pc_offset_ = new_pc_offset; }
233
234 private:
237 std::vector<Value> changed_values_;
238 };
239
240 // Adds a new entry in regular code.
241 void NewEntry(int pc_offset,
242 base::Vector<DebugSideTable::Entry::Value> values) {
243 entries_.emplace_back(pc_offset, static_cast<int>(values.size()),
244 GetChangedStackValues(last_values_, values));
245 }
246
247 // Adds a new entry for OOL code, and returns a pointer to a builder for
248 // modifying that entry.
249 EntryBuilder* NewOOLEntry(base::Vector<DebugSideTable::Entry::Value> values) {
250 constexpr int kNoPcOffsetYet = -1;
251 ool_entries_.emplace_back(kNoPcOffsetYet, static_cast<int>(values.size()),
252 GetChangedStackValues(last_ool_values_, values));
253 return &ool_entries_.back();
254 }
255
256 void SetNumLocals(int num_locals) {
257 DCHECK_EQ(-1, num_locals_);
258 DCHECK_LE(0, num_locals);
259 num_locals_ = num_locals;
260 }
261
262 std::unique_ptr<DebugSideTable> GenerateDebugSideTable() {
263 DCHECK_LE(0, num_locals_);
264
265 // Connect {entries_} and {ool_entries_} by removing redundant stack
266 // information from the first {ool_entries_} entry (based on
267 // {last_values_}).
268 if (!entries_.empty() && !ool_entries_.empty()) {
269 ool_entries_.front().MinimizeBasedOnPreviousStack(last_values_);
270 }
271
272 std::vector<Entry> entries;
273 entries.reserve(entries_.size() + ool_entries_.size());
274 for (auto& entry : entries_) entries.push_back(entry.ToTableEntry());
275 for (auto& entry : ool_entries_) entries.push_back(entry.ToTableEntry());
276 DCHECK(std::is_sorted(
277 entries.begin(), entries.end(),
278 [](Entry& a, Entry& b) { return a.pc_offset() < b.pc_offset(); }));
279 return std::make_unique<DebugSideTable>(num_locals_, std::move(entries));
280 }
281
282 private:
283 static std::vector<Value> GetChangedStackValues(
284 std::vector<Value>& last_values, base::Vector<Value> values) {
285 std::vector<Value> changed_values;
286 int old_stack_size = static_cast<int>(last_values.size());
287 last_values.resize(values.size());
288
289 int index = 0;
290 for (const auto& value : values) {
291 if (index >= old_stack_size || last_values[index] != value) {
292 changed_values.push_back(value);
293 last_values[index] = value;
294 }
295 ++index;
296 }
297 return changed_values;
298 }
299
300 int num_locals_ = -1;
301 // Keep a snapshot of the stack of the last entry, to generate a delta to the
302 // next entry.
303 std::vector<Value> last_values_;
304 std::vector<EntryBuilder> entries_;
305 // Keep OOL code entries separate so we can do proper delta-encoding (more
306 // entries might be added between the existing {entries_} and the
307 // {ool_entries_}). Store the entries in a list so the pointer is not
308 // invalidated by adding more entries.
309 std::vector<Value> last_ool_values_;
310 std::list<EntryBuilder> ool_entries_;
311};
312
313void CheckBailoutAllowed(LiftoffBailoutReason reason, const char* detail,
314 const CompilationEnv* env) {
315 // Decode errors are ok.
316 if (reason == kDecodeError) return;
317
318 // --liftoff-only ensures that tests actually exercise the Liftoff path
319 // without bailing out. We also fail for missing CPU support, to avoid
320 // running any TurboFan code under --liftoff-only.
321 if (v8_flags.liftoff_only) {
322 FATAL("--liftoff-only: treating bailout as fatal error. Cause: %s", detail);
323 }
324
325 // Missing CPU features are generally OK, except with --liftoff-only.
326 if (reason == kMissingCPUFeature) return;
327
328 // If --enable-testing-opcode-in-wasm is set, we are expected to bailout with
329 // "testing opcode".
330 if (v8_flags.enable_testing_opcode_in_wasm &&
331 strcmp(detail, "testing opcode") == 0) {
332 return;
333 }
334
335 // Some externally maintained architectures don't fully implement Liftoff yet.
336#if V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_S390X || V8_TARGET_ARCH_PPC64 || \
337 V8_TARGET_ARCH_LOONG64
338 return;
339#endif
340
341#if V8_TARGET_ARCH_ARM
342 // Allow bailout for missing ARMv7 support.
343 if (!CpuFeatures::IsSupported(ARMv7) && reason == kUnsupportedArchitecture) {
344 return;
345 }
346#endif
347
348#define LIST_FEATURE(name, ...) WasmEnabledFeature::name,
349 constexpr WasmEnabledFeatures kExperimentalFeatures{
351#undef LIST_FEATURE
352
353 // Bailout is allowed if any experimental feature is enabled.
354 if (env->enabled_features.contains_any(kExperimentalFeatures)) return;
355
356 // Otherwise, bailout is not allowed.
357 FATAL("Liftoff bailout should not happen. Cause: %s\n", detail);
358}
359
360class TempRegisterScope {
361 public:
362 LiftoffRegister Acquire(RegClass rc) {
363 LiftoffRegList candidates = free_temps_ & GetCacheRegList(rc);
364 DCHECK(!candidates.is_empty());
365 return free_temps_.clear(candidates.GetFirstRegSet());
366 }
367
368 void Return(LiftoffRegister&& temp) {
369 DCHECK(!free_temps_.has(temp));
370 free_temps_.set(temp);
371 }
372
373 void Return(Register&& temp) {
374 Return(LiftoffRegister{temp});
375 temp = no_reg;
376 }
377
378 LiftoffRegList AddTempRegisters(int count, RegClass rc,
379 LiftoffAssembler* lasm,
380 LiftoffRegList pinned) {
381 LiftoffRegList temps;
382 pinned |= free_temps_;
383 for (int i = 0; i < count; ++i) {
384 temps.set(lasm->GetUnusedRegister(rc, pinned | temps));
385 }
386 free_temps_ |= temps;
387 return temps;
388 }
389
390 private:
391 LiftoffRegList free_temps_;
392};
393
394class ScopedTempRegister {
395 public:
396 ScopedTempRegister(TempRegisterScope& temp_scope, RegClass rc)
397 : reg_(temp_scope.Acquire(rc)), temp_scope_(&temp_scope) {}
398
399 ScopedTempRegister(const ScopedTempRegister&) = delete;
400
401 ScopedTempRegister(ScopedTempRegister&& other) V8_NOEXCEPT
402 : reg_(other.reg_),
403 temp_scope_(other.temp_scope_) {
404 other.temp_scope_ = nullptr;
405 }
406
407 ScopedTempRegister& operator=(const ScopedTempRegister&) = delete;
408
409 ~ScopedTempRegister() {
410 if (temp_scope_) Reset();
411 }
412
413 LiftoffRegister reg() const {
414 DCHECK_NOT_NULL(temp_scope_);
415 return reg_;
416 }
417
418 Register gp_reg() const { return reg().gp(); }
419
420 void Reset() {
421 DCHECK_NOT_NULL(temp_scope_);
422 temp_scope_->Return(std::move(reg_));
423 temp_scope_ = nullptr;
424 }
425
426 private:
427 LiftoffRegister reg_;
428 TempRegisterScope* temp_scope_;
429};
430
431class LiftoffCompiler {
432 public:
433 using ValidationTag = Decoder::NoValidationTag;
434 using Value = ValueBase<ValidationTag>;
435 static constexpr bool kUsesPoppedArgs = false;
436
437 // Some constants for tier-up checks.
438 // In general we use the number of executed machine code bytes as an estimate
439 // of how much time was spent in this function.
440 // - {kTierUpCostForCheck} is the cost for checking for the tier-up itself,
441 // which is added to the PC distance on every tier-up check. This cost is
442 // for loading the tiering budget, subtracting from one entry in the array,
443 // and the conditional branch if the value is negative.
444 // - {kTierUpCostForFunctionEntry} reflects the cost for calling the frame
445 // setup stub in the function prologue (the time is spent in another code
446 // object and hence not reflected in the PC distance).
447 static constexpr int kTierUpCostForCheck = 20;
448 static constexpr int kTierUpCostForFunctionEntry = 40;
449
450 struct ElseState {
451 explicit ElseState(Zone* zone) : label(zone), state(zone) {}
452 MovableLabel label;
453 LiftoffAssembler::CacheState state;
454 };
455
456 struct TryInfo {
457 explicit TryInfo(Zone* zone) : catch_state(zone) {}
458 LiftoffAssembler::CacheState catch_state;
460 bool catch_reached = false;
461 bool in_handler = false;
462 };
463
464 struct Control : public ControlBase<Value, ValidationTag> {
465 ElseState* else_state = nullptr;
466 LiftoffAssembler::CacheState label_state;
467 MovableLabel label;
468 TryInfo* try_info = nullptr;
469 // Number of exceptions on the stack below this control.
471
473
474 template <typename... Args>
475 explicit Control(Zone* zone, Args&&... args) V8_NOEXCEPT
476 : ControlBase(zone, std::forward<Args>(args)...),
477 label_state(zone),
478 label(zone) {}
479 };
480
481 using FullDecoder = WasmFullDecoder<ValidationTag, LiftoffCompiler>;
483
484 class MostlySmallValueKindSig : public Signature<ValueKind> {
485 public:
486 MostlySmallValueKindSig(Zone* zone, const FunctionSig* sig)
487 : Signature<ValueKind>(sig->return_count(), sig->parameter_count(),
488 MakeKinds(inline_storage_, zone, sig)) {}
489
490 private:
491 static constexpr size_t kInlineStorage = 8;
492
493 static ValueKind* MakeKinds(ValueKind* storage, Zone* zone,
494 const FunctionSig* sig) {
495 const size_t size = sig->parameter_count() + sig->return_count();
496 if (V8_UNLIKELY(size > kInlineStorage)) {
497 storage = zone->AllocateArray<ValueKind>(size);
498 }
499 std::transform(sig->all().begin(), sig->all().end(), storage,
500 [](ValueType type) { return type.kind(); });
501 return storage;
502 }
503
505 };
506
507 // For debugging, we need to spill registers before a trap or a stack check to
508 // be able to inspect them.
509 struct SpilledRegistersForInspection : public ZoneObject {
510 struct Entry {
512 LiftoffRegister reg;
514 };
515 ZoneVector<Entry> entries;
516
517 explicit SpilledRegistersForInspection(Zone* zone) : entries(zone) {}
518 };
519
520 struct OutOfLineSafepointInfo {
521 ZoneVector<int> slots;
522 LiftoffRegList spills;
523
524 explicit OutOfLineSafepointInfo(Zone* zone) : slots(zone) {}
525 };
526
527 struct OutOfLineCode {
528 MovableLabel label;
529 MovableLabel continuation;
530 Builtin builtin;
532 LiftoffRegList regs_to_save;
534 OutOfLineSafepointInfo* safepoint_info;
535 // These two pointers will only be used for debug code:
536 SpilledRegistersForInspection* spilled_registers;
537 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder;
538
539 // Named constructors:
540 static OutOfLineCode Trap(
541 Zone* zone, Builtin builtin, WasmCodePosition pos,
542 SpilledRegistersForInspection* spilled_registers,
543 OutOfLineSafepointInfo* safepoint_info,
544 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) {
545 DCHECK_LT(0, pos);
546 return {
547 MovableLabel{zone}, // label
548 MovableLabel{zone}, // continuation
549 builtin, // builtin
550 pos, // position
551 {}, // regs_to_save
552 no_reg, // cached_instance_data
553 safepoint_info, // safepoint_info
554 spilled_registers, // spilled_registers
555 debug_sidetable_entry_builder // debug_side_table_entry_builder
556 };
557 }
558 static OutOfLineCode StackCheck(
559 Zone* zone, WasmCodePosition pos, LiftoffRegList regs_to_save,
560 Register cached_instance_data,
561 SpilledRegistersForInspection* spilled_regs,
562 OutOfLineSafepointInfo* safepoint_info,
563 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) {
564 Builtin stack_guard = Builtin::kWasmStackGuard;
565 if (v8_flags.experimental_wasm_growable_stacks) {
566 stack_guard = Builtin::kWasmGrowableStackGuard;
567 }
568 return {
569 MovableLabel{zone}, // label
570 MovableLabel{zone}, // continuation
571 stack_guard, // builtin
572 pos, // position
573 regs_to_save, // regs_to_save
574 cached_instance_data, // cached_instance_data
575 safepoint_info, // safepoint_info
576 spilled_regs, // spilled_registers
577 debug_sidetable_entry_builder // debug_side_table_entry_builder
578 };
579 }
580 static OutOfLineCode TierupCheck(
581 Zone* zone, WasmCodePosition pos, LiftoffRegList regs_to_save,
582 Register cached_instance_data,
583 SpilledRegistersForInspection* spilled_regs,
584 OutOfLineSafepointInfo* safepoint_info,
585 DebugSideTableBuilder::EntryBuilder* debug_sidetable_entry_builder) {
586 return {
587 MovableLabel{zone}, // label
588 MovableLabel{zone}, // continuation,
589 Builtin::kWasmTriggerTierUp, // builtin
590 pos, // position
591 regs_to_save, // regs_to_save
592 cached_instance_data, // cached_instance_data
593 safepoint_info, // safepoint_info
594 spilled_regs, // spilled_registers
595 debug_sidetable_entry_builder // debug_side_table_entry_builder
596 };
597 }
598 };
599
600 LiftoffCompiler(compiler::CallDescriptor* call_descriptor,
601 CompilationEnv* env, Zone* zone,
602 std::unique_ptr<AssemblerBuffer> buffer,
603 DebugSideTableBuilder* debug_sidetable_builder,
604 const LiftoffOptions& options)
605 : asm_(zone, std::move(buffer)),
606 descriptor_(GetLoweredCallDescriptor(zone, call_descriptor)),
607 env_(env),
608 debug_sidetable_builder_(debug_sidetable_builder),
609 for_debugging_(options.for_debugging),
610 func_index_(options.func_index),
611 out_of_line_code_(zone),
614 zone_(zone),
616 next_breakpoint_ptr_(options.breakpoints.begin()),
617 next_breakpoint_end_(options.breakpoints.end()),
618 dead_breakpoint_(options.dead_breakpoint),
619 handlers_(zone),
620 max_steps_(options.max_steps),
621 detect_nondeterminism_(options.detect_nondeterminism),
622 deopt_info_bytecode_offset_(options.deopt_info_bytecode_offset),
623 deopt_location_kind_(options.deopt_location_kind) {
624 // We often see huge numbers of traps per function, so pre-reserve some
625 // space in that vector. 128 entries is enough for ~94% of functions on
626 // modern modules, as of 2022-06-03.
627 out_of_line_code_.reserve(128);
628
629 DCHECK(options.is_initialized());
630 // If there are no breakpoints, both pointers should be nullptr.
632 next_breakpoint_ptr_ == next_breakpoint_end_,
633 next_breakpoint_ptr_ == nullptr && next_breakpoint_end_ == nullptr);
634 DCHECK_IMPLIES(!for_debugging_, debug_sidetable_builder_ == nullptr);
635 }
636
637 bool did_bailout() const { return bailout_reason_ != kSuccess; }
638 LiftoffBailoutReason bailout_reason() const { return bailout_reason_; }
639
640 void GetCode(CodeDesc* desc) {
641 asm_.GetCode(nullptr, desc, &safepoint_table_builder_,
642 handler_table_offset_);
643 }
644
645 std::unique_ptr<AssemblerBuffer> ReleaseBuffer() {
646 return asm_.ReleaseBuffer();
647 }
648
649 std::unique_ptr<LiftoffFrameDescriptionForDeopt> ReleaseFrameDescriptions() {
650 return std::move(frame_description_);
651 }
652
653 base::OwnedVector<uint8_t> GetSourcePositionTable() {
654 return source_position_table_builder_.ToSourcePositionTableVector();
655 }
656
657 base::OwnedVector<uint8_t> GetProtectedInstructionsData() const {
659 base::VectorOf(protected_instructions_)));
660 }
661
662 uint32_t GetTotalFrameSlotCountForGC() const {
663 return __ GetTotalFrameSlotCountForGC();
664 }
665
666 uint32_t OolSpillCount() const { return __ OolSpillCount(); }
667
668 void unsupported(FullDecoder* decoder, LiftoffBailoutReason reason,
669 const char* detail) {
670 DCHECK_NE(kSuccess, reason);
671 if (did_bailout()) return;
672 bailout_reason_ = reason;
673 TRACE("unsupported: %s\n", detail);
674 decoder->errorf(decoder->pc_offset(), "unsupported liftoff operation: %s",
675 detail);
676 UnuseLabels(decoder);
677 CheckBailoutAllowed(reason, detail, env_);
678 }
679
680 bool DidAssemblerBailout(FullDecoder* decoder) {
681 if (decoder->failed() || !__ did_bailout()) return false;
682 unsupported(decoder, __ bailout_reason(), __ bailout_detail());
683 return true;
684 }
685
686 V8_INLINE bool CheckSupportedType(FullDecoder* decoder, ValueKind kind,
687 const char* context) {
688 if (V8_LIKELY(supported_types_.contains(kind))) return true;
689 return MaybeBailoutForUnsupportedType(decoder, kind, context);
690 }
691
692 V8_NOINLINE bool MaybeBailoutForUnsupportedType(FullDecoder* decoder,
694 const char* context) {
695 DCHECK(!supported_types_.contains(kind));
696
697 // Lazily update {supported_types_}; then check again.
699 if (supported_types_.contains(kind)) return true;
700
701 LiftoffBailoutReason bailout_reason;
702 switch (kind) {
703 case kS128:
704 bailout_reason = kSimd;
705 break;
706 default:
707 UNREACHABLE();
708 }
709 base::EmbeddedVector<char, 128> buffer;
710 SNPrintF(buffer, "%s %s", name(kind), context);
711 unsupported(decoder, bailout_reason, buffer.begin());
712 return false;
713 }
714
715 void UnuseLabels(FullDecoder* decoder) {
716#ifdef DEBUG
717 auto Unuse = [](Label* label) {
718 label->Unuse();
719 label->UnuseNear();
720 };
721 // Unuse all labels now, otherwise their destructor will fire a DCHECK error
722 // if they where referenced before.
723 uint32_t control_depth = decoder ? decoder->control_depth() : 0;
724 for (uint32_t i = 0; i < control_depth; ++i) {
725 Control* c = decoder->control_at(i);
726 Unuse(c->label.get());
727 if (c->else_state) Unuse(c->else_state->label.get());
728 if (c->try_info != nullptr) Unuse(&c->try_info->catch_label);
729 }
730 for (auto& ool : out_of_line_code_) Unuse(ool.label.get());
731#endif
732 }
733
734 void StartFunction(FullDecoder* decoder) {
735 if (v8_flags.trace_liftoff && !v8_flags.trace_wasm_decoder) {
736 StdoutStream{} << "hint: add --trace-wasm-decoder to also see the wasm "
737 "instructions being decoded\n";
738 }
739 int num_locals = decoder->num_locals();
740 __ set_num_locals(num_locals);
741 for (int i = 0; i < num_locals; ++i) {
742 ValueKind kind = decoder->local_type(i).kind();
743 __ set_local_kind(i, kind);
744 }
745 }
746
747 class ParameterProcessor {
748 public:
749 ParameterProcessor(LiftoffCompiler* compiler, uint32_t num_params)
750 : compiler_(compiler), num_params_(num_params) {}
751
752 void Process() {
753 // First pass: collect parameter registers.
754 while (NextParam()) {
755 MaybeCollectRegister();
756 if (needs_gp_pair_) {
757 NextLocation();
758 MaybeCollectRegister();
759 }
760 }
761 // Second pass: allocate parameters.
762 param_idx_ = 0;
764 while (NextParam()) {
765 LiftoffRegister reg = LoadToReg(param_regs_);
766 // The sandbox's function signature checks don't care to distinguish
767 // i32 and i64 primitives. That's usually fine, but in a few cases
768 // i32 parameters with non-zero upper halves can violate security-
769 // relevant invariants, so we explicitly clear them here.
770 // 'clear_i32_upper_half' is empty on LoongArch64, MIPS64 and riscv64,
771 // because they will explicitly zero-extend their lower halves before
772 // using them for memory accesses anyway.
773 // In addition, the generic js-to-wasm wrapper does a sign-extension
774 // of i32 parameters, so clearing the upper half is required for
775 // correctness in this case.
776#if V8_TARGET_ARCH_64_BIT
777 if (kind_ == kI32 && location_.IsRegister()) {
778 compiler_->asm_.clear_i32_upper_half(reg.gp());
779 }
780#endif
781 if (needs_gp_pair_) {
782 NextLocation();
783 LiftoffRegister reg2 = LoadToReg(param_regs_ | LiftoffRegList{reg});
784 reg = LiftoffRegister::ForPair(reg.gp(), reg2.gp());
785 }
786 compiler_->asm_.PushRegister(kind_, reg);
787 }
788 }
789
790 private:
791 bool NextParam() {
792 if (param_idx_ >= num_params_) {
793 DCHECK_EQ(input_idx_, compiler_->descriptor_->InputCount());
794 return false;
795 }
796 kind_ = compiler_->asm_.local_kind(param_idx_++);
799 rc_ = reg_class_for(reg_kind_);
800 NextLocation();
801 return true;
802 }
803
804 void NextLocation() {
805 location_ = compiler_->descriptor_->GetInputLocation(input_idx_++);
806 }
807
808 LiftoffRegister CurrentRegister() {
809 DCHECK(!location_.IsAnyRegister());
810 return LiftoffRegister::from_external_code(rc_, reg_kind_,
811 location_.AsRegister());
812 }
813
814 void MaybeCollectRegister() {
815 if (!location_.IsRegister()) return;
816 DCHECK(!param_regs_.has(CurrentRegister()));
817 param_regs_.set(CurrentRegister());
818 }
819
820 LiftoffRegister LoadToReg(LiftoffRegList pinned) {
821 if (location_.IsRegister()) {
822 LiftoffRegister reg = CurrentRegister();
823 DCHECK(compiler_->asm_.cache_state()->is_free(reg));
824 // Unpin the register, to avoid depending on the set of allocatable
825 // registers being larger than the set of parameter registers.
826 param_regs_.clear(reg);
827 return reg;
828 }
829 DCHECK(location_.IsCallerFrameSlot());
830 LiftoffRegister reg = compiler_->asm_.GetUnusedRegister(rc_, pinned);
831 compiler_->asm_.LoadCallerFrameSlot(reg, -location_.AsCallerFrameSlot(),
832 reg_kind_);
833 return reg;
834 }
835
836 // Input 0 is the code target, 1 is the instance data.
837 static constexpr uint32_t kFirstInputIdx = 2;
838
839 LiftoffCompiler* compiler_;
840 const uint32_t num_params_;
841 uint32_t param_idx_{0};
848 LiftoffRegList param_regs_;
849 };
850
851 void StackCheck(FullDecoder* decoder, WasmCodePosition position) {
852 CODE_COMMENT("stack check");
853 if (!v8_flags.wasm_stack_checks) return;
854
855 LiftoffRegList regs_to_save = __ cache_state()->used_registers;
856 // The cached instance data will be reloaded separately.
857 if (__ cache_state()->cached_instance_data != no_reg) {
858 DCHECK(regs_to_save.has(__ cache_state()->cached_instance_data));
859 regs_to_save.clear(__ cache_state()->cached_instance_data);
860 }
861 SpilledRegistersForInspection* spilled_regs = nullptr;
862
863 OutOfLineSafepointInfo* safepoint_info =
864 zone_->New<OutOfLineSafepointInfo>(zone_);
865 __ cache_state()->GetTaggedSlotsForOOLCode(
866 &safepoint_info->slots, &safepoint_info->spills,
867 for_debugging_
870 if (V8_UNLIKELY(for_debugging_)) {
871 // When debugging, we do not just push all registers to the stack, but we
872 // spill them to their proper stack locations such that we can inspect
873 // them.
874 // The only exception is the cached memory start, which we just push
875 // before the stack check and pop afterwards.
876 regs_to_save = {};
877 if (__ cache_state()->cached_mem_start != no_reg) {
878 regs_to_save.set(__ cache_state()->cached_mem_start);
879 }
880 spilled_regs = GetSpilledRegistersForInspection();
881 }
882 out_of_line_code_.push_back(OutOfLineCode::StackCheck(
883 zone_, position, regs_to_save, __ cache_state()->cached_instance_data,
884 spilled_regs, safepoint_info, RegisterOOLDebugSideTableEntry(decoder)));
885 OutOfLineCode& ool = out_of_line_code_.back();
886 __ StackCheck(ool.label.get());
887 __ bind(ool.continuation.get());
888 }
889
890 void TierupCheck(FullDecoder* decoder, WasmCodePosition position,
891 int budget_used) {
892 if (for_debugging_ != kNotForDebugging) return;
893 SCOPED_CODE_COMMENT("tierup check");
894 budget_used += kTierUpCostForCheck;
895 // We never want to blow the entire budget at once.
896 const int max_budget_use = std::max(1, v8_flags.wasm_tiering_budget / 4);
897 if (budget_used > max_budget_use) budget_used = max_budget_use;
898
899 // We should always decrement the budget, and we don't expect integer
900 // overflows in the budget calculation.
901 DCHECK_LE(1, budget_used);
902
903 SpilledRegistersForInspection* spilled_regs = nullptr;
904
905 OutOfLineSafepointInfo* safepoint_info =
906 zone_->New<OutOfLineSafepointInfo>(zone_);
907 __ cache_state()->GetTaggedSlotsForOOLCode(
908 &safepoint_info->slots, &safepoint_info->spills,
910
911 LiftoffRegList regs_to_save = __ cache_state()->used_registers;
912 // The cached instance will be reloaded separately.
913 if (__ cache_state()->cached_instance_data != no_reg) {
914 DCHECK(regs_to_save.has(__ cache_state()->cached_instance_data));
915 regs_to_save.clear(__ cache_state()->cached_instance_data);
916 }
917
918 out_of_line_code_.push_back(OutOfLineCode::TierupCheck(
919 zone_, position, regs_to_save, __ cache_state()->cached_instance_data,
920 spilled_regs, safepoint_info, RegisterOOLDebugSideTableEntry(decoder)));
921 OutOfLineCode& ool = out_of_line_code_.back();
922
923 FREEZE_STATE(tierup_check);
924 __ CheckTierUp(declared_function_index(env_->module, func_index_),
925 budget_used, ool.label.get(), tierup_check);
926
927 __ bind(ool.continuation.get());
928 }
929
930 bool SpillLocalsInitially(FullDecoder* decoder, uint32_t num_params) {
931 int actual_locals = __ num_locals() - num_params;
932 DCHECK_LE(0, actual_locals);
933 constexpr int kNumCacheRegisters = kLiftoffAssemblerGpCacheRegs.Count();
934 // If we have many locals, we put them on the stack initially. This avoids
935 // having to spill them on merge points. Use of these initial values should
936 // be rare anyway.
937 if (actual_locals > kNumCacheRegisters / 2) return true;
938 // If there are locals which are not i32 or i64, we also spill all locals,
939 // because other types cannot be initialized to constants.
940 for (uint32_t param_idx = num_params; param_idx < __ num_locals();
941 ++param_idx) {
942 ValueKind kind = __ local_kind(param_idx);
943 if (kind != kI32 && kind != kI64) return true;
944 }
945 return false;
946 }
947
948 V8_NOINLINE V8_PRESERVE_MOST void TraceFunctionEntry(FullDecoder* decoder) {
949 CODE_COMMENT("trace function entry");
950 __ SpillAllRegisters();
952 __ pc_offset(), SourcePosition(decoder->position()), false);
953 __ CallBuiltin(Builtin::kWasmTraceEnter);
954 DefineSafepoint();
955 }
956
957 bool dynamic_tiering() {
958 return v8_flags.wasm_dynamic_tiering &&
960 (v8_flags.wasm_tier_up_filter == -1 ||
961 v8_flags.wasm_tier_up_filter == func_index_);
962 }
963
964 void StartFunctionBody(FullDecoder* decoder, Control* block) {
965 for (uint32_t i = 0; i < __ num_locals(); ++i) {
966 if (!CheckSupportedType(decoder, __ local_kind(i), "param")) return;
967 }
968
969 // Parameter 0 is the instance data.
970 uint32_t num_params =
971 static_cast<uint32_t>(decoder->sig_->parameter_count());
972
973 __ CodeEntry();
974
975 if (v8_flags.wasm_inlining) {
976 CODE_COMMENT("frame setup");
977 int declared_func_index =
978 func_index_ - env_->module->num_imported_functions;
979 DCHECK_GE(declared_func_index, 0);
980 __ CallFrameSetupStub(declared_func_index);
981 } else {
982 __ EnterFrame(StackFrame::WASM);
983 }
984 __ set_has_frame(true);
985 pc_offset_stack_frame_construction_ = __ PrepareStackFrame();
986 // {PrepareStackFrame} is the first platform-specific assembler method.
987 // If this failed, we can bail out immediately, avoiding runtime overhead
988 // and potential failures because of other unimplemented methods.
989 // A platform implementing {PrepareStackFrame} must ensure that we can
990 // finish compilation without errors even if we hit unimplemented
991 // LiftoffAssembler methods.
992 if (DidAssemblerBailout(decoder)) return;
993
994 // Input 0 is the call target, the trusted instance data is at 1.
995 [[maybe_unused]] constexpr int kInstanceDataParameterIndex = 1;
996 // Check that {kWasmImplicitArgRegister} matches our call descriptor.
999 descriptor_->GetInputLocation(kInstanceDataParameterIndex)
1000 .AsRegister()));
1001 __ cache_state() -> SetInstanceCacheRegister(kWasmImplicitArgRegister);
1002
1003 if (num_params) {
1004 CODE_COMMENT("process parameters");
1005 ParameterProcessor processor(this, num_params);
1006 processor.Process();
1007 }
1008 int params_size = __ TopSpillOffset();
1009
1010 // Initialize locals beyond parameters.
1011 if (num_params < __ num_locals()) CODE_COMMENT("init locals");
1012 if (SpillLocalsInitially(decoder, num_params)) {
1013 bool has_refs = false;
1014 for (uint32_t param_idx = num_params; param_idx < __ num_locals();
1015 ++param_idx) {
1016 ValueKind kind = __ local_kind(param_idx);
1017 has_refs |= is_reference(kind);
1018 __ PushStack(kind);
1019 }
1020 int spill_size = __ TopSpillOffset() - params_size;
1021 __ FillStackSlotsWithZero(params_size, spill_size);
1022
1023 // Initialize all reference type locals with ref.null.
1024 if (has_refs) {
1025 LiftoffRegList pinned;
1026 Register null_ref_reg =
1027 pinned.set(__ GetUnusedRegister(kGpReg, pinned).gp());
1028 Register wasm_null_ref_reg =
1029 pinned.set(__ GetUnusedRegister(kGpReg, pinned).gp());
1030 LoadNullValue(null_ref_reg, kWasmExternRef);
1031 LoadNullValue(wasm_null_ref_reg, kWasmAnyRef);
1032 for (uint32_t local_index = num_params; local_index < __ num_locals();
1033 ++local_index) {
1034 ValueType type = decoder->local_types_[local_index];
1035 if (type.is_reference()) {
1036 __ Spill(__ cache_state()->stack_state[local_index].offset(),
1037 type.use_wasm_null() ? LiftoffRegister(wasm_null_ref_reg)
1038 : LiftoffRegister(null_ref_reg),
1039 type.kind());
1040 }
1041 }
1042 }
1043 } else {
1044 for (uint32_t param_idx = num_params; param_idx < __ num_locals();
1045 ++param_idx) {
1046 ValueKind kind = __ local_kind(param_idx);
1047 // Anything which is not i32 or i64 requires spilling.
1048 DCHECK(kind == kI32 || kind == kI64);
1049 __ PushConstant(kind, int32_t{0});
1050 }
1051 }
1052
1053 DCHECK_EQ(__ num_locals(), __ cache_state()->stack_height());
1054
1055 if (V8_UNLIKELY(debug_sidetable_builder_)) {
1056 debug_sidetable_builder_->SetNumLocals(__ num_locals());
1057 }
1058
1059 if (V8_UNLIKELY(for_debugging_)) {
1060 __ ResetOSRTarget();
1061 if (V8_UNLIKELY(max_steps_)) {
1062 // Generate the single OOL code to jump to if {max_steps_} have been
1063 // executed.
1064 DCHECK_EQ(0, out_of_line_code_.size());
1065 // This trap is never intercepted (e.g. by a debugger), so we do not
1066 // need safepoint information (which would be difficult to compute if
1067 // the OOL code is shared).
1068 out_of_line_code_.push_back(OutOfLineCode::Trap(
1069 zone_, Builtin::kThrowWasmTrapUnreachable, decoder->position(),
1070 nullptr, nullptr, nullptr));
1071
1072 // Subtract 16 steps for the function call itself (including the
1073 // function prologue), plus 1 for each local (including parameters). Do
1074 // this only *after* setting up the frame completely, even though we
1075 // already executed the work then.
1076 CheckMaxSteps(decoder, 16 + __ num_locals());
1077 }
1078 } else {
1079 DCHECK(!max_steps_);
1080 }
1081
1082 // If debug code is enabled, assert that the first parameter is a
1083 // WasmTrustedInstanceData.
1084 if (v8_flags.debug_code) {
1085 SCOPED_CODE_COMMENT("Check instance data parameter type");
1086 LiftoffRegList pinned;
1087 Register scratch = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
1088 Register instance = pinned.set(LoadInstanceIntoRegister(pinned, scratch));
1089 // Load the map.
1090 __ LoadMap(scratch, instance);
1091 // Load the instance type.
1092 __ Load(LiftoffRegister{scratch}, scratch, no_reg,
1093 wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset),
1094 LoadType::kI32Load16U);
1095 // If not WASM_TRUSTED_INSTANCE_DATA_TYPE -> error.
1096 Label ok;
1097 FreezeCacheState frozen{asm_};
1098 __ emit_i32_cond_jumpi(kEqual, &ok, scratch,
1099 WASM_TRUSTED_INSTANCE_DATA_TYPE, frozen);
1100 __ AssertUnreachable(AbortReason::kUnexpectedInstanceType);
1101 __ bind(&ok);
1102 }
1103
1104 // The function-prologue stack check is associated with position 0, which
1105 // is never a position of any instruction in the function.
1106 StackCheck(decoder, 0);
1107
1108 if (V8_UNLIKELY(v8_flags.trace_wasm)) TraceFunctionEntry(decoder);
1109 }
1110
1111 void GenerateOutOfLineCode(OutOfLineCode* ool) {
1112 CODE_COMMENT((std::string("OOL: ") + Builtins::name(ool->builtin)).c_str());
1113 __ bind(ool->label.get());
1114 const bool is_stack_check =
1115 ool->builtin == Builtin::kWasmStackGuard ||
1116 ool->builtin == Builtin::kWasmGrowableStackGuard;
1117 const bool is_tierup = ool->builtin == Builtin::kWasmTriggerTierUp;
1118
1119 if (!ool->regs_to_save.is_empty()) {
1120 __ PushRegisters(ool->regs_to_save);
1121 }
1122 if (V8_UNLIKELY(ool->spilled_registers != nullptr)) {
1123 for (auto& entry : ool->spilled_registers->entries) {
1124 // We should not push and spill the same register.
1125 DCHECK(!ool->regs_to_save.has(entry.reg));
1126 __ Spill(entry.offset, entry.reg, entry.kind);
1127 }
1128 }
1129
1130 if (ool->builtin == Builtin::kWasmGrowableStackGuard) {
1131 WasmGrowableStackGuardDescriptor descriptor;
1132 DCHECK_EQ(0, descriptor.GetStackParameterCount());
1133 DCHECK_EQ(1, descriptor.GetRegisterParameterCount());
1134 Register param_reg = descriptor.GetRegisterParameter(0);
1135 __ LoadConstant(LiftoffRegister(param_reg),
1136 WasmValue::ForUintPtr(descriptor_->ParameterSlotCount() *
1138 }
1139
1141 __ pc_offset(), SourcePosition(ool->position), true);
1142 __ CallBuiltin(ool->builtin);
1143 // It is safe to not check for existing safepoint at this address since we
1144 // just emitted a call.
1145 auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_);
1146
1147 if (ool->safepoint_info) {
1148 for (auto index : ool->safepoint_info->slots) {
1149 safepoint.DefineTaggedStackSlot(index);
1150 }
1151
1152 int total_frame_size = __ GetTotalFrameSize();
1153 // {total_frame_size} is the highest offset from the FP that is used to
1154 // store a value. The offset of the first spill slot should therefore be
1155 // {(total_frame_size / kSystemPointerSize) + 1}. However, spill slots
1156 // don't start at offset '0' but at offset '-1' (or
1157 // {-kSystemPointerSize}). Therefore we have to add another '+ 1' to the
1158 // index of the first spill slot.
1159 int index = (total_frame_size / kSystemPointerSize) + 2;
1160
1161 __ RecordSpillsInSafepoint(safepoint, ool->regs_to_save,
1162 ool->safepoint_info->spills, index);
1163 }
1164
1165 DCHECK_EQ(!debug_sidetable_builder_, !ool->debug_sidetable_entry_builder);
1166 if (V8_UNLIKELY(ool->debug_sidetable_entry_builder)) {
1167 ool->debug_sidetable_entry_builder->set_pc_offset(__ pc_offset());
1168 }
1169 DCHECK_EQ(ool->continuation.get()->is_bound(), is_stack_check || is_tierup);
1170 if (is_stack_check) {
1171 MaybeOSR();
1172 }
1173 if (!ool->regs_to_save.is_empty()) __ PopRegisters(ool->regs_to_save);
1174 if (is_stack_check || is_tierup) {
1175 if (V8_UNLIKELY(ool->spilled_registers != nullptr)) {
1176 DCHECK(for_debugging_);
1177 for (auto& entry : ool->spilled_registers->entries) {
1178 __ Fill(entry.reg, entry.offset, entry.kind);
1179 }
1180 }
1181 if (ool->cached_instance_data != no_reg) {
1182 __ LoadInstanceDataFromFrame(ool->cached_instance_data);
1183 }
1184 __ emit_jump(ool->continuation.get());
1185 } else {
1186 __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap);
1187 }
1188 }
1189
1190 void FinishFunction(FullDecoder* decoder) {
1191 if (DidAssemblerBailout(decoder)) return;
1192 __ AlignFrameSize();
1193#if DEBUG
1194 int frame_size = __ GetTotalFrameSize();
1195#endif
1196 for (OutOfLineCode& ool : out_of_line_code_) {
1197 GenerateOutOfLineCode(&ool);
1198 }
1199 DCHECK_EQ(frame_size, __ GetTotalFrameSize());
1200 __ PatchPrepareStackFrame(pc_offset_stack_frame_construction_,
1201 &safepoint_table_builder_, v8_flags.wasm_inlining,
1202 descriptor_->ParameterSlotCount());
1203 __ FinishCode();
1204 safepoint_table_builder_.Emit(&asm_, __ GetTotalFrameSlotCountForGC());
1205 // Emit the handler table.
1206 if (!handlers_.empty()) {
1208 for (auto& handler : handlers_) {
1209 HandlerTable::EmitReturnEntry(&asm_, handler.pc_offset,
1210 handler.handler.get()->pos());
1211 }
1212 }
1213 __ MaybeEmitOutOfLineConstantPool();
1214 // The previous calls may have also generated a bailout.
1215 DidAssemblerBailout(decoder);
1216 DCHECK_EQ(num_exceptions_, 0);
1217
1218 if (v8_flags.wasm_inlining && !encountered_call_instructions_.empty()) {
1219 // Update the call targets stored in the WasmModule.
1220 TypeFeedbackStorage& type_feedback = env_->module->type_feedback;
1221 base::MutexGuard mutex_guard(&type_feedback.mutex);
1222 FunctionTypeFeedback& function_feedback =
1223 type_feedback.feedback_for_function[func_index_];
1224 function_feedback.liftoff_frame_size = __ GetTotalFrameSize();
1225 base::OwnedVector<uint32_t>& call_targets =
1226 function_feedback.call_targets;
1227 if (call_targets.empty()) {
1228 call_targets = base::OwnedCopyOf(encountered_call_instructions_);
1229 } else {
1230 DCHECK_EQ(call_targets.as_vector(),
1231 base::VectorOf(encountered_call_instructions_));
1232 }
1233 }
1234
1235 if (frame_description_) {
1236 frame_description_->total_frame_size = __ GetTotalFrameSize();
1237 }
1238 }
1239
1240 void OnFirstError(FullDecoder* decoder) {
1241 if (!did_bailout()) bailout_reason_ = kDecodeError;
1242 UnuseLabels(decoder);
1243 asm_.AbortCompilation();
1244 }
1245
1246 // Rule of thumb: an instruction is "heavy" when its runtime is linear in
1247 // some random variable that the fuzzer generates.
1248#define FUZZER_HEAVY_INSTRUCTION \
1249 do { \
1250 if (V8_UNLIKELY(max_steps_ != nullptr)) { \
1251 CheckMaxSteps(decoder, kHeavyInstructionSteps); \
1252 } \
1253 } while (false)
1254
1255 V8_NOINLINE void CheckMaxSteps(FullDecoder* decoder, int steps_done = 1) {
1256 DCHECK_LE(1, steps_done);
1257 SCOPED_CODE_COMMENT("check max steps");
1258 LiftoffRegList pinned;
1259 LiftoffRegister max_steps = pinned.set(__ GetUnusedRegister(kGpReg, {}));
1260 LiftoffRegister max_steps_addr =
1261 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1262 {
1263 FREEZE_STATE(frozen);
1264 __ LoadConstant(
1265 max_steps_addr,
1266 WasmValue::ForUintPtr(reinterpret_cast<uintptr_t>(max_steps_)));
1267 __ Load(max_steps, max_steps_addr.gp(), no_reg, 0, LoadType::kI32Load);
1268 // Subtract first (and store the result), so the caller sees that
1269 // max_steps ran negative. Since we never subtract too much at once, we
1270 // cannot underflow.
1271 DCHECK_GE(kMaxInt / 16, steps_done); // An arbitrary limit.
1272 __ emit_i32_subi(max_steps.gp(), max_steps.gp(), steps_done);
1273 __ Store(max_steps_addr.gp(), no_reg, 0, max_steps, StoreType::kI32Store,
1274 pinned);
1275 // Abort if max steps have been executed.
1276 DCHECK_EQ(Builtin::kThrowWasmTrapUnreachable,
1277 out_of_line_code_.front().builtin);
1278 Label* trap_label = out_of_line_code_.front().label.get();
1279 __ emit_i32_cond_jumpi(kLessThan, trap_label, max_steps.gp(), 0, frozen);
1280 }
1281 }
1282
1283 V8_NOINLINE void EmitDebuggingInfo(FullDecoder* decoder, WasmOpcode opcode) {
1284 DCHECK(for_debugging_);
1285
1286 // Snapshot the value types (from the decoder) here, for potentially
1287 // building a debug side table entry later. Arguments will have been popped
1288 // from the stack later (when we need them), and Liftoff does not keep
1289 // precise type information.
1290 stack_value_types_for_debugging_ = GetStackValueTypesForDebugging(decoder);
1291
1292 if (!WasmOpcodes::IsBreakable(opcode)) return;
1293
1294 bool has_breakpoint = false;
1295 if (next_breakpoint_ptr_) {
1296 if (*next_breakpoint_ptr_ == 0) {
1297 // A single breakpoint at offset 0 indicates stepping.
1298 DCHECK_EQ(next_breakpoint_ptr_ + 1, next_breakpoint_end_);
1299 has_breakpoint = true;
1300 } else {
1301 while (next_breakpoint_ptr_ != next_breakpoint_end_ &&
1302 *next_breakpoint_ptr_ < decoder->position()) {
1303 // Skip unreachable breakpoints.
1305 }
1306 if (next_breakpoint_ptr_ == next_breakpoint_end_) {
1308 } else if (*next_breakpoint_ptr_ == decoder->position()) {
1309 has_breakpoint = true;
1310 }
1311 }
1312 }
1313 if (has_breakpoint) {
1314 CODE_COMMENT("breakpoint");
1315 EmitBreakpoint(decoder);
1316 // Once we emitted an unconditional breakpoint, we don't need to check
1317 // function entry breaks any more.
1319 } else if (!did_function_entry_break_checks_) {
1321 CODE_COMMENT("check function entry break");
1322 Label do_break;
1323 Label no_break;
1324 Register flag = __ GetUnusedRegister(kGpReg, {}).gp();
1325
1326 // Check the "hook on function call" flag. If set, trigger a break.
1327 LOAD_INSTANCE_FIELD(flag, HookOnFunctionCallAddress, kSystemPointerSize,
1328 {});
1329 FREEZE_STATE(frozen);
1330 __ Load(LiftoffRegister{flag}, flag, no_reg, 0, LoadType::kI32Load8U, {});
1331 __ emit_cond_jump(kNotZero, &do_break, kI32, flag, no_reg, frozen);
1332
1333 // Check if we should stop on "script entry".
1334 LOAD_INSTANCE_FIELD(flag, BreakOnEntry, kUInt8Size, {});
1335 __ emit_cond_jump(kZero, &no_break, kI32, flag, no_reg, frozen);
1336
1337 __ bind(&do_break);
1338 EmitBreakpoint(decoder);
1339 __ bind(&no_break);
1340 } else if (dead_breakpoint_ == decoder->position()) {
1341 DCHECK(!next_breakpoint_ptr_ ||
1342 *next_breakpoint_ptr_ != dead_breakpoint_);
1343 // The top frame is paused at this position, but the breakpoint was
1344 // removed. Adding a dead breakpoint here ensures that the source
1345 // position exists, and that the offset to the return address is the
1346 // same as in the old code.
1347 CODE_COMMENT("dead breakpoint");
1348 Label cont;
1349 __ emit_jump(&cont);
1350 EmitBreakpoint(decoder);
1351 __ bind(&cont);
1352 }
1353 if (V8_UNLIKELY(max_steps_ != nullptr)) {
1354 CheckMaxSteps(decoder);
1355 }
1356 }
1357
1358 void NextInstruction(FullDecoder* decoder, WasmOpcode opcode) {
1359 TraceCacheState(decoder);
1360 SLOW_DCHECK(__ ValidateCacheState());
1363 ? decoder->read_prefixed_opcode<ValidationTag>(decoder->pc()).first
1364 : opcode));
1365
1366 if (!has_outstanding_op() && decoder->control_at(0)->reachable()) {
1367 // Decoder stack and liftoff stack have to be in sync if current code
1368 // path is reachable.
1369 DCHECK_EQ(decoder->stack_size() + __ num_locals() + num_exceptions_,
1370 __ cache_state()->stack_state.size());
1371 }
1372
1373 // Add a single check, so that the fast path can be inlined while
1374 // {EmitDebuggingInfo} stays outlined.
1375 if (V8_UNLIKELY(for_debugging_)) EmitDebuggingInfo(decoder, opcode);
1376 }
1377
1378 void EmitBreakpoint(FullDecoder* decoder) {
1379 DCHECK(for_debugging_);
1381 __ pc_offset(), SourcePosition(decoder->position()), true);
1382 __ CallBuiltin(Builtin::kWasmDebugBreak);
1383 DefineSafepointWithCalleeSavedRegisters();
1384 RegisterDebugSideTableEntry(decoder,
1385 DebugSideTableBuilder::kAllowRegisters);
1386 MaybeOSR();
1387 }
1388
1389 void PushControl(Control* block) {
1390 // The Liftoff stack includes implicit exception refs stored for catch
1391 // blocks, so that they can be rethrown.
1392 block->num_exceptions = num_exceptions_;
1393 }
1394
1395 void Block(FullDecoder* decoder, Control* block) { PushControl(block); }
1396
1397 void Loop(FullDecoder* decoder, Control* loop) {
1398 // Before entering a loop, spill all locals to the stack, in order to free
1399 // the cache registers, and to avoid unnecessarily reloading stack values
1400 // into registers at branches.
1401 // TODO(clemensb): Come up with a better strategy here, involving
1402 // pre-analysis of the function.
1403 __ SpillLocals();
1404
1405 __ SpillLoopArgs(loop->start_merge.arity);
1406
1407 // Loop labels bind at the beginning of the block.
1408 __ bind(loop->label.get());
1409
1410 // Save the current cache state for the merge when jumping to this loop.
1411 loop->label_state.Split(*__ cache_state());
1412
1413 PushControl(loop);
1414
1415 if (!dynamic_tiering()) {
1416 // When the budget-based tiering mechanism is enabled, use that to
1417 // check for interrupt requests; otherwise execute a stack check in the
1418 // loop header.
1419 StackCheck(decoder, decoder->position());
1420 }
1421 }
1422
1423 void Try(FullDecoder* decoder, Control* block) {
1424 block->try_info = zone_->New<TryInfo>(zone_);
1425 PushControl(block);
1426 }
1427
1428 // Load the property in {kReturnRegister0}.
1429 LiftoffRegister GetExceptionProperty(const VarState& exception,
1430 RootIndex root_index) {
1431 DCHECK(root_index == RootIndex::kwasm_exception_tag_symbol ||
1432 root_index == RootIndex::kwasm_exception_values_symbol);
1433
1434 LiftoffRegList pinned;
1435 LiftoffRegister tag_symbol_reg =
1436 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1437 LoadExceptionSymbol(tag_symbol_reg.gp(), pinned, root_index);
1438 LiftoffRegister context_reg =
1439 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1440 LOAD_TAGGED_PTR_INSTANCE_FIELD(context_reg.gp(), NativeContext, pinned);
1441
1442 VarState tag_symbol{kRef, tag_symbol_reg, 0};
1443 VarState context{kRef, context_reg, 0};
1444
1445 CallBuiltin(Builtin::kWasmGetOwnProperty,
1446 MakeSig::Returns(kRef).Params(kRef, kRef, kRef),
1447 {exception, tag_symbol, context}, kNoSourcePosition);
1448
1449 return LiftoffRegister(kReturnRegister0);
1450 }
1451
1452 void CatchException(FullDecoder* decoder, const TagIndexImmediate& imm,
1453 Control* block, base::Vector<Value> values) {
1454 DCHECK(block->is_try_catch());
1455 __ emit_jump(block->label.get());
1456
1457 // This is the last use of this label. Reuse the field for the label of the
1458 // next catch block, and jump there if the tag does not match.
1459 __ bind(&block->try_info->catch_label);
1460 block->try_info->catch_label.Unuse();
1461 block->try_info->catch_label.UnuseNear();
1462
1463 __ cache_state()->Split(block->try_info->catch_state);
1464
1465 CODE_COMMENT("load caught exception tag");
1466 DCHECK_EQ(__ cache_state()->stack_state.back().kind(), kRef);
1467 LiftoffRegister caught_tag =
1468 GetExceptionProperty(__ cache_state()->stack_state.back(),
1469 RootIndex::kwasm_exception_tag_symbol);
1470 LiftoffRegList pinned;
1471 pinned.set(caught_tag);
1472
1473 CODE_COMMENT("load expected exception tag");
1474 Register imm_tag = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
1475 LOAD_TAGGED_PTR_INSTANCE_FIELD(imm_tag, TagsTable, pinned);
1476 __ LoadTaggedPointer(
1477 imm_tag, imm_tag, no_reg,
1479
1480 CODE_COMMENT("compare tags");
1481
1482 if (imm.tag->sig->parameter_count() == 1 &&
1483 imm.tag->sig->GetParam(0) == kWasmExternRef) {
1484 // Check for the special case where the tag is WebAssembly.JSTag and the
1485 // exception is not a WebAssembly.Exception. In this case the exception is
1486 // caught and pushed on the operand stack.
1487 // Only perform this check if the tag signature is the same as
1488 // the JSTag signature, i.e. a single externref, otherwise we know
1489 // statically that it cannot be the JSTag.
1490 LiftoffRegister undefined =
1491 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1492 __ LoadFullPointer(
1493 undefined.gp(), kRootRegister,
1494 IsolateData::root_slot_offset(RootIndex::kUndefinedValue));
1495 LiftoffRegister js_tag = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1496 LOAD_TAGGED_PTR_INSTANCE_FIELD(js_tag.gp(), NativeContext, pinned);
1497 __ LoadTaggedPointer(
1498 js_tag.gp(), js_tag.gp(), no_reg,
1499 NativeContext::SlotOffset(Context::WASM_JS_TAG_INDEX));
1500 __ LoadTaggedPointer(
1501 js_tag.gp(), js_tag.gp(), no_reg,
1502 wasm::ObjectAccess::ToTagged(WasmTagObject::kTagOffset));
1503 {
1504 LiftoffAssembler::CacheState initial_state(zone_);
1505 LiftoffAssembler::CacheState end_state(zone_);
1506 Label js_exception;
1507 Label done;
1508 Label uncaught;
1509 initial_state.Split(*__ cache_state());
1510 {
1511 FREEZE_STATE(state_merged_explicitly);
1512 // If the tag is undefined, this is not a wasm exception. Go to a
1513 // different block to process the JS exception. Otherwise compare it
1514 // with the expected tag.
1515 __ emit_cond_jump(kEqual, &js_exception, kRefNull, caught_tag.gp(),
1516 undefined.gp(), state_merged_explicitly);
1517 __ emit_cond_jump(kNotEqual, &uncaught, kRefNull, imm_tag,
1518 caught_tag.gp(), state_merged_explicitly);
1519 }
1520 // Case 1: A wasm exception with a matching tag.
1521 GetExceptionValues(decoder, __ cache_state()->stack_state.back(),
1522 imm.tag);
1523 // GetExceptionValues modified the cache state. Remember the new state
1524 // to merge the end state of case 2 into it.
1525 end_state.Steal(*__ cache_state());
1526 __ emit_jump(&done);
1527
1528 __ bind(&js_exception);
1529 __ cache_state()->Split(initial_state);
1530 {
1531 FREEZE_STATE(state_merged_explicitly);
1532 __ emit_cond_jump(kNotEqual, &uncaught, kRefNull, imm_tag,
1533 js_tag.gp(), state_merged_explicitly);
1534 }
1535 // Case 2: A JS exception, and the expected tag is JSTag.
1536 // TODO(thibaudm): Can we avoid some state splitting/stealing by
1537 // reserving this register earlier and not modifying the state in this
1538 // block?
1539 LiftoffRegister exception = __ PeekToRegister(0, pinned);
1540 __ PushRegister(kRef, exception);
1541 // The exception is now on the stack twice: once as an implicit operand
1542 // for rethrow, and once as the "unpacked" value.
1543 __ MergeFullStackWith(end_state);
1544 __ emit_jump(&done);
1545
1546 // Case 3: Either a wasm exception with a mismatching tag, or a JS
1547 // exception but the expected tag is not JSTag.
1548 __ bind(&uncaught);
1549 __ cache_state()->Steal(initial_state);
1550 __ MergeFullStackWith(block->try_info->catch_state);
1551 __ emit_jump(&block->try_info->catch_label);
1552
1553 __ bind(&done);
1554 __ cache_state()->Steal(end_state);
1555 }
1556 } else {
1557 {
1558 FREEZE_STATE(frozen);
1559 Label caught;
1560 __ emit_cond_jump(kEqual, &caught, kRefNull, imm_tag, caught_tag.gp(),
1561 frozen);
1562 // The tags don't match, merge the current state into the catch state
1563 // and jump to the next handler.
1564 __ MergeFullStackWith(block->try_info->catch_state);
1565 __ emit_jump(&block->try_info->catch_label);
1566 __ bind(&caught);
1567 }
1568 GetExceptionValues(decoder, __ cache_state()->stack_state.back(),
1569 imm.tag);
1570 }
1571 if (!block->try_info->in_handler) {
1572 block->try_info->in_handler = true;
1574 }
1575 }
1576
1577 void Rethrow(FullDecoder* decoder, const VarState& exception) {
1578 CallBuiltin(Builtin::kWasmRethrow, MakeSig::Params(kRef), {exception},
1579 decoder->position());
1580 }
1581
1582 void Delegate(FullDecoder* decoder, uint32_t depth, Control* block) {
1583 DCHECK_EQ(block, decoder->control_at(0));
1584 Control* target = decoder->control_at(depth);
1585 DCHECK(block->is_incomplete_try());
1586 __ bind(&block->try_info->catch_label);
1587 if (block->try_info->catch_reached) {
1588 __ cache_state()->Steal(block->try_info->catch_state);
1589 if (depth == decoder->control_depth() - 1) {
1590 // Delegate to the caller, do not emit a landing pad.
1591 Rethrow(decoder, __ cache_state()->stack_state.back());
1592 MaybeOSR();
1593 } else {
1594 DCHECK(target->is_incomplete_try());
1595 if (target->try_info->catch_reached) {
1596 __ MergeStackWith(target->try_info->catch_state, 1,
1598 } else {
1599 target->try_info->catch_state = __ MergeIntoNewState(
1600 __ num_locals(), 1, target->stack_depth + target->num_exceptions);
1601 target->try_info->catch_reached = true;
1602 }
1603 __ emit_jump(&target->try_info->catch_label);
1604 }
1605 }
1606 }
1607
1608 void Rethrow(FullDecoder* decoder, Control* try_block) {
1609 int index = try_block->try_info->catch_state.stack_height() - 1;
1610 auto& exception = __ cache_state()->stack_state[index];
1611 Rethrow(decoder, exception);
1612 int pc_offset = __ pc_offset();
1613 MaybeOSR();
1614 EmitLandingPad(decoder, pc_offset);
1615 }
1616
1617 void CatchAll(FullDecoder* decoder, Control* block) {
1618 DCHECK(block->is_try_catchall() || block->is_try_catch());
1619 DCHECK_EQ(decoder->control_at(0), block);
1620 __ bind(&block->try_info->catch_label);
1621 __ cache_state()->Split(block->try_info->catch_state);
1622 if (!block->try_info->in_handler) {
1623 block->try_info->in_handler = true;
1625 }
1626 }
1627
1628 void TryTable(FullDecoder* decoder, Control* block) {
1629 block->try_info = zone_->New<TryInfo>(zone_);
1630 PushControl(block);
1631 }
1632
1633 void CatchCase(FullDecoder* decoder, Control* block,
1634 const CatchCase& catch_case, base::Vector<Value> values) {
1635 DCHECK(block->is_try_table());
1636
1637 // This is the last use of this label. Reuse the field for the label of the
1638 // next catch block, and jump there if the tag does not match.
1639 __ bind(&block->try_info->catch_label);
1640 block->try_info->catch_label.Unuse();
1641 block->try_info->catch_label.UnuseNear();
1642 __ cache_state()->Split(block->try_info->catch_state);
1643
1644 if (catch_case.kind == kCatchAll || catch_case.kind == kCatchAllRef) {
1645 // The landing pad pushed the exception on the stack, so keep
1646 // it there for {kCatchAllRef}, and drop it for {kCatchAll}.
1647 if (catch_case.kind == kCatchAll) {
1648 __ DropValues(1);
1649 }
1650 BrOrRet(decoder, catch_case.br_imm.depth);
1651 return;
1652 }
1653
1654 CODE_COMMENT("load caught exception tag");
1655 DCHECK_EQ(__ cache_state()->stack_state.back().kind(), kRef);
1656 LiftoffRegister caught_tag =
1657 GetExceptionProperty(__ cache_state()->stack_state.back(),
1658 RootIndex::kwasm_exception_tag_symbol);
1659 LiftoffRegList pinned;
1660 pinned.set(caught_tag);
1661
1662 CODE_COMMENT("load expected exception tag");
1663 Register imm_tag = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
1664 LOAD_TAGGED_PTR_INSTANCE_FIELD(imm_tag, TagsTable, pinned);
1665 __ LoadTaggedPointer(imm_tag, imm_tag, no_reg,
1667 catch_case.maybe_tag.tag_imm.index));
1668
1669 VarState exn = __ cache_state() -> stack_state.back();
1670
1671 CODE_COMMENT("compare tags");
1672 if (catch_case.maybe_tag.tag_imm.tag->sig->parameter_count() == 1 &&
1673 catch_case.maybe_tag.tag_imm.tag->sig->GetParam(0) == kWasmExternRef) {
1674 // Check for the special case where the tag is WebAssembly.JSTag and the
1675 // exception is not a WebAssembly.Exception. In this case the exception is
1676 // caught and pushed on the operand stack.
1677 // Only perform this check if the tag signature is the same as
1678 // the JSTag signature, i.e. a single externref, otherwise we know
1679 // statically that it cannot be the JSTag.
1680 LiftoffRegister undefined =
1681 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1682 __ LoadFullPointer(
1683 undefined.gp(), kRootRegister,
1684 IsolateData::root_slot_offset(RootIndex::kUndefinedValue));
1685 LiftoffRegister js_tag = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
1686 LOAD_TAGGED_PTR_INSTANCE_FIELD(js_tag.gp(), NativeContext, pinned);
1687 __ LoadTaggedPointer(
1688 js_tag.gp(), js_tag.gp(), no_reg,
1689 NativeContext::SlotOffset(Context::WASM_JS_TAG_INDEX));
1690 __ LoadTaggedPointer(
1691 js_tag.gp(), js_tag.gp(), no_reg,
1692 wasm::ObjectAccess::ToTagged(WasmTagObject::kTagOffset));
1693 {
1694 LiftoffAssembler::CacheState initial_state(zone_);
1695 LiftoffAssembler::CacheState end_state(zone_);
1696 Label js_exception;
1697 Label done;
1698 Label uncaught;
1699 initial_state.Split(*__ cache_state());
1700 {
1701 FREEZE_STATE(state_merged_explicitly);
1702 // If the tag is undefined, this is not a wasm exception. Go to a
1703 // different block to process the JS exception. Otherwise compare it
1704 // with the expected tag.
1705 __ emit_cond_jump(kEqual, &js_exception, kRefNull, caught_tag.gp(),
1706 undefined.gp(), state_merged_explicitly);
1707 __ emit_cond_jump(kNotEqual, &uncaught, kRefNull, imm_tag,
1708 caught_tag.gp(), state_merged_explicitly);
1709 }
1710 // Case 1: A wasm exception with a matching tag.
1711 CODE_COMMENT("unpack exception");
1712 GetExceptionValues(decoder, __ cache_state()->stack_state.back(),
1713 catch_case.maybe_tag.tag_imm.tag);
1714 // GetExceptionValues modified the cache state. Remember the new state
1715 // to merge the end state of case 2 into it.
1716 end_state.Steal(*__ cache_state());
1717 __ emit_jump(&done);
1718
1719 __ bind(&js_exception);
1720 __ cache_state() -> Split(initial_state);
1721 {
1722 FREEZE_STATE(state_merged_explicitly);
1723 __ emit_cond_jump(kNotEqual, &uncaught, kRefNull, imm_tag,
1724 js_tag.gp(), state_merged_explicitly);
1725 }
1726 // Case 2: A JS exception, and the expected tag is JSTag.
1727 // TODO(thibaudm): Can we avoid some state splitting/stealing by
1728 // reserving this register earlier and not modifying the state in this
1729 // block?
1730 CODE_COMMENT("JS exception caught by JSTag");
1731 LiftoffRegister exception = __ PeekToRegister(0, pinned);
1732 __ PushRegister(kRefNull, exception);
1733 // The exception is now on the stack twice: once as an implicit operand
1734 // for rethrow, and once as the "unpacked" value.
1735 __ MergeFullStackWith(end_state);
1736 __ emit_jump(&done);
1737
1738 // Case 3: Either a wasm exception with a mismatching tag, or a JS
1739 // exception but the expected tag is not JSTag.
1740 __ bind(&uncaught);
1741 __ cache_state() -> Steal(initial_state);
1742 __ MergeFullStackWith(block->try_info->catch_state);
1743 __ emit_jump(&block->try_info->catch_label);
1744
1745 __ bind(&done);
1746 __ cache_state() -> Steal(end_state);
1747 }
1748 } else {
1749 {
1750 FREEZE_STATE(frozen);
1751 Label caught;
1752 __ emit_cond_jump(kEqual, &caught, kRefNull, imm_tag, caught_tag.gp(),
1753 frozen);
1754 // The tags don't match, merge the current state into the catch state
1755 // and jump to the next handler.
1756 __ MergeFullStackWith(block->try_info->catch_state);
1757 __ emit_jump(&block->try_info->catch_label);
1758 __ bind(&caught);
1759 }
1760
1761 CODE_COMMENT("unpack exception");
1762 pinned = {};
1763 GetExceptionValues(decoder, __ cache_state()->stack_state.back(),
1764 catch_case.maybe_tag.tag_imm.tag);
1765 }
1766
1767 if (catch_case.kind == kCatchRef) {
1768 // Append the exception on the operand stack.
1769 DCHECK(exn.is_stack());
1770 auto rc = reg_class_for(kRefNull);
1771 LiftoffRegister reg = __ GetUnusedRegister(rc, pinned);
1772 __ Fill(reg, exn.offset(), kRefNull);
1773 __ PushRegister(kRefNull, reg);
1774 }
1775 // There is an extra copy of the exception at this point, below the unpacked
1776 // values (if any). It will be dropped in the branch below.
1777 BrOrRet(decoder, catch_case.br_imm.depth);
1778 bool is_last = &catch_case == &block->catch_cases.last();
1779 if (is_last && !decoder->HasCatchAll(block)) {
1780 __ bind(&block->try_info->catch_label);
1781 __ cache_state()->Steal(block->try_info->catch_state);
1782 ThrowRef(decoder, nullptr);
1783 }
1784 }
1785
1786 void ThrowRef(FullDecoder* decoder, Value*) {
1787 // Like Rethrow, but pops the exception from the stack.
1788 VarState exn = __ PopVarState();
1789 CallBuiltin(Builtin::kWasmThrowRef, MakeSig::Params(kRef), {exn},
1790 decoder->position());
1791 int pc_offset = __ pc_offset();
1792 MaybeOSR();
1793 EmitLandingPad(decoder, pc_offset);
1794 }
1795
1796 // Before emitting the conditional branch, {will_freeze} will be initialized
1797 // to prevent cache state changes in conditionally executed code.
1798 void JumpIfFalse(FullDecoder* decoder, Label* false_dst,
1799 std::optional<FreezeCacheState>& will_freeze) {
1800 DCHECK(!will_freeze.has_value());
1801 Condition cond =
1802 test_and_reset_outstanding_op(kExprI32Eqz) ? kNotZero : kZero;
1803
1804 if (!has_outstanding_op()) {
1805 // Unary comparison.
1806 Register value = __ PopToRegister().gp();
1807 will_freeze.emplace(asm_);
1808 __ emit_cond_jump(cond, false_dst, kI32, value, no_reg, *will_freeze);
1809 return;
1810 }
1811
1812 // Binary comparison of i32 values.
1813 cond = Negate(GetCompareCondition(outstanding_op_));
1815 VarState rhs_slot = __ cache_state()->stack_state.back();
1816 if (rhs_slot.is_const()) {
1817 // Compare to a constant.
1818 int32_t rhs_imm = rhs_slot.i32_const();
1819 __ cache_state()->stack_state.pop_back();
1820 Register lhs = __ PopToRegister().gp();
1821 will_freeze.emplace(asm_);
1822 __ emit_i32_cond_jumpi(cond, false_dst, lhs, rhs_imm, *will_freeze);
1823 return;
1824 }
1825
1826 Register rhs = __ PopToRegister().gp();
1827 VarState lhs_slot = __ cache_state()->stack_state.back();
1828 if (lhs_slot.is_const()) {
1829 // Compare a constant to an arbitrary value.
1830 int32_t lhs_imm = lhs_slot.i32_const();
1831 __ cache_state()->stack_state.pop_back();
1832 // Flip the condition, because {lhs} and {rhs} are swapped.
1833 will_freeze.emplace(asm_);
1834 __ emit_i32_cond_jumpi(Flip(cond), false_dst, rhs, lhs_imm, *will_freeze);
1835 return;
1836 }
1837
1838 // Compare two arbitrary values.
1839 Register lhs = __ PopToRegister(LiftoffRegList{rhs}).gp();
1840 will_freeze.emplace(asm_);
1841 __ emit_cond_jump(cond, false_dst, kI32, lhs, rhs, *will_freeze);
1842 }
1843
1844 void If(FullDecoder* decoder, const Value& cond, Control* if_block) {
1845 DCHECK_EQ(if_block, decoder->control_at(0));
1846 DCHECK(if_block->is_if());
1847
1848 // Allocate the else state.
1849 if_block->else_state = zone_->New<ElseState>(zone_);
1850
1851 // Test the condition on the value stack, jump to else if zero.
1852 std::optional<FreezeCacheState> frozen;
1853 JumpIfFalse(decoder, if_block->else_state->label.get(), frozen);
1854 frozen.reset();
1855
1856 // Store the state (after popping the value) for executing the else branch.
1857 if_block->else_state->state.Split(*__ cache_state());
1858
1859 PushControl(if_block);
1860 }
1861
1862 void FallThruTo(FullDecoder* decoder, Control* c) {
1863 DCHECK_IMPLIES(c->is_try_catchall(), !c->end_merge.reached);
1864 if (c->end_merge.reached) {
1865 __ MergeStackWith(c->label_state, c->br_merge()->arity,
1867 } else {
1868 c->label_state = __ MergeIntoNewState(__ num_locals(), c->end_merge.arity,
1869 c->stack_depth + c->num_exceptions);
1870 }
1871 __ emit_jump(c->label.get());
1872 TraceCacheState(decoder);
1873 }
1874
1875 void FinishOneArmedIf(FullDecoder* decoder, Control* c) {
1876 DCHECK(c->is_onearmed_if());
1877 if (c->end_merge.reached) {
1878 // Someone already merged to the end of the if. Merge both arms into that.
1879 if (c->reachable()) {
1880 // Merge the if state into the end state.
1881 __ MergeFullStackWith(c->label_state);
1882 __ emit_jump(c->label.get());
1883 }
1884 // Merge the else state into the end state. Set this state as the current
1885 // state first so helper functions know which registers are in use.
1886 __ bind(c->else_state->label.get());
1887 __ cache_state()->Steal(c->else_state->state);
1888 __ MergeFullStackWith(c->label_state);
1889 __ cache_state()->Steal(c->label_state);
1890 } else if (c->reachable()) {
1891 // No merge yet at the end of the if, but we need to create a merge for
1892 // the both arms of this if. Thus init the merge point from the current
1893 // state, then merge the else state into that.
1894 DCHECK_EQ(c->start_merge.arity, c->end_merge.arity);
1895 c->label_state =
1896 __ MergeIntoNewState(__ num_locals(), c->start_merge.arity,
1897 c->stack_depth + c->num_exceptions);
1898 __ emit_jump(c->label.get());
1899 // Merge the else state into the end state. Set this state as the current
1900 // state first so helper functions know which registers are in use.
1901 __ bind(c->else_state->label.get());
1902 __ cache_state()->Steal(c->else_state->state);
1903 __ MergeFullStackWith(c->label_state);
1904 __ cache_state()->Steal(c->label_state);
1905 } else {
1906 // No merge needed, just continue with the else state.
1907 __ bind(c->else_state->label.get());
1908 __ cache_state()->Steal(c->else_state->state);
1909 }
1910 }
1911
1912 void FinishTry(FullDecoder* decoder, Control* c) {
1913 DCHECK(c->is_try_catch() || c->is_try_catchall() || c->is_try_table());
1914 if (!c->end_merge.reached) {
1915 if (c->try_info->catch_reached && !c->is_try_table()) {
1916 // Drop the implicit exception ref.
1917 __ DropExceptionValueAtOffset(__ num_locals() + c->stack_depth +
1918 c->num_exceptions);
1919 }
1920 // Else we did not enter the catch state, continue with the current state.
1921 } else {
1922 if (c->reachable()) {
1923 __ MergeStackWith(c->label_state, c->br_merge()->arity,
1925 }
1926 __ cache_state()->Steal(c->label_state);
1927 }
1928 if (c->try_info->catch_reached && !c->is_try_table()) {
1930 }
1931 }
1932
1933 void PopControl(FullDecoder* decoder, Control* c) {
1934 if (c->is_loop()) return; // A loop just falls through.
1935 if (c->is_onearmed_if()) {
1936 // Special handling for one-armed ifs.
1937 FinishOneArmedIf(decoder, c);
1938 } else if (c->is_try_catch() || c->is_try_catchall() || c->is_try_table()) {
1939 FinishTry(decoder, c);
1940 } else if (c->end_merge.reached) {
1941 // There is a merge already. Merge our state into that, then continue with
1942 // that state.
1943 if (c->reachable()) {
1944 __ MergeFullStackWith(c->label_state);
1945 }
1946 __ cache_state()->Steal(c->label_state);
1947 } else {
1948 // No merge, just continue with our current state.
1949 }
1950
1951 if (!c->label.get()->is_bound()) __ bind(c->label.get());
1952 }
1953
1954 // Call a C function (with default C calling conventions). Returns the
1955 // register holding the result if any.
1956 LiftoffRegister GenerateCCall(ValueKind return_kind,
1957 const std::initializer_list<VarState> args,
1958 ExternalReference ext_ref) {
1960 std::string{"Call extref: "} +
1962 ext_ref.address(), IsolateGroup::current()->external_ref_table()));
1963 __ SpillAllRegisters();
1964 __ CallC(args, ext_ref);
1965 if (needs_gp_reg_pair(return_kind)) {
1967 }
1968 return LiftoffRegister{kReturnRegister0};
1969 }
1970
1971 void GenerateCCallWithStackBuffer(const LiftoffRegister* result_regs,
1972 ValueKind return_kind,
1973 ValueKind out_argument_kind,
1974 const std::initializer_list<VarState> args,
1975 ExternalReference ext_ref) {
1977 std::string{"Call extref: "} +
1979 ext_ref.address(), IsolateGroup::current()->external_ref_table()));
1980
1981 // Before making a call, spill all cache registers.
1982 __ SpillAllRegisters();
1983
1984 // Store arguments on our stack, then align the stack for calling to C.
1985 int param_bytes = 0;
1986 for (const VarState& arg : args) {
1987 param_bytes += value_kind_size(arg.kind());
1988 }
1989 int out_arg_bytes =
1990 out_argument_kind == kVoid ? 0 : value_kind_size(out_argument_kind);
1991 int stack_bytes = std::max(param_bytes, out_arg_bytes);
1992 __ CallCWithStackBuffer(args, result_regs, return_kind, out_argument_kind,
1993 stack_bytes, ext_ref);
1994 }
1995
1996 template <typename EmitFn, typename... Args>
1997 void CallEmitFn(EmitFn fn, Args... args)
1998 requires(!std::is_member_function_pointer_v<EmitFn>)
1999 {
2000 fn(args...);
2001 }
2002
2003 template <typename EmitFn, typename... Args>
2004 void CallEmitFn(EmitFn fn, Args... args)
2005 requires std::is_member_function_pointer_v<EmitFn>
2006 {
2007 (asm_.*fn)(ConvertAssemblerArg(args)...);
2008 }
2009
2010 // Wrap a {LiftoffRegister} with implicit conversions to {Register} and
2011 // {DoubleRegister}.
2012 struct AssemblerRegisterConverter {
2013 LiftoffRegister reg;
2014 operator LiftoffRegister() { return reg; }
2015 operator Register() { return reg.gp(); }
2016 operator DoubleRegister() { return reg.fp(); }
2017 };
2018
2019 // Convert {LiftoffRegister} to {AssemblerRegisterConverter}, other types stay
2020 // unchanged.
2021 template <typename T>
2022 std::conditional_t<std::is_same_v<LiftoffRegister, T>,
2023 AssemblerRegisterConverter, T>
2024 ConvertAssemblerArg(T t) {
2025 return {t};
2026 }
2027
2028 template <typename EmitFn, typename ArgType>
2029 struct EmitFnWithFirstArg {
2030 EmitFn fn;
2031 ArgType first_arg;
2032 };
2033
2034 template <typename EmitFn, typename ArgType>
2035 EmitFnWithFirstArg<EmitFn, ArgType> BindFirst(EmitFn fn, ArgType arg) {
2036 return {fn, arg};
2037 }
2038
2039 template <typename EmitFn, typename T, typename... Args>
2040 void CallEmitFn(EmitFnWithFirstArg<EmitFn, T> bound_fn, Args... args) {
2041 CallEmitFn(bound_fn.fn, bound_fn.first_arg, ConvertAssemblerArg(args)...);
2042 }
2043
2044 template <ValueKind src_kind, ValueKind result_kind,
2045 ValueKind result_lane_kind = kVoid, class EmitFn>
2046 void EmitUnOp(EmitFn fn) {
2047 constexpr RegClass src_rc = reg_class_for(src_kind);
2048 constexpr RegClass result_rc = reg_class_for(result_kind);
2049 LiftoffRegister src = __ PopToRegister();
2050 LiftoffRegister dst = src_rc == result_rc
2051 ? __ GetUnusedRegister(result_rc, {src}, {})
2052 : __ GetUnusedRegister(result_rc, {});
2053 CallEmitFn(fn, dst, src);
2054 if (V8_UNLIKELY(detect_nondeterminism_)) {
2055 LiftoffRegList pinned{dst};
2056 if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) {
2057 CheckNan(dst, pinned, result_kind);
2058 } else if (result_kind == ValueKind::kS128 &&
2059 (result_lane_kind == kF32 || result_lane_kind == kF64)) {
2060 // TODO(irezvov): Add NaN detection for F16.
2061 CheckS128Nan(dst, pinned, result_lane_kind);
2062 }
2063 }
2064 __ PushRegister(result_kind, dst);
2065 }
2066
2067 template <ValueKind kind>
2068 void EmitFloatUnOpWithCFallback(
2069 bool (LiftoffAssembler::*emit_fn)(DoubleRegister, DoubleRegister),
2070 ExternalReference (*fallback_fn)()) {
2071 auto emit_with_c_fallback = [this, emit_fn, fallback_fn](
2072 LiftoffRegister dst, LiftoffRegister src) {
2073 if ((asm_.*emit_fn)(dst.fp(), src.fp())) return;
2074 ExternalReference ext_ref = fallback_fn();
2075 GenerateCCallWithStackBuffer(&dst, kVoid, kind, {VarState{kind, src, 0}},
2076 ext_ref);
2077 };
2078 EmitUnOp<kind, kind>(emit_with_c_fallback);
2079 }
2080
2081 enum TypeConversionTrapping : bool { kCanTrap = true, kNoTrap = false };
2082 template <ValueKind dst_kind, ValueKind src_kind,
2083 TypeConversionTrapping can_trap>
2084 void EmitTypeConversion(FullDecoder* decoder, WasmOpcode opcode,
2085 ExternalReference (*fallback_fn)()) {
2086 static constexpr RegClass src_rc = reg_class_for(src_kind);
2087 static constexpr RegClass dst_rc = reg_class_for(dst_kind);
2088 LiftoffRegister src = __ PopToRegister();
2089 LiftoffRegister dst = src_rc == dst_rc
2090 ? __ GetUnusedRegister(dst_rc, {src}, {})
2091 : __ GetUnusedRegister(dst_rc, {});
2092 bool emitted = __ emit_type_conversion(
2093 opcode, dst, src,
2094 can_trap ? AddOutOfLineTrap(decoder,
2095 Builtin::kThrowWasmTrapFloatUnrepresentable)
2096 .label()
2097 : nullptr);
2098 if (!emitted) {
2099 DCHECK_NOT_NULL(fallback_fn);
2100 ExternalReference ext_ref = fallback_fn();
2101 if (can_trap) {
2102 // External references for potentially trapping conversions return int.
2103 LiftoffRegister ret_reg =
2104 __ GetUnusedRegister(kGpReg, LiftoffRegList{dst});
2105 LiftoffRegister dst_regs[] = {ret_reg, dst};
2106 GenerateCCallWithStackBuffer(dst_regs, kI32, dst_kind,
2107 {VarState{src_kind, src, 0}}, ext_ref);
2108 OolTrapLabel trap = AddOutOfLineTrap(
2109 decoder, Builtin::kThrowWasmTrapFloatUnrepresentable);
2110 __ emit_cond_jump(kEqual, trap.label(), kI32, ret_reg.gp(), no_reg,
2111 trap.frozen());
2112 } else {
2113 GenerateCCallWithStackBuffer(&dst, kVoid, dst_kind,
2114 {VarState{src_kind, src, 0}}, ext_ref);
2115 }
2116 }
2117 __ PushRegister(dst_kind, dst);
2118 }
2119
2120 void EmitIsNull(WasmOpcode opcode, ValueType type) {
2121 LiftoffRegList pinned;
2122 LiftoffRegister ref = pinned.set(__ PopToRegister());
2123 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned);
2124 LoadNullValueForCompare(null.gp(), pinned, type);
2125 // Prefer to overwrite one of the input registers with the result
2126 // of the comparison.
2127 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {ref, null}, {});
2128#if defined(V8_COMPRESS_POINTERS)
2129 // As the value in the {null} register is only the tagged pointer part,
2130 // we may only compare 32 bits, not the full pointer size.
2131 __ emit_i32_set_cond(opcode == kExprRefIsNull ? kEqual : kNotEqual,
2132 dst.gp(), ref.gp(), null.gp());
2133#else
2134 __ emit_ptrsize_set_cond(opcode == kExprRefIsNull ? kEqual : kNotEqual,
2135 dst.gp(), ref, null);
2136#endif
2137 __ PushRegister(kI32, dst);
2138 }
2139
2140 void UnOp(FullDecoder* decoder, WasmOpcode opcode, const Value& value,
2141 Value* result) {
2142#define CASE_I32_UNOP(opcode, fn) \
2143 case kExpr##opcode: \
2144 return EmitUnOp<kI32, kI32>(&LiftoffAssembler::emit_##fn);
2145#define CASE_I64_UNOP(opcode, fn) \
2146 case kExpr##opcode: \
2147 return EmitUnOp<kI64, kI64>(&LiftoffAssembler::emit_##fn);
2148#define CASE_FLOAT_UNOP(opcode, kind, fn) \
2149 case kExpr##opcode: \
2150 return EmitUnOp<k##kind, k##kind>(&LiftoffAssembler::emit_##fn);
2151#define CASE_FLOAT_UNOP_WITH_CFALLBACK(opcode, kind, fn) \
2152 case kExpr##opcode: \
2153 return EmitFloatUnOpWithCFallback<k##kind>(&LiftoffAssembler::emit_##fn, \
2154 &ExternalReference::wasm_##fn);
2155#define CASE_TYPE_CONVERSION(opcode, dst_kind, src_kind, ext_ref, can_trap) \
2156 case kExpr##opcode: \
2157 return EmitTypeConversion<k##dst_kind, k##src_kind, can_trap>( \
2158 decoder, kExpr##opcode, ext_ref);
2159 switch (opcode) {
2160 CASE_I32_UNOP(I32Clz, i32_clz)
2161 CASE_I32_UNOP(I32Ctz, i32_ctz)
2162 CASE_FLOAT_UNOP(F32Abs, F32, f32_abs)
2163 CASE_FLOAT_UNOP(F32Neg, F32, f32_neg)
2164 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Ceil, F32, f32_ceil)
2165 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Floor, F32, f32_floor)
2166 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32Trunc, F32, f32_trunc)
2167 CASE_FLOAT_UNOP_WITH_CFALLBACK(F32NearestInt, F32, f32_nearest_int)
2168 CASE_FLOAT_UNOP(F32Sqrt, F32, f32_sqrt)
2169 CASE_FLOAT_UNOP(F64Abs, F64, f64_abs)
2170 CASE_FLOAT_UNOP(F64Neg, F64, f64_neg)
2171 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Ceil, F64, f64_ceil)
2172 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Floor, F64, f64_floor)
2173 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64Trunc, F64, f64_trunc)
2174 CASE_FLOAT_UNOP_WITH_CFALLBACK(F64NearestInt, F64, f64_nearest_int)
2175 CASE_FLOAT_UNOP(F64Sqrt, F64, f64_sqrt)
2176 CASE_TYPE_CONVERSION(I32ConvertI64, I32, I64, nullptr, kNoTrap)
2177 CASE_TYPE_CONVERSION(I32SConvertF32, I32, F32, nullptr, kCanTrap)
2178 CASE_TYPE_CONVERSION(I32UConvertF32, I32, F32, nullptr, kCanTrap)
2179 CASE_TYPE_CONVERSION(I32SConvertF64, I32, F64, nullptr, kCanTrap)
2180 CASE_TYPE_CONVERSION(I32UConvertF64, I32, F64, nullptr, kCanTrap)
2181 CASE_TYPE_CONVERSION(I32ReinterpretF32, I32, F32, nullptr, kNoTrap)
2182 CASE_TYPE_CONVERSION(I64SConvertI32, I64, I32, nullptr, kNoTrap)
2183 CASE_TYPE_CONVERSION(I64UConvertI32, I64, I32, nullptr, kNoTrap)
2184 CASE_TYPE_CONVERSION(I64SConvertF32, I64, F32,
2185 &ExternalReference::wasm_float32_to_int64, kCanTrap)
2186 CASE_TYPE_CONVERSION(I64UConvertF32, I64, F32,
2187 &ExternalReference::wasm_float32_to_uint64, kCanTrap)
2188 CASE_TYPE_CONVERSION(I64SConvertF64, I64, F64,
2189 &ExternalReference::wasm_float64_to_int64, kCanTrap)
2190 CASE_TYPE_CONVERSION(I64UConvertF64, I64, F64,
2191 &ExternalReference::wasm_float64_to_uint64, kCanTrap)
2192 CASE_TYPE_CONVERSION(I64ReinterpretF64, I64, F64, nullptr, kNoTrap)
2193 CASE_TYPE_CONVERSION(F32SConvertI32, F32, I32, nullptr, kNoTrap)
2194 CASE_TYPE_CONVERSION(F32UConvertI32, F32, I32, nullptr, kNoTrap)
2195 CASE_TYPE_CONVERSION(F32SConvertI64, F32, I64,
2196 &ExternalReference::wasm_int64_to_float32, kNoTrap)
2197 CASE_TYPE_CONVERSION(F32UConvertI64, F32, I64,
2198 &ExternalReference::wasm_uint64_to_float32, kNoTrap)
2199 CASE_TYPE_CONVERSION(F32ConvertF64, F32, F64, nullptr, kNoTrap)
2200 CASE_TYPE_CONVERSION(F32ReinterpretI32, F32, I32, nullptr, kNoTrap)
2201 CASE_TYPE_CONVERSION(F64SConvertI32, F64, I32, nullptr, kNoTrap)
2202 CASE_TYPE_CONVERSION(F64UConvertI32, F64, I32, nullptr, kNoTrap)
2203 CASE_TYPE_CONVERSION(F64SConvertI64, F64, I64,
2204 &ExternalReference::wasm_int64_to_float64, kNoTrap)
2205 CASE_TYPE_CONVERSION(F64UConvertI64, F64, I64,
2206 &ExternalReference::wasm_uint64_to_float64, kNoTrap)
2207 CASE_TYPE_CONVERSION(F64ConvertF32, F64, F32, nullptr, kNoTrap)
2208 CASE_TYPE_CONVERSION(F64ReinterpretI64, F64, I64, nullptr, kNoTrap)
2209 CASE_I32_UNOP(I32SExtendI8, i32_signextend_i8)
2210 CASE_I32_UNOP(I32SExtendI16, i32_signextend_i16)
2211 CASE_I64_UNOP(I64SExtendI8, i64_signextend_i8)
2212 CASE_I64_UNOP(I64SExtendI16, i64_signextend_i16)
2213 CASE_I64_UNOP(I64SExtendI32, i64_signextend_i32)
2214 CASE_I64_UNOP(I64Clz, i64_clz)
2215 CASE_I64_UNOP(I64Ctz, i64_ctz)
2216 CASE_TYPE_CONVERSION(I32SConvertSatF32, I32, F32, nullptr, kNoTrap)
2217 CASE_TYPE_CONVERSION(I32UConvertSatF32, I32, F32, nullptr, kNoTrap)
2218 CASE_TYPE_CONVERSION(I32SConvertSatF64, I32, F64, nullptr, kNoTrap)
2219 CASE_TYPE_CONVERSION(I32UConvertSatF64, I32, F64, nullptr, kNoTrap)
2220 CASE_TYPE_CONVERSION(I64SConvertSatF32, I64, F32,
2221 &ExternalReference::wasm_float32_to_int64_sat,
2222 kNoTrap)
2223 CASE_TYPE_CONVERSION(I64UConvertSatF32, I64, F32,
2224 &ExternalReference::wasm_float32_to_uint64_sat,
2225 kNoTrap)
2226 CASE_TYPE_CONVERSION(I64SConvertSatF64, I64, F64,
2227 &ExternalReference::wasm_float64_to_int64_sat,
2228 kNoTrap)
2229 CASE_TYPE_CONVERSION(I64UConvertSatF64, I64, F64,
2230 &ExternalReference::wasm_float64_to_uint64_sat,
2231 kNoTrap)
2232 case kExprI32Eqz:
2233 DCHECK(decoder->lookahead(0, kExprI32Eqz));
2234 if ((decoder->lookahead(1, kExprBrIf) ||
2235 decoder->lookahead(1, kExprIf)) &&
2236 !for_debugging_) {
2237 DCHECK(!has_outstanding_op());
2238 outstanding_op_ = kExprI32Eqz;
2239 break;
2240 }
2241 return EmitUnOp<kI32, kI32>(&LiftoffAssembler::emit_i32_eqz);
2242 case kExprI64Eqz:
2243 return EmitUnOp<kI64, kI32>(&LiftoffAssembler::emit_i64_eqz);
2244 case kExprI32Popcnt:
2245 return EmitUnOp<kI32, kI32>(
2246 [this](LiftoffRegister dst, LiftoffRegister src) {
2247 if (__ emit_i32_popcnt(dst.gp(), src.gp())) return;
2248 LiftoffRegister result =
2249 GenerateCCall(kI32, {VarState{kI32, src, 0}},
2250 ExternalReference::wasm_word32_popcnt());
2251 if (result != dst) __ Move(dst.gp(), result.gp(), kI32);
2252 });
2253 case kExprI64Popcnt:
2254 return EmitUnOp<kI64, kI64>(
2255 [this](LiftoffRegister dst, LiftoffRegister src) {
2256 if (__ emit_i64_popcnt(dst, src)) return;
2257 // The c function returns i32. We will zero-extend later.
2258 LiftoffRegister result =
2259 GenerateCCall(kI32, {VarState{kI64, src, 0}},
2260 ExternalReference::wasm_word64_popcnt());
2261 // Now zero-extend the result to i64.
2262 __ emit_type_conversion(kExprI64UConvertI32, dst, result,
2263 nullptr);
2264 });
2265 case kExprRefIsNull:
2266 // We abuse ref.as_non_null, which isn't otherwise used in this switch, as
2267 // a sentinel for the negation of ref.is_null.
2268 case kExprRefAsNonNull:
2269 return EmitIsNull(opcode, value.type);
2270 case kExprAnyConvertExtern: {
2271 VarState input_state = __ cache_state()->stack_state.back();
2272 CallBuiltin(Builtin::kWasmAnyConvertExtern,
2273 MakeSig::Returns(kRefNull).Params(kRefNull), {input_state},
2274 decoder->position());
2275 __ DropValues(1);
2276 __ PushRegister(kRef, LiftoffRegister(kReturnRegister0));
2277 return;
2278 }
2279 case kExprExternConvertAny: {
2280 LiftoffRegList pinned;
2281 LiftoffRegister ref = pinned.set(__ PopToModifiableRegister(pinned));
2282 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned);
2283 LoadNullValueForCompare(null.gp(), pinned, kWasmAnyRef);
2284 Label label;
2285 {
2286 FREEZE_STATE(frozen);
2287 __ emit_cond_jump(kNotEqual, &label, kRefNull, ref.gp(), null.gp(),
2288 frozen);
2289 LoadNullValue(ref.gp(), kWasmExternRef);
2290 __ bind(&label);
2291 }
2292 __ PushRegister(kRefNull, ref);
2293 return;
2294 }
2295 default:
2296 UNREACHABLE();
2297 }
2298#undef CASE_I32_UNOP
2299#undef CASE_I64_UNOP
2300#undef CASE_FLOAT_UNOP
2301#undef CASE_FLOAT_UNOP_WITH_CFALLBACK
2302#undef CASE_TYPE_CONVERSION
2303 }
2304
2305 template <ValueKind src_kind, ValueKind result_kind, typename EmitFn,
2306 typename EmitFnImm>
2307 void EmitBinOpImm(EmitFn fn, EmitFnImm fnImm) {
2308 static constexpr RegClass src_rc = reg_class_for(src_kind);
2309 static constexpr RegClass result_rc = reg_class_for(result_kind);
2310
2311 VarState rhs_slot = __ cache_state()->stack_state.back();
2312 // Check if the RHS is an immediate.
2313 if (rhs_slot.is_const()) {
2314 __ cache_state()->stack_state.pop_back();
2315 int32_t imm = rhs_slot.i32_const();
2316
2317 LiftoffRegister lhs = __ PopToRegister();
2318 // Either reuse {lhs} for {dst}, or choose a register (pair) which does
2319 // not overlap, for easier code generation.
2320 LiftoffRegList pinned{lhs};
2321 LiftoffRegister dst = src_rc == result_rc
2322 ? __ GetUnusedRegister(result_rc, {lhs}, pinned)
2323 : __ GetUnusedRegister(result_rc, pinned);
2324
2325 CallEmitFn(fnImm, dst, lhs, imm);
2326 static_assert(result_kind != kF32 && result_kind != kF64,
2327 "Unhandled nondeterminism for fuzzing.");
2328 __ PushRegister(result_kind, dst);
2329 } else {
2330 // The RHS was not an immediate.
2331 EmitBinOp<src_kind, result_kind>(fn);
2332 }
2333 }
2334
2335 template <ValueKind src_kind, ValueKind result_kind,
2336 bool swap_lhs_rhs = false, ValueKind result_lane_kind = kVoid,
2337 typename EmitFn>
2338 void EmitBinOp(EmitFn fn) {
2339 static constexpr RegClass src_rc = reg_class_for(src_kind);
2340 static constexpr RegClass result_rc = reg_class_for(result_kind);
2341 LiftoffRegister rhs = __ PopToRegister();
2342 LiftoffRegister lhs = __ PopToRegister(LiftoffRegList{rhs});
2343 LiftoffRegister dst = src_rc == result_rc
2344 ? __ GetUnusedRegister(result_rc, {lhs, rhs}, {})
2345 : __ GetUnusedRegister(result_rc, {});
2346
2347 if (swap_lhs_rhs) std::swap(lhs, rhs);
2348
2349 CallEmitFn(fn, dst, lhs, rhs);
2350 if (V8_UNLIKELY(detect_nondeterminism_)) {
2351 LiftoffRegList pinned{dst};
2352 if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) {
2353 CheckNan(dst, pinned, result_kind);
2354 } else if (result_kind == ValueKind::kS128 &&
2355 (result_lane_kind == kF32 || result_lane_kind == kF64)) {
2356 CheckS128Nan(dst, pinned, result_lane_kind);
2357 }
2358 }
2359 __ PushRegister(result_kind, dst);
2360 }
2361
2362 // We do not use EmitBinOp for Swizzle because in the no-avx case, we have the
2363 // additional constraint that dst does not alias the mask.
2364 void EmitI8x16Swizzle(bool relaxed) {
2365 static constexpr RegClass result_rc = reg_class_for(kS128);
2366 LiftoffRegister mask = __ PopToRegister();
2367 LiftoffRegister src = __ PopToRegister(LiftoffRegList{mask});
2368#if defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64)
2369 LiftoffRegister dst =
2371 ? __ GetUnusedRegister(result_rc, {src, mask}, {})
2372 : __ GetUnusedRegister(result_rc, {src}, LiftoffRegList{mask});
2373#else
2374 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {src, mask}, {});
2375#endif
2376 if (relaxed) {
2377 __ emit_i8x16_relaxed_swizzle(dst, src, mask);
2378 } else {
2379 __ emit_i8x16_swizzle(dst, src, mask);
2380 }
2381 __ PushRegister(kS128, dst);
2382 }
2383
2384 void EmitDivOrRem64CCall(LiftoffRegister dst, LiftoffRegister lhs,
2385 LiftoffRegister rhs, ExternalReference ext_ref,
2386 Label* trap_by_zero,
2387 Label* trap_unrepresentable = nullptr) {
2388 // Cannot emit native instructions, build C call.
2389 LiftoffRegister ret = __ GetUnusedRegister(kGpReg, LiftoffRegList{dst});
2390 LiftoffRegister result_regs[] = {ret, dst};
2391 GenerateCCallWithStackBuffer(result_regs, kI32, kI64,
2392 {{kI64, lhs, 0}, {kI64, rhs, 0}}, ext_ref);
2393 FREEZE_STATE(trapping);
2394 __ emit_i32_cond_jumpi(kEqual, trap_by_zero, ret.gp(), 0, trapping);
2395 if (trap_unrepresentable) {
2396 __ emit_i32_cond_jumpi(kEqual, trap_unrepresentable, ret.gp(), -1,
2397 trapping);
2398 }
2399 }
2400
2401 template <WasmOpcode opcode>
2402 void EmitI32CmpOp(FullDecoder* decoder) {
2403 DCHECK(decoder->lookahead(0, opcode));
2404 if ((decoder->lookahead(1, kExprBrIf) || decoder->lookahead(1, kExprIf)) &&
2405 !for_debugging_) {
2406 DCHECK(!has_outstanding_op());
2407 outstanding_op_ = opcode;
2408 return;
2409 }
2410 return EmitBinOp<kI32, kI32>(BindFirst(&LiftoffAssembler::emit_i32_set_cond,
2411 GetCompareCondition(opcode)));
2412 }
2413
2414 template <ValueKind kind, ExternalReference(ExtRefFn)()>
2415 void EmitBitRotationCCall() {
2416 EmitBinOp<kind, kind>([this](LiftoffRegister dst, LiftoffRegister input,
2417 LiftoffRegister shift) {
2418 // The shift is always passed as 32-bit value.
2419 if (needs_gp_reg_pair(kind)) shift = shift.low();
2420 LiftoffRegister result =
2421 GenerateCCall(kind, {{kind, input, 0}, {kI32, shift, 0}}, ExtRefFn());
2422 if (dst != result) __ Move(dst, result, kind);
2423 });
2424 }
2425
2426 template <typename EmitFn, typename EmitFnImm>
2427 void EmitI64Shift(EmitFn fn, EmitFnImm fnImm) {
2428 return EmitBinOpImm<kI64, kI64>(
2429 [this, fn](LiftoffRegister dst, LiftoffRegister src,
2430 LiftoffRegister amount) {
2431 CallEmitFn(fn, dst, src,
2432 amount.is_gp_pair() ? amount.low_gp() : amount.gp());
2433 },
2434 fnImm);
2435 }
2436
2437 void BinOp(FullDecoder* decoder, WasmOpcode opcode, const Value& lhs,
2438 const Value& rhs, Value* result) {
2439 switch (opcode) {
2440 case kExprI32Add:
2441 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_add,
2443 case kExprI32Sub:
2444 return EmitBinOp<kI32, kI32>(&LiftoffAssembler::emit_i32_sub);
2445 case kExprI32Mul:
2446 return EmitBinOp<kI32, kI32>(&LiftoffAssembler::emit_i32_mul);
2447 case kExprI32And:
2448 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_and,
2450 case kExprI32Ior:
2451 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_or,
2453 case kExprI32Xor:
2454 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_xor,
2456 case kExprI32Eq:
2457 return EmitI32CmpOp<kExprI32Eq>(decoder);
2458 case kExprI32Ne:
2459 return EmitI32CmpOp<kExprI32Ne>(decoder);
2460 case kExprI32LtS:
2461 return EmitI32CmpOp<kExprI32LtS>(decoder);
2462 case kExprI32LtU:
2463 return EmitI32CmpOp<kExprI32LtU>(decoder);
2464 case kExprI32GtS:
2465 return EmitI32CmpOp<kExprI32GtS>(decoder);
2466 case kExprI32GtU:
2467 return EmitI32CmpOp<kExprI32GtU>(decoder);
2468 case kExprI32LeS:
2469 return EmitI32CmpOp<kExprI32LeS>(decoder);
2470 case kExprI32LeU:
2471 return EmitI32CmpOp<kExprI32LeU>(decoder);
2472 case kExprI32GeS:
2473 return EmitI32CmpOp<kExprI32GeS>(decoder);
2474 case kExprI32GeU:
2475 return EmitI32CmpOp<kExprI32GeU>(decoder);
2476 case kExprI64Add:
2477 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_add,
2479 case kExprI64Sub:
2480 return EmitBinOp<kI64, kI64>(&LiftoffAssembler::emit_i64_sub);
2481 case kExprI64Mul:
2482 return EmitBinOp<kI64, kI64>(&LiftoffAssembler::emit_i64_mul);
2483 case kExprI64And:
2484 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_and,
2486 case kExprI64Ior:
2487 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_or,
2489 case kExprI64Xor:
2490 return EmitBinOpImm<kI64, kI64>(&LiftoffAssembler::emit_i64_xor,
2492 case kExprI64Eq:
2493 return EmitBinOp<kI64, kI32>(
2495 case kExprI64Ne:
2496 return EmitBinOp<kI64, kI32>(
2498 case kExprI64LtS:
2499 return EmitBinOp<kI64, kI32>(
2501 case kExprI64LtU:
2502 return EmitBinOp<kI64, kI32>(
2504 case kExprI64GtS:
2505 return EmitBinOp<kI64, kI32>(
2507 case kExprI64GtU:
2508 return EmitBinOp<kI64, kI32>(BindFirst(
2510 case kExprI64LeS:
2511 return EmitBinOp<kI64, kI32>(
2513 case kExprI64LeU:
2514 return EmitBinOp<kI64, kI32>(BindFirst(
2516 case kExprI64GeS:
2517 return EmitBinOp<kI64, kI32>(
2519 case kExprI64GeU:
2520 return EmitBinOp<kI64, kI32>(BindFirst(
2522 case kExprF32Eq:
2523 return EmitBinOp<kF32, kI32>(
2525 case kExprF32Ne:
2526 return EmitBinOp<kF32, kI32>(
2528 case kExprF32Lt:
2529 return EmitBinOp<kF32, kI32>(
2531 case kExprF32Gt:
2532 return EmitBinOp<kF32, kI32>(BindFirst(
2534 case kExprF32Le:
2535 return EmitBinOp<kF32, kI32>(BindFirst(
2537 case kExprF32Ge:
2538 return EmitBinOp<kF32, kI32>(BindFirst(
2540 case kExprF64Eq:
2541 return EmitBinOp<kF64, kI32>(
2543 case kExprF64Ne:
2544 return EmitBinOp<kF64, kI32>(
2546 case kExprF64Lt:
2547 return EmitBinOp<kF64, kI32>(
2549 case kExprF64Gt:
2550 return EmitBinOp<kF64, kI32>(BindFirst(
2552 case kExprF64Le:
2553 return EmitBinOp<kF64, kI32>(BindFirst(
2555 case kExprF64Ge:
2556 return EmitBinOp<kF64, kI32>(BindFirst(
2558 case kExprI32Shl:
2559 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_shl,
2561 case kExprI32ShrS:
2562 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_sar,
2564 case kExprI32ShrU:
2565 return EmitBinOpImm<kI32, kI32>(&LiftoffAssembler::emit_i32_shr,
2567 case kExprI32Rol:
2568 return EmitBitRotationCCall<kI32, ExternalReference::wasm_word32_rol>();
2569 case kExprI32Ror:
2570 return EmitBitRotationCCall<kI32, ExternalReference::wasm_word32_ror>();
2571 case kExprI64Shl:
2572 return EmitI64Shift(&LiftoffAssembler::emit_i64_shl,
2574 case kExprI64ShrS:
2575 return EmitI64Shift(&LiftoffAssembler::emit_i64_sar,
2577 case kExprI64ShrU:
2578 return EmitI64Shift(&LiftoffAssembler::emit_i64_shr,
2580 case kExprI64Rol:
2581 return EmitBitRotationCCall<kI64, ExternalReference::wasm_word64_rol>();
2582 case kExprI64Ror:
2583 return EmitBitRotationCCall<kI64, ExternalReference::wasm_word64_ror>();
2584 case kExprF32Add:
2585 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_add);
2586 case kExprF32Sub:
2587 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_sub);
2588 case kExprF32Mul:
2589 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_mul);
2590 case kExprF32Div:
2591 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_div);
2592 case kExprF32Min:
2593 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_min);
2594 case kExprF32Max:
2595 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_max);
2596 case kExprF32CopySign:
2597 return EmitBinOp<kF32, kF32>(&LiftoffAssembler::emit_f32_copysign);
2598 case kExprF64Add:
2599 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_add);
2600 case kExprF64Sub:
2601 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_sub);
2602 case kExprF64Mul:
2603 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_mul);
2604 case kExprF64Div:
2605 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_div);
2606 case kExprF64Min:
2607 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_min);
2608 case kExprF64Max:
2609 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_max);
2610 case kExprF64CopySign:
2611 return EmitBinOp<kF64, kF64>(&LiftoffAssembler::emit_f64_copysign);
2612 case kExprI32DivS:
2613 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst,
2614 LiftoffRegister lhs,
2615 LiftoffRegister rhs) {
2616 AddOutOfLineTrapDeprecated(decoder, Builtin::kThrowWasmTrapDivByZero);
2617 // Adding the second trap might invalidate the pointer returned for
2618 // the first one, thus get both pointers afterwards.
2619 AddOutOfLineTrapDeprecated(decoder,
2620 Builtin::kThrowWasmTrapDivUnrepresentable);
2621 Label* div_by_zero = out_of_line_code_.end()[-2].label.get();
2622 Label* div_unrepresentable = out_of_line_code_.end()[-1].label.get();
2623 __ emit_i32_divs(dst.gp(), lhs.gp(), rhs.gp(), div_by_zero,
2624 div_unrepresentable);
2625 });
2626 case kExprI32DivU:
2627 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst,
2628 LiftoffRegister lhs,
2629 LiftoffRegister rhs) {
2630 Label* div_by_zero = AddOutOfLineTrapDeprecated(
2631 decoder, Builtin::kThrowWasmTrapDivByZero);
2632 __ emit_i32_divu(dst.gp(), lhs.gp(), rhs.gp(), div_by_zero);
2633 });
2634 case kExprI32RemS:
2635 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst,
2636 LiftoffRegister lhs,
2637 LiftoffRegister rhs) {
2638 Label* rem_by_zero = AddOutOfLineTrapDeprecated(
2639 decoder, Builtin::kThrowWasmTrapRemByZero);
2640 __ emit_i32_rems(dst.gp(), lhs.gp(), rhs.gp(), rem_by_zero);
2641 });
2642 case kExprI32RemU:
2643 return EmitBinOp<kI32, kI32>([this, decoder](LiftoffRegister dst,
2644 LiftoffRegister lhs,
2645 LiftoffRegister rhs) {
2646 Label* rem_by_zero = AddOutOfLineTrapDeprecated(
2647 decoder, Builtin::kThrowWasmTrapRemByZero);
2648 __ emit_i32_remu(dst.gp(), lhs.gp(), rhs.gp(), rem_by_zero);
2649 });
2650 case kExprI64DivS:
2651 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst,
2652 LiftoffRegister lhs,
2653 LiftoffRegister rhs) {
2654 AddOutOfLineTrapDeprecated(decoder, Builtin::kThrowWasmTrapDivByZero);
2655 // Adding the second trap might invalidate the pointer returned for
2656 // the first one, thus get both pointers afterwards.
2657 AddOutOfLineTrapDeprecated(decoder,
2658 Builtin::kThrowWasmTrapDivUnrepresentable);
2659 Label* div_by_zero = out_of_line_code_.end()[-2].label.get();
2660 Label* div_unrepresentable = out_of_line_code_.end()[-1].label.get();
2661 if (!__ emit_i64_divs(dst, lhs, rhs, div_by_zero,
2662 div_unrepresentable)) {
2663 ExternalReference ext_ref = ExternalReference::wasm_int64_div();
2664 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, div_by_zero,
2665 div_unrepresentable);
2666 }
2667 });
2668 case kExprI64DivU:
2669 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst,
2670 LiftoffRegister lhs,
2671 LiftoffRegister rhs) {
2672 Label* div_by_zero = AddOutOfLineTrapDeprecated(
2673 decoder, Builtin::kThrowWasmTrapDivByZero);
2674 if (!__ emit_i64_divu(dst, lhs, rhs, div_by_zero)) {
2675 ExternalReference ext_ref = ExternalReference::wasm_uint64_div();
2676 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, div_by_zero);
2677 }
2678 });
2679 case kExprI64RemS:
2680 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst,
2681 LiftoffRegister lhs,
2682 LiftoffRegister rhs) {
2683 Label* rem_by_zero = AddOutOfLineTrapDeprecated(
2684 decoder, Builtin::kThrowWasmTrapRemByZero);
2685 if (!__ emit_i64_rems(dst, lhs, rhs, rem_by_zero)) {
2686 ExternalReference ext_ref = ExternalReference::wasm_int64_mod();
2687 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, rem_by_zero);
2688 }
2689 });
2690 case kExprI64RemU:
2691 return EmitBinOp<kI64, kI64>([this, decoder](LiftoffRegister dst,
2692 LiftoffRegister lhs,
2693 LiftoffRegister rhs) {
2694 Label* rem_by_zero = AddOutOfLineTrapDeprecated(
2695 decoder, Builtin::kThrowWasmTrapRemByZero);
2696 if (!__ emit_i64_remu(dst, lhs, rhs, rem_by_zero)) {
2697 ExternalReference ext_ref = ExternalReference::wasm_uint64_mod();
2698 EmitDivOrRem64CCall(dst, lhs, rhs, ext_ref, rem_by_zero);
2699 }
2700 });
2701 case kExprRefEq: {
2702#if defined(V8_COMPRESS_POINTERS)
2703 // In pointer compression, we smi-corrupt (the upper bits of a
2704 // Smi are arbitrary). So, we should only compare the lower 32 bits.
2705 return EmitBinOp<kRefNull, kI32>(
2707#else
2708 return EmitBinOp<kRefNull, kI32>(
2710#endif
2711 }
2712
2713 default:
2714 UNREACHABLE();
2715 }
2716 }
2717
2718 void TraceInstruction(FullDecoder* decoder, uint32_t markid) {
2719#if V8_TARGET_ARCH_X64
2720 __ emit_trace_instruction(markid);
2721#endif
2722 }
2723
2724 void I32Const(FullDecoder* decoder, Value* result, int32_t value) {
2725 __ PushConstant(kI32, value);
2726 }
2727
2728 void I64Const(FullDecoder* decoder, Value* result, int64_t value) {
2729 // The {VarState} stores constant values as int32_t, thus we only store
2730 // 64-bit constants in this field if it fits in an int32_t. Larger values
2731 // cannot be used as immediate value anyway, so we can also just put them in
2732 // a register immediately.
2733 int32_t value_i32 = static_cast<int32_t>(value);
2734 if (value_i32 == value) {
2735 __ PushConstant(kI64, value_i32);
2736 } else {
2737 LiftoffRegister reg = __ GetUnusedRegister(reg_class_for(kI64), {});
2738 __ LoadConstant(reg, WasmValue(value));
2739 __ PushRegister(kI64, reg);
2740 }
2741 }
2742
2743 void F32Const(FullDecoder* decoder, Value* result, float value) {
2744 LiftoffRegister reg = __ GetUnusedRegister(kFpReg, {});
2745 __ LoadConstant(reg, WasmValue(value));
2746 __ PushRegister(kF32, reg);
2747 }
2748
2749 void F64Const(FullDecoder* decoder, Value* result, double value) {
2750 LiftoffRegister reg = __ GetUnusedRegister(kFpReg, {});
2751 __ LoadConstant(reg, WasmValue(value));
2752 __ PushRegister(kF64, reg);
2753 }
2754
2755 void RefNull(FullDecoder* decoder, ValueType type, Value*) {
2756 LiftoffRegister null = __ GetUnusedRegister(kGpReg, {});
2757 LoadNullValue(null.gp(), type);
2758 __ PushRegister(type.kind(), null);
2759 }
2760
2761 void RefFunc(FullDecoder* decoder, uint32_t function_index, Value* result) {
2762 CallBuiltin(Builtin::kWasmRefFunc,
2763 MakeSig::Returns(kRef).Params(kI32, kI32),
2764 {VarState{kI32, static_cast<int>(function_index), 0},
2765 VarState{kI32, 0, 0}},
2766 decoder->position());
2767 __ PushRegister(kRef, LiftoffRegister(kReturnRegister0));
2768 }
2769
2770 void RefAsNonNull(FullDecoder* decoder, const Value& arg, Value* result) {
2771 // The decoder only calls this function if the type is nullable.
2772 DCHECK(arg.type.is_nullable());
2773 LiftoffRegList pinned;
2774 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
2775 if (null_check_strategy_ == compiler::NullCheckStrategy::kExplicit ||
2776 IsSubtypeOf(kWasmRefI31, arg.type, decoder->module_) ||
2777 !arg.type.use_wasm_null()) {
2778 // Use an explicit null check if
2779 // (1) we cannot use trap handler or
2780 // (2) the object might be a Smi or
2781 // (3) the object might be a JS object.
2782 MaybeEmitNullCheck(decoder, obj.gp(), pinned, arg.type);
2783 } else if (!v8_flags.experimental_wasm_skip_null_checks) {
2784 // Otherwise, load the word after the map word.
2785 static_assert(WasmStruct::kHeaderSize > kTaggedSize);
2786 static_assert(WasmArray::kHeaderSize > kTaggedSize);
2787 static_assert(WasmInternalFunction::kHeaderSize > kTaggedSize);
2788 LiftoffRegister dst = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
2789 uint32_t protected_load_pc = 0;
2791 LoadType::kI32Load, &protected_load_pc);
2792 RegisterProtectedInstruction(decoder, protected_load_pc);
2793 }
2794 __ PushRegister(kRef, obj);
2795 }
2796
2797 void Drop(FullDecoder* decoder) { __ DropValues(1); }
2798
2799 V8_NOINLINE V8_PRESERVE_MOST void TraceFunctionExit(FullDecoder* decoder) {
2800 CODE_COMMENT("trace function exit");
2801 // Before making the runtime call, spill all cache registers.
2802 __ SpillAllRegisters();
2803
2804 // Store the return value if there is exactly one. Multiple return values
2805 // are not handled yet.
2806 size_t num_returns = decoder->sig_->return_count();
2807 // Put the parameter in its place.
2808 WasmTraceExitDescriptor descriptor;
2809 DCHECK_EQ(0, descriptor.GetStackParameterCount());
2810 DCHECK_EQ(1, descriptor.GetRegisterParameterCount());
2811 Register param_reg = descriptor.GetRegisterParameter(0);
2812 if (num_returns == 1) {
2813 auto& return_slot = __ cache_state()->stack_state.back();
2814 if (return_slot.is_const()) {
2815 __ Spill(&return_slot);
2816 }
2817 DCHECK(return_slot.is_stack());
2818 __ LoadSpillAddress(param_reg, return_slot.offset(), return_slot.kind());
2819 } else {
2820 // Make sure to pass a "valid" parameter (Smi::zero()).
2821 LoadSmi(LiftoffRegister{param_reg}, 0);
2822 }
2823
2825 __ pc_offset(), SourcePosition(decoder->position()), false);
2826 __ CallBuiltin(Builtin::kWasmTraceExit);
2827 DefineSafepoint();
2828 }
2829
2830 void TierupCheckOnTailCall(FullDecoder* decoder) {
2831 if (!dynamic_tiering()) return;
2832 TierupCheck(decoder, decoder->position(),
2833 __ pc_offset() + kTierUpCostForFunctionEntry);
2834 }
2835
2836 void DoReturn(FullDecoder* decoder, uint32_t /* drop values */) {
2837 ReturnImpl(decoder);
2838 }
2839
2840 void ReturnImpl(FullDecoder* decoder) {
2841 if (V8_UNLIKELY(v8_flags.trace_wasm)) TraceFunctionExit(decoder);
2842 // A function returning an uninhabitable type can't ever actually reach
2843 // a {ret} instruction (it can only return by throwing or trapping). So
2844 // if we do get here, there must have been a bug. Crash to flush it out.
2845 base::Vector<const ValueType> returns = decoder->sig_->returns();
2846 if (V8_UNLIKELY(std::any_of(
2847 returns.begin(), returns.end(),
2848 [](const ValueType type) { return type.is_uninhabited(); }))) {
2849 __ Abort(AbortReason::kUninhabitableType);
2850 return;
2851 }
2852 if (dynamic_tiering()) {
2853 TierupCheck(decoder, decoder->position(),
2854 __ pc_offset() + kTierUpCostForFunctionEntry);
2855 }
2856 size_t num_returns = decoder->sig_->return_count();
2857 if (num_returns > 0) __ MoveToReturnLocations(decoder->sig_, descriptor_);
2858 if (v8_flags.experimental_wasm_growable_stacks) {
2859 __ CheckStackShrink();
2860 }
2861 __ LeaveFrame(StackFrame::WASM);
2862 __ DropStackSlotsAndRet(
2863 static_cast<uint32_t>(descriptor_->ParameterSlotCount()));
2864 }
2865
2866 void LocalGet(FullDecoder* decoder, Value* result,
2867 const IndexImmediate& imm) {
2868 auto local_slot = __ cache_state()->stack_state[imm.index];
2869 __ cache_state()->stack_state.emplace_back(
2870 local_slot.kind(), __ NextSpillOffset(local_slot.kind()));
2871 auto* slot = &__ cache_state()->stack_state.back();
2872 if (local_slot.is_reg()) {
2873 __ cache_state()->inc_used(local_slot.reg());
2874 slot->MakeRegister(local_slot.reg());
2875 } else if (local_slot.is_const()) {
2876 slot->MakeConstant(local_slot.i32_const());
2877 } else {
2878 DCHECK(local_slot.is_stack());
2879 auto rc = reg_class_for(local_slot.kind());
2880 LiftoffRegister reg = __ GetUnusedRegister(rc, {});
2881 __ cache_state()->inc_used(reg);
2882 slot->MakeRegister(reg);
2883 __ Fill(reg, local_slot.offset(), local_slot.kind());
2884 }
2885 }
2886
2887 void LocalSetFromStackSlot(VarState* dst_slot, uint32_t local_index) {
2888 auto& state = *__ cache_state();
2889 auto& src_slot = state.stack_state.back();
2890 ValueKind kind = dst_slot->kind();
2891 if (dst_slot->is_reg()) {
2892 LiftoffRegister slot_reg = dst_slot->reg();
2893 if (state.get_use_count(slot_reg) == 1) {
2894 __ Fill(dst_slot->reg(), src_slot.offset(), kind);
2895 return;
2896 }
2897 state.dec_used(slot_reg);
2898 dst_slot->MakeStack();
2899 }
2900 DCHECK(CompatibleStackSlotTypes(kind, __ local_kind(local_index)));
2902 LiftoffRegister dst_reg = __ GetUnusedRegister(rc, {});
2903 __ Fill(dst_reg, src_slot.offset(), kind);
2904 *dst_slot = VarState(kind, dst_reg, dst_slot->offset());
2905 __ cache_state()->inc_used(dst_reg);
2906 }
2907
2908 void LocalSet(uint32_t local_index, bool is_tee) {
2909 auto& state = *__ cache_state();
2910 auto& source_slot = state.stack_state.back();
2911 auto& target_slot = state.stack_state[local_index];
2912 switch (source_slot.loc()) {
2913 case kRegister:
2914 if (target_slot.is_reg()) state.dec_used(target_slot.reg());
2915 target_slot.Copy(source_slot);
2916 if (is_tee) state.inc_used(target_slot.reg());
2917 break;
2918 case kIntConst:
2919 if (target_slot.is_reg()) state.dec_used(target_slot.reg());
2920 target_slot.Copy(source_slot);
2921 break;
2922 case kStack:
2923 LocalSetFromStackSlot(&target_slot, local_index);
2924 break;
2925 }
2926 if (!is_tee) __ cache_state()->stack_state.pop_back();
2927 }
2928
2929 void LocalSet(FullDecoder* decoder, const Value& value,
2930 const IndexImmediate& imm) {
2931 LocalSet(imm.index, false);
2932 }
2933
2934 void LocalTee(FullDecoder* decoder, const Value& value, Value* result,
2935 const IndexImmediate& imm) {
2936 LocalSet(imm.index, true);
2937 }
2938
2939 Register GetGlobalBaseAndOffset(const WasmGlobal* global,
2940 LiftoffRegList* pinned, uint32_t* offset) {
2941 Register addr = pinned->set(__ GetUnusedRegister(kGpReg, {})).gp();
2942 if (global->mutability && global->imported) {
2943 LOAD_TAGGED_PTR_INSTANCE_FIELD(addr, ImportedMutableGlobals, *pinned);
2944 int field_offset =
2946 global->index);
2947 __ LoadFullPointer(addr, addr, field_offset);
2948 *offset = 0;
2949#ifdef V8_ENABLE_SANDBOX
2950 __ DecodeSandboxedPointer(addr);
2951#endif
2952 } else {
2953 LOAD_INSTANCE_FIELD(addr, GlobalsStart, kSystemPointerSize, *pinned);
2954 *offset = global->offset;
2955 }
2956 return addr;
2957 }
2958
2959 void GetBaseAndOffsetForImportedMutableExternRefGlobal(
2960 const WasmGlobal* global, LiftoffRegList* pinned, Register* base,
2961 Register* offset) {
2962 Register globals_buffer =
2963 pinned->set(__ GetUnusedRegister(kGpReg, *pinned)).gp();
2964 LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer,
2965 ImportedMutableGlobalsBuffers, *pinned);
2966 *base = globals_buffer;
2967 __ LoadTaggedPointer(
2968 *base, globals_buffer, no_reg,
2970
2971 // For the offset we need the index of the global in the buffer, and
2972 // then calculate the actual offset from the index. Load the index from
2973 // the ImportedMutableGlobals array of the instance.
2974 Register imported_mutable_globals =
2975 pinned->set(__ GetUnusedRegister(kGpReg, *pinned)).gp();
2976
2977 LOAD_TAGGED_PTR_INSTANCE_FIELD(imported_mutable_globals,
2978 ImportedMutableGlobals, *pinned);
2979 *offset = imported_mutable_globals;
2980 int field_offset =
2982 global->index);
2983 __ Load(LiftoffRegister(*offset), imported_mutable_globals, no_reg,
2984 field_offset, LoadType::kI32Load);
2985 __ emit_i32_shli(*offset, *offset, kTaggedSizeLog2);
2986 __ emit_i32_addi(*offset, *offset,
2988 }
2989
2990 void GlobalGet(FullDecoder* decoder, Value* result,
2991 const GlobalIndexImmediate& imm) {
2992 const auto* global = &env_->module->globals[imm.index];
2993 ValueKind kind = global->type.kind();
2994 if (!CheckSupportedType(decoder, kind, "global")) {
2995 return;
2996 }
2997
2998 if (is_reference(kind)) {
2999 if (global->mutability && global->imported) {
3000 LiftoffRegList pinned;
3003 GetBaseAndOffsetForImportedMutableExternRefGlobal(global, &pinned,
3004 &base, &offset);
3005 __ LoadTaggedPointer(base, base, offset, 0);
3006 __ PushRegister(kind, LiftoffRegister(base));
3007 return;
3008 }
3009
3010 LiftoffRegList pinned;
3011 Register globals_buffer =
3012 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
3013 LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, TaggedGlobalsBuffer,
3014 pinned);
3015 Register value = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
3016 __ LoadTaggedPointer(value, globals_buffer, no_reg,
3018 imm.global->offset));
3019 __ PushRegister(kind, LiftoffRegister(value));
3020 return;
3021 }
3022 LiftoffRegList pinned;
3023 uint32_t offset = 0;
3024 Register addr = GetGlobalBaseAndOffset(global, &pinned, &offset);
3025 LiftoffRegister value =
3026 pinned.set(__ GetUnusedRegister(reg_class_for(kind), pinned));
3027 LoadType type = LoadType::ForValueKind(kind);
3028 __ Load(value, addr, no_reg, offset, type, nullptr, false);
3029 __ PushRegister(kind, value);
3030 }
3031
3032 void GlobalSet(FullDecoder* decoder, const Value&,
3033 const GlobalIndexImmediate& imm) {
3034 auto* global = &env_->module->globals[imm.index];
3035 ValueKind kind = global->type.kind();
3036 if (!CheckSupportedType(decoder, kind, "global")) {
3037 return;
3038 }
3039
3040 if (is_reference(kind)) {
3041 if (global->mutability && global->imported) {
3042 LiftoffRegList pinned;
3043 Register value = pinned.set(__ PopToRegister(pinned)).gp();
3046 GetBaseAndOffsetForImportedMutableExternRefGlobal(global, &pinned,
3047 &base, &offset);
3048 __ StoreTaggedPointer(base, offset, 0, value, pinned);
3049 return;
3050 }
3051
3052 LiftoffRegList pinned;
3053 Register globals_buffer =
3054 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
3055 LOAD_TAGGED_PTR_INSTANCE_FIELD(globals_buffer, TaggedGlobalsBuffer,
3056 pinned);
3057 Register value = pinned.set(__ PopToRegister(pinned)).gp();
3058 __ StoreTaggedPointer(globals_buffer, no_reg,
3060 imm.global->offset),
3061 value, pinned);
3062 return;
3063 }
3064 LiftoffRegList pinned;
3065 uint32_t offset = 0;
3066 Register addr = GetGlobalBaseAndOffset(global, &pinned, &offset);
3067 LiftoffRegister reg = pinned.set(__ PopToRegister(pinned));
3068 StoreType type = StoreType::ForValueKind(kind);
3069 __ Store(addr, no_reg, offset, reg, type, {}, nullptr, false);
3070 }
3071
3072 void TableGet(FullDecoder* decoder, const Value&, Value*,
3073 const TableIndexImmediate& imm) {
3074 Register index_high_word = no_reg;
3075 LiftoffRegList pinned;
3076 VarState table_index{kI32, static_cast<int>(imm.index), 0};
3077
3078 // Convert the index to the table to an intptr.
3079 VarState index = PopIndexToVarState(&index_high_word, &pinned);
3080 // Trap if any bit in the high word was set.
3081 CheckHighWordEmptyForTableType(decoder, index_high_word, &pinned);
3082
3083 ValueType type = imm.table->type;
3084 bool is_funcref = IsSubtypeOf(type, kWasmFuncRef, env_->module);
3085 auto stub =
3086 is_funcref ? Builtin::kWasmTableGetFuncRef : Builtin::kWasmTableGet;
3087
3088 CallBuiltin(stub, MakeSig::Returns(type.kind()).Params(kI32, kIntPtrKind),
3089 {table_index, index}, decoder->position());
3090
3091 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
3092
3093 __ PushRegister(type.kind(), LiftoffRegister(kReturnRegister0));
3094 }
3095
3096 void TableSet(FullDecoder* decoder, const Value&, const Value&,
3097 const TableIndexImmediate& imm) {
3098 Register index_high_word = no_reg;
3099 LiftoffRegList pinned;
3100 VarState table_index{kI32, static_cast<int>(imm.index), 0};
3101
3102 VarState value = __ PopVarState();
3103 if (value.is_reg()) pinned.set(value.reg());
3104 // Convert the index to the table to an intptr.
3105 VarState index = PopIndexToVarState(&index_high_word, &pinned);
3106 // Trap if any bit in the high word was set.
3107 CheckHighWordEmptyForTableType(decoder, index_high_word, &pinned);
3108 VarState extract_shared_part{kI32, 0, 0};
3109
3110 bool is_funcref = IsSubtypeOf(imm.table->type, kWasmFuncRef, env_->module);
3111 auto stub =
3112 is_funcref ? Builtin::kWasmTableSetFuncRef : Builtin::kWasmTableSet;
3113
3114 CallBuiltin(stub, MakeSig::Params(kI32, kI32, kIntPtrKind, kRefNull),
3115 {table_index, extract_shared_part, index, value},
3116 decoder->position());
3117
3118 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
3119 }
3120
3121 Builtin GetBuiltinForTrapReason(TrapReason reason) {
3122 switch (reason) {
3123#define RUNTIME_STUB_FOR_TRAP(trap_reason) \
3124 case k##trap_reason: \
3125 return Builtin::kThrowWasm##trap_reason;
3126
3128#undef RUNTIME_STUB_FOR_TRAP
3129 default:
3130 UNREACHABLE();
3131 }
3132 }
3133
3134 void Trap(FullDecoder* decoder, TrapReason reason) {
3135 OolTrapLabel trap =
3136 AddOutOfLineTrap(decoder, GetBuiltinForTrapReason(reason));
3137 __ emit_jump(trap.label());
3138 __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap);
3139 }
3140
3141 void AssertNullTypecheckImpl(FullDecoder* decoder, const Value& arg,
3142 Value* result, Condition cond) {
3143 LiftoffRegList pinned;
3144 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
3145 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned);
3146 LoadNullValueForCompare(null.gp(), pinned, arg.type);
3147 {
3148 OolTrapLabel trap =
3149 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapIllegalCast);
3150 __ emit_cond_jump(cond, trap.label(), kRefNull, obj.gp(), null.gp(),
3151 trap.frozen());
3152 }
3153 __ PushRegister(kRefNull, obj);
3154 }
3155
3156 void AssertNullTypecheck(FullDecoder* decoder, const Value& arg,
3157 Value* result) {
3158 AssertNullTypecheckImpl(decoder, arg, result, kNotEqual);
3159 }
3160
3161 void AssertNotNullTypecheck(FullDecoder* decoder, const Value& arg,
3162 Value* result) {
3163 AssertNullTypecheckImpl(decoder, arg, result, kEqual);
3164 }
3165
3166 void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) {
3167 unsupported(decoder, kOtherReason, "testing opcode");
3168 }
3169
3170 void Select(FullDecoder* decoder, const Value& cond, const Value& fval,
3171 const Value& tval, Value* result) {
3172 LiftoffRegList pinned;
3173 Register condition = pinned.set(__ PopToRegister()).gp();
3174 ValueKind kind = __ cache_state()->stack_state.end()[-1].kind();
3175 DCHECK(CompatibleStackSlotTypes(
3176 kind, __ cache_state()->stack_state.end()[-2].kind()));
3177 LiftoffRegister false_value = pinned.set(__ PopToRegister(pinned));
3178 LiftoffRegister true_value = __ PopToRegister(pinned);
3179 LiftoffRegister dst = __ GetUnusedRegister(true_value.reg_class(),
3180 {true_value, false_value}, {});
3181 if (!__ emit_select(dst, condition, true_value, false_value, kind)) {
3182 FREEZE_STATE(frozen);
3183 // Emit generic code (using branches) instead.
3184 Label cont;
3185 Label case_false;
3186 __ emit_cond_jump(kEqual, &case_false, kI32, condition, no_reg, frozen);
3187 if (dst != true_value) __ Move(dst, true_value, kind);
3188 __ emit_jump(&cont);
3189
3190 __ bind(&case_false);
3191 if (dst != false_value) __ Move(dst, false_value, kind);
3192 __ bind(&cont);
3193 }
3194 __ PushRegister(kind, dst);
3195 }
3196
3197 void BrImpl(FullDecoder* decoder, Control* target) {
3198 if (dynamic_tiering()) {
3199 if (target->is_loop()) {
3200 DCHECK(target->label.get()->is_bound());
3201 int jump_distance = __ pc_offset() - target->label.get()->pos();
3202 TierupCheck(decoder, decoder->position(), jump_distance);
3203 } else {
3204 // To estimate time spent in this function more accurately, we could
3205 // increment the tiering budget on forward jumps. However, we don't
3206 // know the jump distance yet; using a blanket value has been tried
3207 // and found to not make a difference.
3208 }
3209 }
3210 if (target->br_merge()->reached) {
3211 __ MergeStackWith(target->label_state, target->br_merge()->arity,
3212 target->is_loop() ? LiftoffAssembler::kBackwardJump
3214 } else {
3215 target->label_state =
3216 __ MergeIntoNewState(__ num_locals(), target->br_merge()->arity,
3217 target->stack_depth + target->num_exceptions);
3218 }
3219 __ jmp(target->label.get());
3220 }
3221
3222 bool NeedsTierupCheck(FullDecoder* decoder, uint32_t br_depth) {
3223 if (!dynamic_tiering()) return false;
3224 return br_depth == decoder->control_depth() - 1 ||
3225 decoder->control_at(br_depth)->is_loop();
3226 }
3227
3228 void BrOrRet(FullDecoder* decoder, uint32_t depth) {
3229 if (depth == decoder->control_depth() - 1) {
3230 ReturnImpl(decoder);
3231 } else {
3232 BrImpl(decoder, decoder->control_at(depth));
3233 }
3234 }
3235
3236 void BrIf(FullDecoder* decoder, const Value& /* cond */, uint32_t depth) {
3237 // Avoid having sequences of branches do duplicate work.
3238 if (depth != decoder->control_depth() - 1) {
3239 __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {});
3240 }
3241
3242 Label cont_false;
3243
3244 // Test the condition on the value stack, jump to {cont_false} if zero.
3245 std::optional<FreezeCacheState> frozen;
3246 JumpIfFalse(decoder, &cont_false, frozen);
3247
3248 BrOrRet(decoder, depth);
3249
3250 __ bind(&cont_false);
3251 }
3252
3253 // Generate a branch table case, potentially reusing previously generated
3254 // stack transfer code.
3255 void GenerateBrCase(FullDecoder* decoder, uint32_t br_depth,
3256 ZoneMap<uint32_t, MovableLabel>* br_targets) {
3257 auto [iterator, is_new_target] = br_targets->emplace(br_depth, zone_);
3258 Label* label = iterator->second.get();
3259 DCHECK_EQ(is_new_target, !label->is_bound());
3260 if (is_new_target) {
3261 __ bind(label);
3262 BrOrRet(decoder, br_depth);
3263 } else {
3264 __ jmp(label);
3265 }
3266 }
3267
3268 // Generate a branch table for input in [min, max).
3269 // TODO(wasm): Generate a real branch table (like TF TableSwitch).
3270 void GenerateBrTable(FullDecoder* decoder, LiftoffRegister value,
3271 uint32_t min, uint32_t max,
3272 BranchTableIterator<ValidationTag>* table_iterator,
3273 ZoneMap<uint32_t, MovableLabel>* br_targets,
3274 const FreezeCacheState& frozen) {
3275 DCHECK_LT(min, max);
3276 // Check base case.
3277 if (max == min + 1) {
3278 DCHECK_EQ(min, table_iterator->cur_index());
3279 GenerateBrCase(decoder, table_iterator->next(), br_targets);
3280 return;
3281 }
3282
3283 uint32_t split = min + (max - min) / 2;
3284 Label upper_half;
3285 __ emit_i32_cond_jumpi(kUnsignedGreaterThanEqual, &upper_half, value.gp(),
3286 split, frozen);
3287 // Emit br table for lower half:
3288 GenerateBrTable(decoder, value, min, split, table_iterator, br_targets,
3289 frozen);
3290 __ bind(&upper_half);
3291 // table_iterator will trigger a DCHECK if we don't stop decoding now.
3292 if (did_bailout()) return;
3293 // Emit br table for upper half:
3294 GenerateBrTable(decoder, value, split, max, table_iterator, br_targets,
3295 frozen);
3296 }
3297
3298 void BrTable(FullDecoder* decoder, const BranchTableImmediate& imm,
3299 const Value& key) {
3300 LiftoffRegList pinned;
3301 LiftoffRegister value = pinned.set(__ PopToRegister());
3302
3303 {
3304 // All targets must have the same arity (checked by validation), so
3305 // we can just sample any of them to find that arity.
3306 auto [sample_depth, unused_length] =
3307 decoder->read_u32v<Decoder::NoValidationTag>(imm.table,
3308 "first depth");
3309 __ PrepareForBranch(decoder->control_at(sample_depth)->br_merge()->arity,
3310 pinned);
3311 }
3312
3313 BranchTableIterator<ValidationTag> table_iterator{decoder, imm};
3314 ZoneMap<uint32_t, MovableLabel> br_targets{zone_};
3315
3316 if (imm.table_count > 0) {
3317 FREEZE_STATE(frozen);
3318 Label case_default;
3319 __ emit_i32_cond_jumpi(kUnsignedGreaterThanEqual, &case_default,
3320 value.gp(), imm.table_count, frozen);
3321
3322 GenerateBrTable(decoder, value, 0, imm.table_count, &table_iterator,
3323 &br_targets, frozen);
3324
3325 __ bind(&case_default);
3326 // table_iterator will trigger a DCHECK if we don't stop decoding now.
3327 if (did_bailout()) return;
3328 }
3329
3330 // Generate the default case.
3331 GenerateBrCase(decoder, table_iterator.next(), &br_targets);
3332 DCHECK(!table_iterator.has_next());
3333 }
3334
3335 void Else(FullDecoder* decoder, Control* c) {
3336 if (c->reachable()) {
3337 if (c->end_merge.reached) {
3338 __ MergeFullStackWith(c->label_state);
3339 } else {
3340 c->label_state =
3341 __ MergeIntoNewState(__ num_locals(), c->end_merge.arity,
3342 c->stack_depth + c->num_exceptions);
3343 }
3344 __ emit_jump(c->label.get());
3345 }
3346 __ bind(c->else_state->label.get());
3347 __ cache_state()->Steal(c->else_state->state);
3348 }
3349
3350 SpilledRegistersForInspection* GetSpilledRegistersForInspection() {
3351 DCHECK(for_debugging_);
3352 // If we are generating debugging code, we really need to spill all
3353 // registers to make them inspectable when stopping at the trap.
3354 auto* spilled = zone_->New<SpilledRegistersForInspection>(zone_);
3355 for (uint32_t i = 0, e = __ cache_state()->stack_height(); i < e; ++i) {
3356 auto& slot = __ cache_state()->stack_state[i];
3357 if (!slot.is_reg()) continue;
3358 spilled->entries.push_back(SpilledRegistersForInspection::Entry{
3359 slot.offset(), slot.reg(), slot.kind()});
3360 __ RecordUsedSpillOffset(slot.offset());
3361 }
3362 return spilled;
3363 }
3364
3365 // TODO(mliedtke): Replace all occurrences with the new mechanism!
3366 Label* AddOutOfLineTrapDeprecated(FullDecoder* decoder, Builtin builtin) {
3367 return AddOutOfLineTrap(decoder, builtin).label();
3368 }
3369
3370 class OolTrapLabel {
3371 public:
3372 OolTrapLabel(LiftoffAssembler& assembler, Label* label)
3373 : label_(label), freeze_(assembler) {}
3374
3375 Label* label() const { return label_; }
3376 const FreezeCacheState& frozen() const { return freeze_; }
3377
3378 private:
3379 Label* label_;
3380 FreezeCacheState freeze_;
3381 };
3382
3383 OolTrapLabel AddOutOfLineTrap(FullDecoder* decoder, Builtin builtin) {
3384 DCHECK_IMPLIES(builtin == Builtin::kThrowWasmTrapMemOutOfBounds,
3385 v8_flags.wasm_bounds_checks);
3386 OutOfLineSafepointInfo* safepoint_info = nullptr;
3387 // Execution does not return after a trap. Therefore we don't have to define
3388 // a safepoint for traps that would preserve references on the stack.
3389 // However, if this is debug code, then we have to preserve the references
3390 // so that they can be inspected.
3392 safepoint_info = zone_->New<OutOfLineSafepointInfo>(zone_);
3393 __ cache_state()->GetTaggedSlotsForOOLCode(
3394 &safepoint_info->slots, &safepoint_info->spills,
3396 }
3397 out_of_line_code_.push_back(OutOfLineCode::Trap(
3398 zone_, builtin, decoder->position(),
3399 V8_UNLIKELY(for_debugging_) ? GetSpilledRegistersForInspection()
3400 : nullptr,
3401 safepoint_info, RegisterOOLDebugSideTableEntry(decoder)));
3402 return OolTrapLabel(asm_, out_of_line_code_.back().label.get());
3403 }
3404
3405 enum ForceCheck : bool { kDoForceCheck = true, kDontForceCheck = false };
3406 enum AlignmentCheck : bool {
3407 kCheckAlignment = true,
3408 kDontCheckAlignment = false
3409 };
3410
3411 // Returns the GP {index} register holding the ptrsized index.
3412 // Note that the {index} will typically not be pinned, but the returned
3413 // register will be pinned by the caller. This avoids one pinned register if
3414 // {full_index} is a pair.
3415 Register BoundsCheckMem(FullDecoder* decoder, const WasmMemory* memory,
3416 uint32_t access_size, uint64_t offset,
3417 LiftoffRegister index, LiftoffRegList pinned,
3418 ForceCheck force_check,
3419 AlignmentCheck check_alignment) {
3420 // The decoder ensures that the access is not statically OOB.
3422 memory->max_memory_size));
3423
3424 wasm::BoundsCheckStrategy bounds_checks = memory->bounds_checks;
3425
3426 // After bounds checking, we know that the index must be ptrsize, hence only
3427 // look at the lower word on 32-bit systems (the high word is bounds-checked
3428 // further down).
3429 Register index_ptrsize =
3430 kNeedI64RegPair && index.is_gp_pair() ? index.low_gp() : index.gp();
3431
3432 if (check_alignment) {
3433 AlignmentCheckMem(decoder, access_size, offset, index_ptrsize,
3434 pinned | LiftoffRegList{index});
3435 }
3436
3437 // Without bounds checks (testing only), just return the ptrsize index.
3438 if (V8_UNLIKELY(bounds_checks == kNoBoundsChecks)) {
3439 return index_ptrsize;
3440 }
3441
3442 // We already checked that an access at `offset` is within max memory size.
3443 uintptr_t end_offset = offset + access_size - 1u;
3444 DCHECK_LT(end_offset, memory->max_memory_size);
3445
3446 // Early return for trap handler.
3447 bool use_trap_handler = !force_check && bounds_checks == kTrapHandler;
3448
3450 memory->is_memory64() && !v8_flags.wasm_memory64_trap_handling,
3451 bounds_checks == kExplicitBoundsChecks);
3452#if V8_TRAP_HANDLER_SUPPORTED
3453 if (use_trap_handler) {
3454#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64
3455 if (memory->is_memory64()) {
3456 FREEZE_STATE(trapping);
3457 OolTrapLabel trap =
3458 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
3459 SCOPED_CODE_COMMENT("bounds check memory");
3460 // Bounds check `index` against `max_mem_size - end_offset`, such that
3461 // at runtime `index + end_offset` will be < `max_mem_size`, where the
3462 // trap handler can handle out-of-bound accesses.
3463 __ set_trap_on_oob_mem64(index_ptrsize, kMaxMemory64Size - end_offset,
3464 trap.label());
3465 }
3466#else
3467 CHECK(!memory->is_memory64());
3468#endif // V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_X64
3469
3470 // With trap handlers we should not have a register pair as input (we
3471 // would only return the lower half).
3472 DCHECK(index.is_gp());
3473 return index_ptrsize;
3474 }
3475#else
3476 CHECK(!use_trap_handler);
3477#endif // V8_TRAP_HANDLER_SUPPORTED
3478
3479 SCOPED_CODE_COMMENT("bounds check memory");
3480
3481 pinned.set(index_ptrsize);
3482 // Convert the index to ptrsize, bounds-checking the high word on 32-bit
3483 // systems for memory64.
3484 if (!memory->is_memory64()) {
3485 __ emit_u32_to_uintptr(index_ptrsize, index_ptrsize);
3486 } else if (kSystemPointerSize == kInt32Size) {
3487 DCHECK_GE(kMaxUInt32, memory->max_memory_size);
3488 OolTrapLabel trap =
3489 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
3490 __ emit_cond_jump(kNotZero, trap.label(), kI32, index.high_gp(), no_reg,
3491 trap.frozen());
3492 }
3493
3494 // Note that allocating the registers here before creating the trap label is
3495 // needed to prevent the tagged bits for the ool safe point to be
3496 // invalidated!
3497 LiftoffRegister end_offset_reg =
3498 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
3499 LiftoffRegister mem_size = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
3500
3501 // TODO(13957): Clamp the loaded memory size to a safe value.
3502 if (memory->index == 0) {
3503 LOAD_INSTANCE_FIELD(mem_size.gp(), Memory0Size, kSystemPointerSize,
3504 pinned);
3505 } else {
3506 LOAD_PROTECTED_PTR_INSTANCE_FIELD(mem_size.gp(), MemoryBasesAndSizes,
3507 pinned);
3508 int buffer_offset =
3510 kSystemPointerSize * (memory->index * 2 + 1);
3511 __ LoadFullPointer(mem_size.gp(), mem_size.gp(), buffer_offset);
3512 }
3513
3514 // {for_debugging_} needs spill slots in out of line code.
3515 OolTrapLabel trap =
3516 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
3517
3518 __ LoadConstant(end_offset_reg, WasmValue::ForUintPtr(end_offset));
3519
3520 // If the end offset is larger than the smallest memory, dynamically check
3521 // the end offset against the actual memory size, which is not known at
3522 // compile time. Otherwise, only one check is required (see below).
3523 if (end_offset > memory->min_memory_size) {
3524 __ emit_cond_jump(kUnsignedGreaterThanEqual, trap.label(), kIntPtrKind,
3525 end_offset_reg.gp(), mem_size.gp(), trap.frozen());
3526 }
3527
3528 // Just reuse the end_offset register for computing the effective size
3529 // (which is >= 0 because of the check above).
3530 LiftoffRegister effective_size_reg = end_offset_reg;
3531 __ emit_ptrsize_sub(effective_size_reg.gp(), mem_size.gp(),
3532 end_offset_reg.gp());
3533
3534 __ emit_cond_jump(kUnsignedGreaterThanEqual, trap.label(), kIntPtrKind,
3535 index_ptrsize, effective_size_reg.gp(), trap.frozen());
3536 return index_ptrsize;
3537 }
3538
3539 void AlignmentCheckMem(FullDecoder* decoder, uint32_t access_size,
3540 uintptr_t offset, Register index,
3541 LiftoffRegList pinned) {
3542 DCHECK_NE(0, access_size);
3543 // For access_size 1 there is no minimum alignment.
3544 if (access_size == 1) return;
3545 SCOPED_CODE_COMMENT("alignment check");
3546 Register address = __ GetUnusedRegister(kGpReg, pinned).gp();
3547 OolTrapLabel trap =
3548 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapUnalignedAccess);
3549
3550 const uint32_t align_mask = access_size - 1;
3551 if ((offset & align_mask) == 0) {
3552 // If {offset} is aligned, we can produce faster code.
3553
3554 // TODO(ahaas): On Intel, the "test" instruction implicitly computes the
3555 // AND of two operands. We could introduce a new variant of
3556 // {emit_cond_jump} to use the "test" instruction without the "and" here.
3557 // Then we can also avoid using the temp register here.
3558 __ emit_i32_andi(address, index, align_mask);
3559 __ emit_cond_jump(kNotEqual, trap.label(), kI32, address, no_reg,
3560 trap.frozen());
3561 } else {
3562 // For alignment checks we only look at the lower 32-bits in {offset}.
3563 __ emit_i32_addi(address, index, static_cast<uint32_t>(offset));
3564 __ emit_i32_andi(address, address, align_mask);
3565 __ emit_cond_jump(kNotEqual, trap.label(), kI32, address, no_reg,
3566 trap.frozen());
3567 }
3568 }
3569
3570 void TraceMemoryOperation(bool is_store, MachineRepresentation rep,
3571 Register index, uintptr_t offset,
3573 // Before making the runtime call, spill all cache registers.
3574 __ SpillAllRegisters();
3575
3576 LiftoffRegList pinned;
3577 if (index != no_reg) pinned.set(index);
3578 // Get one register for computing the effective offset (offset + index).
3579 LiftoffRegister effective_offset =
3580 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
3581 // TODO(14259): Support multiple memories.
3582 const WasmMemory* memory = env_->module->memories.data();
3583 if (memory->is_memory64() && !kNeedI64RegPair) {
3584 __ LoadConstant(effective_offset,
3585 WasmValue(static_cast<uint64_t>(offset)));
3586 if (index != no_reg) {
3587 __ emit_i64_add(effective_offset, effective_offset,
3588 LiftoffRegister(index));
3589 }
3590 } else {
3591 // The offset is actually a 32-bits number when 'kNeedI64RegPair'
3592 // is true, so we just do 32-bits operations on it under memory64.
3594 __ LoadConstant(effective_offset,
3595 WasmValue(static_cast<uint32_t>(offset)));
3596 if (index != no_reg) {
3597 __ emit_i32_add(effective_offset.gp(), effective_offset.gp(), index);
3598 }
3599 }
3600
3601 // Get a register to hold the stack slot for MemoryTracingInfo.
3602 LiftoffRegister info = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
3603 // Allocate stack slot for MemoryTracingInfo.
3604 __ AllocateStackSlot(info.gp(), sizeof(MemoryTracingInfo));
3605
3606 // Reuse the {effective_offset} register for all information to be stored in
3607 // the MemoryTracingInfo struct.
3608 LiftoffRegister data = effective_offset;
3609
3610 // Now store all information into the MemoryTracingInfo struct.
3611 if (kSystemPointerSize == 8 && !memory->is_memory64()) {
3612 // Zero-extend the effective offset to u64.
3613 CHECK(__ emit_type_conversion(kExprI64UConvertI32, data, effective_offset,
3614 nullptr));
3615 }
3616 __ Store(
3617 info.gp(), no_reg, offsetof(MemoryTracingInfo, offset), data,
3618 kSystemPointerSize == 8 ? StoreType::kI64Store : StoreType::kI32Store,
3619 pinned);
3620 __ LoadConstant(data, WasmValue(is_store ? 1 : 0));
3621 __ Store(info.gp(), no_reg, offsetof(MemoryTracingInfo, is_store), data,
3622 StoreType::kI32Store8, pinned);
3623 __ LoadConstant(data, WasmValue(static_cast<int>(rep)));
3624 __ Store(info.gp(), no_reg, offsetof(MemoryTracingInfo, mem_rep), data,
3625 StoreType::kI32Store8, pinned);
3626
3627 WasmTraceMemoryDescriptor descriptor;
3628 DCHECK_EQ(0, descriptor.GetStackParameterCount());
3629 DCHECK_EQ(1, descriptor.GetRegisterParameterCount());
3630 Register param_reg = descriptor.GetRegisterParameter(0);
3631 if (info.gp() != param_reg) {
3632 __ Move(param_reg, info.gp(), kIntPtrKind);
3633 }
3634
3636 SourcePosition(position), false);
3637 __ CallBuiltin(Builtin::kWasmTraceMemory);
3638 DefineSafepoint();
3639
3640 __ DeallocateStackSlot(sizeof(MemoryTracingInfo));
3641 }
3642
3643 bool IndexStaticallyInBounds(const WasmMemory* memory,
3644 const VarState& index_slot, int access_size,
3645 uintptr_t* offset) {
3646 if (!index_slot.is_const()) return false;
3647
3648 // memory64: Sign-extend to restore the original index value.
3649 // memory32: Zero-extend the 32 bit value.
3650 const uintptr_t index =
3651 memory->is_memory64()
3652 ? static_cast<uintptr_t>(intptr_t{index_slot.i32_const()})
3653 : uintptr_t{static_cast<uint32_t>(index_slot.i32_const())};
3654 const uintptr_t effective_offset = index + *offset;
3655
3656 if (effective_offset < index // overflow
3657 || !base::IsInBounds<uintptr_t>(effective_offset, access_size,
3658 memory->min_memory_size)) {
3659 return false;
3660 }
3661
3662 *offset = effective_offset;
3663 return true;
3664 }
3665
3666 bool IndexStaticallyInBoundsAndAligned(const WasmMemory* memory,
3667 const VarState& index_slot,
3668 int access_size, uintptr_t* offset) {
3669 uintptr_t new_offset = *offset;
3670 if (IndexStaticallyInBounds(memory, index_slot, access_size, &new_offset) &&
3671 IsAligned(new_offset, access_size)) {
3672 *offset = new_offset;
3673 return true;
3674 }
3675 return false;
3676 }
3677
3678 V8_INLINE Register GetMemoryStart(int memory_index, LiftoffRegList pinned) {
3679 if (memory_index == __ cache_state()->cached_mem_index) {
3680 Register memory_start = __ cache_state()->cached_mem_start;
3681 DCHECK_NE(no_reg, memory_start);
3682 return memory_start;
3683 }
3684 return GetMemoryStart_Slow(memory_index, pinned);
3685 }
3686
3688 GetMemoryStart_Slow(int memory_index, LiftoffRegList pinned) {
3689 // This method should only be called if we cannot use the cached memory
3690 // start.
3691 DCHECK_NE(memory_index, __ cache_state()->cached_mem_index);
3692 __ cache_state()->ClearCachedMemStartRegister();
3693 SCOPED_CODE_COMMENT("load memory start");
3694 Register memory_start = __ GetUnusedRegister(kGpReg, pinned).gp();
3695 if (memory_index == 0) {
3696 LOAD_INSTANCE_FIELD(memory_start, Memory0Start, kSystemPointerSize,
3697 pinned);
3698 } else {
3699 LOAD_PROTECTED_PTR_INSTANCE_FIELD(memory_start, MemoryBasesAndSizes,
3700 pinned);
3701 int buffer_offset = wasm::ObjectAccess::ToTagged(
3703 __ LoadFullPointer(memory_start, memory_start, buffer_offset);
3704 }
3705 __ cache_state()->SetMemStartCacheRegister(memory_start, memory_index);
3706 return memory_start;
3707 }
3708
3709 void LoadMem(FullDecoder* decoder, LoadType type,
3710 const MemoryAccessImmediate& imm, const Value& index_val,
3711 Value* result) {
3712 DCHECK_EQ(type.value_type().kind(), result->type.kind());
3713 bool needs_f16_to_f32_conv = false;
3714 if (type.value() == LoadType::kF32LoadF16 &&
3715 !asm_.supports_f16_mem_access()) {
3716 needs_f16_to_f32_conv = true;
3717 type = LoadType::kI32Load16U;
3718 }
3719 ValueKind kind = type.value_type().kind();
3720 if (!CheckSupportedType(decoder, kind, "load")) return;
3721
3722 uintptr_t offset = imm.offset;
3723 Register index = no_reg;
3725
3726 // Only look at the slot, do not pop it yet (will happen in PopToRegister
3727 // below, if this is not a statically-in-bounds index).
3728 auto& index_slot = __ cache_state()->stack_state.back();
3729 DCHECK_EQ(index_val.type.kind(), index_slot.kind());
3730 bool i64_offset = imm.memory->is_memory64();
3731 DCHECK_EQ(i64_offset ? kI64 : kI32, index_slot.kind());
3732 if (IndexStaticallyInBounds(imm.memory, index_slot, type.size(), &offset)) {
3733 __ cache_state()->stack_state.pop_back();
3734 SCOPED_CODE_COMMENT("load from memory (constant offset)");
3735 LiftoffRegList pinned;
3736 Register mem = pinned.set(GetMemoryStart(imm.memory->index, pinned));
3737 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned));
3738 __ Load(value, mem, no_reg, offset, type, nullptr, true, i64_offset);
3739 if (needs_f16_to_f32_conv) {
3740 LiftoffRegister dst = __ GetUnusedRegister(kFpReg, {});
3741 auto conv_ref = ExternalReference::wasm_float16_to_float32();
3742 GenerateCCallWithStackBuffer(&dst, kVoid, kF32,
3743 {VarState{kI16, value, 0}}, conv_ref);
3744 __ PushRegister(kF32, dst);
3745 } else {
3746 __ PushRegister(kind, value);
3747 }
3748 } else {
3749 LiftoffRegister full_index = __ PopToRegister();
3750 index =
3751 BoundsCheckMem(decoder, imm.memory, type.size(), offset, full_index,
3752 {}, kDontForceCheck, kDontCheckAlignment);
3753
3754 SCOPED_CODE_COMMENT("load from memory");
3755 LiftoffRegList pinned{index};
3756
3757 // Load the memory start address only now to reduce register pressure
3758 // (important on ia32).
3759 Register mem = pinned.set(GetMemoryStart(imm.memory->index, pinned));
3760 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned));
3761
3762 uint32_t protected_load_pc = 0;
3763 __ Load(value, mem, index, offset, type, &protected_load_pc, true,
3764 i64_offset);
3765 if (imm.memory->bounds_checks == kTrapHandler) {
3766 RegisterProtectedInstruction(decoder, protected_load_pc);
3767 }
3768 if (needs_f16_to_f32_conv) {
3769 LiftoffRegister dst = __ GetUnusedRegister(kFpReg, {});
3770 auto conv_ref = ExternalReference::wasm_float16_to_float32();
3771 GenerateCCallWithStackBuffer(&dst, kVoid, kF32,
3772 {VarState{kI16, value, 0}}, conv_ref);
3773 __ PushRegister(kF32, dst);
3774 } else {
3775 __ PushRegister(kind, value);
3776 }
3777 }
3778
3779 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
3780 // TODO(14259): Implement memory tracing for multiple memories.
3781 CHECK_EQ(0, imm.memory->index);
3782 TraceMemoryOperation(false, type.mem_type().representation(), index,
3783 offset, decoder->position());
3784 }
3785 }
3786
3787 void LoadTransform(FullDecoder* decoder, LoadType type,
3788 LoadTransformationKind transform,
3789 const MemoryAccessImmediate& imm, const Value& index_val,
3790 Value* result) {
3791 CHECK(CheckSupportedType(decoder, kS128, "LoadTransform"));
3792
3793 LiftoffRegister full_index = __ PopToRegister();
3794 // For load splats and load zero, LoadType is the size of the load, and for
3795 // load extends, LoadType is the size of the lane, and it always loads 8
3796 // bytes.
3797 uint32_t access_size =
3798 transform == LoadTransformationKind::kExtend ? 8 : type.size();
3799 Register index =
3800 BoundsCheckMem(decoder, imm.memory, access_size, imm.offset, full_index,
3801 {}, kDontForceCheck, kDontCheckAlignment);
3802
3803 uintptr_t offset = imm.offset;
3804 LiftoffRegList pinned{index};
3805 CODE_COMMENT("load with transformation");
3806 Register addr = GetMemoryStart(imm.mem_index, pinned);
3807 LiftoffRegister value = __ GetUnusedRegister(reg_class_for(kS128), {});
3808 uint32_t protected_load_pc = 0;
3809 bool i64_offset = imm.memory->is_memory64();
3810 __ LoadTransform(value, addr, index, offset, type, transform,
3811 &protected_load_pc, i64_offset);
3812
3813 if (imm.memory->bounds_checks == kTrapHandler) {
3814 protected_instructions_.emplace_back(
3815 trap_handler::ProtectedInstructionData{protected_load_pc});
3817 protected_load_pc, SourcePosition(decoder->position()), true);
3818 if (for_debugging_) {
3819 DefineSafepoint(protected_load_pc);
3820 }
3821 }
3822 __ PushRegister(kS128, value);
3823
3824 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
3825 // TODO(14259): Implement memory tracing for multiple memories.
3826 CHECK_EQ(0, imm.memory->index);
3827 // Again load extend is different.
3828 MachineRepresentation mem_rep =
3831 : type.mem_type().representation();
3832 TraceMemoryOperation(false, mem_rep, index, offset, decoder->position());
3833 }
3834 }
3835
3836 void LoadLane(FullDecoder* decoder, LoadType type, const Value& _value,
3837 const Value& _index, const MemoryAccessImmediate& imm,
3838 const uint8_t laneidx, Value* _result) {
3839 if (!CheckSupportedType(decoder, kS128, "LoadLane")) {
3840 return;
3841 }
3842
3843 LiftoffRegList pinned;
3844 LiftoffRegister value = pinned.set(__ PopToRegister());
3845 LiftoffRegister full_index = __ PopToRegister();
3846 Register index =
3847 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset, full_index,
3848 pinned, kDontForceCheck, kDontCheckAlignment);
3849
3850 bool i64_offset = imm.memory->is_memory64();
3851 DCHECK_EQ(i64_offset ? kI64 : kI32, _index.type.kind());
3852
3853 uintptr_t offset = imm.offset;
3854 pinned.set(index);
3855 CODE_COMMENT("load lane");
3856 Register addr = GetMemoryStart(imm.mem_index, pinned);
3857 LiftoffRegister result = __ GetUnusedRegister(reg_class_for(kS128), {});
3858 uint32_t protected_load_pc = 0;
3859 __ LoadLane(result, value, addr, index, offset, type, laneidx,
3860 &protected_load_pc, i64_offset);
3861 if (imm.memory->bounds_checks == kTrapHandler) {
3862 protected_instructions_.emplace_back(
3863 trap_handler::ProtectedInstructionData{protected_load_pc});
3865 protected_load_pc, SourcePosition(decoder->position()), true);
3866 if (for_debugging_) {
3867 DefineSafepoint(protected_load_pc);
3868 }
3869 }
3870
3871 __ PushRegister(kS128, result);
3872
3873 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
3874 // TODO(14259): Implement memory tracing for multiple memories.
3875 CHECK_EQ(0, imm.memory->index);
3876 TraceMemoryOperation(false, type.mem_type().representation(), index,
3877 offset, decoder->position());
3878 }
3879 }
3880
3881 void StoreMem(FullDecoder* decoder, StoreType type,
3882 const MemoryAccessImmediate& imm, const Value& index_val,
3883 const Value& value_val) {
3884 ValueKind kind = type.value_type().kind();
3885 DCHECK_EQ(kind, value_val.type.kind());
3886 if (!CheckSupportedType(decoder, kind, "store")) return;
3887
3888 LiftoffRegList pinned;
3889 LiftoffRegister value = pinned.set(__ PopToRegister());
3890
3891 if (type.value() == StoreType::kF32StoreF16 &&
3892 !asm_.supports_f16_mem_access()) {
3893 type = StoreType::kI32Store16;
3894 // {value} is always a float, so can't alias with {i16}.
3896 LiftoffRegister i16 = pinned.set(__ GetUnusedRegister(kGpReg, {}));
3897 auto conv_ref = ExternalReference::wasm_float32_to_float16();
3898 GenerateCCallWithStackBuffer(&i16, kVoid, kI16,
3899 {VarState{kF32, value, 0}}, conv_ref);
3900 value = i16;
3901 }
3902
3903 uintptr_t offset = imm.offset;
3904 Register index = no_reg;
3905
3906 auto& index_slot = __ cache_state()->stack_state.back();
3907 DCHECK_EQ(index_val.type.kind(), index_slot.kind());
3908 bool i64_offset = imm.memory->is_memory64();
3909 DCHECK_EQ(i64_offset ? kI64 : kI32, index_val.type.kind());
3910 if (IndexStaticallyInBounds(imm.memory, index_slot, type.size(), &offset)) {
3911 __ cache_state()->stack_state.pop_back();
3912 SCOPED_CODE_COMMENT("store to memory (constant offset)");
3913 Register mem = pinned.set(GetMemoryStart(imm.memory->index, pinned));
3914 __ Store(mem, no_reg, offset, value, type, pinned, nullptr, true,
3915 i64_offset);
3916 } else {
3917 LiftoffRegister full_index = __ PopToRegister(pinned);
3918 ForceCheck force_check = (kPartialOOBWritesAreNoops || type.size() == 1)
3919 ? kDontForceCheck
3920 : kDoForceCheck;
3921 index =
3922 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset,
3923 full_index, pinned, force_check, kDontCheckAlignment);
3924
3925 pinned.set(index);
3926 SCOPED_CODE_COMMENT("store to memory");
3927 uint32_t protected_store_pc = 0;
3928 // Load the memory start address only now to reduce register pressure
3929 // (important on ia32).
3930 Register mem = pinned.set(GetMemoryStart(imm.memory->index, pinned));
3931 LiftoffRegList outer_pinned;
3932 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) outer_pinned.set(index);
3933 __ Store(mem, index, offset, value, type, outer_pinned,
3934 &protected_store_pc, true, i64_offset);
3935 if (imm.memory->bounds_checks == kTrapHandler) {
3936 RegisterProtectedInstruction(decoder, protected_store_pc);
3937 }
3938 }
3939
3940 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
3941 // TODO(14259): Implement memory tracing for multiple memories.
3942 CHECK_EQ(0, imm.memory->index);
3943 TraceMemoryOperation(true, type.mem_rep(), index, offset,
3944 decoder->position());
3945 }
3946 }
3947
3948 void StoreLane(FullDecoder* decoder, StoreType type,
3949 const MemoryAccessImmediate& imm, const Value& _index,
3950 const Value& _value, const uint8_t lane) {
3951 if (!CheckSupportedType(decoder, kS128, "StoreLane")) return;
3952 LiftoffRegList pinned;
3953 LiftoffRegister value = pinned.set(__ PopToRegister());
3954 LiftoffRegister full_index = __ PopToRegister(pinned);
3955 ForceCheck force_check = (kPartialOOBWritesAreNoops || type.size() == 1)
3956 ? kDontForceCheck
3957 : kDoForceCheck;
3958 Register index =
3959 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset, full_index,
3960 pinned, force_check, kDontCheckAlignment);
3961
3962 bool i64_offset = imm.memory->is_memory64();
3963 DCHECK_EQ(i64_offset ? kI64 : kI32, _index.type.kind());
3964
3965 uintptr_t offset = imm.offset;
3966 pinned.set(index);
3967 CODE_COMMENT("store lane to memory");
3968 Register addr = pinned.set(GetMemoryStart(imm.mem_index, pinned));
3969 uint32_t protected_store_pc = 0;
3970 __ StoreLane(addr, index, offset, value, type, lane, &protected_store_pc,
3971 i64_offset);
3972 if (imm.memory->bounds_checks == kTrapHandler) {
3973 protected_instructions_.emplace_back(
3974 trap_handler::ProtectedInstructionData{protected_store_pc});
3976 protected_store_pc, SourcePosition(decoder->position()), true);
3977 if (for_debugging_) {
3978 DefineSafepoint(protected_store_pc);
3979 }
3980 }
3981 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
3982 // TODO(14259): Implement memory tracing for multiple memories.
3983 CHECK_EQ(0, imm.memory->index);
3984 TraceMemoryOperation(true, type.mem_rep(), index, offset,
3985 decoder->position());
3986 }
3987 }
3988
3989 void CurrentMemoryPages(FullDecoder* /* decoder */,
3990 const MemoryIndexImmediate& imm,
3991 Value* /* result */) {
3992 LiftoffRegList pinned;
3993 LiftoffRegister mem_size = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
3994 if (imm.memory->index == 0) {
3995 LOAD_INSTANCE_FIELD(mem_size.gp(), Memory0Size, kSystemPointerSize,
3996 pinned);
3997 } else {
3998 LOAD_PROTECTED_PTR_INSTANCE_FIELD(mem_size.gp(), MemoryBasesAndSizes,
3999 pinned);
4000 int buffer_offset =
4002 kSystemPointerSize * (imm.memory->index * 2 + 1);
4003 __ LoadFullPointer(mem_size.gp(), mem_size.gp(), buffer_offset);
4004 }
4005 // Convert bytes to pages.
4006 __ emit_ptrsize_shri(mem_size.gp(), mem_size.gp(), kWasmPageSizeLog2);
4007 if (imm.memory->is_memory64() && kNeedI64RegPair) {
4008 LiftoffRegister high_word =
4009 __ GetUnusedRegister(kGpReg, LiftoffRegList{mem_size});
4010 // The high word is always 0 on 32-bit systems.
4011 __ LoadConstant(high_word, WasmValue{uint32_t{0}});
4012 mem_size = LiftoffRegister::ForPair(mem_size.gp(), high_word.gp());
4013 }
4014 __ PushRegister(imm.memory->is_memory64() ? kI64 : kI32, mem_size);
4015 }
4016
4017 void MemoryGrow(FullDecoder* decoder, const MemoryIndexImmediate& imm,
4018 const Value& value, Value* result_val) {
4019 // Pop the input, then spill all cache registers to make the runtime call.
4020 LiftoffRegList pinned;
4021 LiftoffRegister num_pages = pinned.set(__ PopToRegister());
4022 __ SpillAllRegisters();
4023
4024 LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
4025
4026 Label done;
4027
4028 if (imm.memory->is_memory64()) {
4029 // If the high word is not 0, this will always fail (would grow by
4030 // >=256TB). The int32_t value will be sign-extended below.
4031 __ LoadConstant(result, WasmValue(int32_t{-1}));
4032 if (kNeedI64RegPair) {
4033 FREEZE_STATE(all_spilled_anyway);
4034 __ emit_cond_jump(kNotEqual, &done, kI32, num_pages.high_gp(), no_reg,
4035 all_spilled_anyway);
4036 num_pages = num_pages.low();
4037 } else {
4038 LiftoffRegister high_word = __ GetUnusedRegister(kGpReg, pinned);
4039 __ emit_i64_shri(high_word, num_pages, 32);
4040 FREEZE_STATE(all_spilled_anyway);
4041 __ emit_cond_jump(kNotEqual, &done, kI32, high_word.gp(), no_reg,
4042 all_spilled_anyway);
4043 }
4044 }
4045
4046 WasmMemoryGrowDescriptor descriptor;
4047 DCHECK_EQ(0, descriptor.GetStackParameterCount());
4048 DCHECK_EQ(2, descriptor.GetRegisterParameterCount());
4049 DCHECK_EQ(machine_type(kI32), descriptor.GetParameterType(0));
4050 DCHECK_EQ(machine_type(kI32), descriptor.GetParameterType(1));
4051
4052 Register num_pages_param_reg = descriptor.GetRegisterParameter(1);
4053 if (num_pages.gp() != num_pages_param_reg) {
4054 __ Move(num_pages_param_reg, num_pages.gp(), kI32);
4055 }
4056
4057 // Load the constant after potentially moving the {num_pages} register to
4058 // avoid overwriting it.
4059 Register mem_index_param_reg = descriptor.GetRegisterParameter(0);
4060 __ LoadConstant(LiftoffRegister{mem_index_param_reg},
4061 WasmValue(imm.memory->index));
4062
4063 __ CallBuiltin(Builtin::kWasmMemoryGrow);
4064 DefineSafepoint();
4065 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
4066
4067 if (kReturnRegister0 != result.gp()) {
4068 __ Move(result.gp(), kReturnRegister0, kI32);
4069 }
4070
4071 __ bind(&done);
4072
4073 // Note: The called runtime function will update the
4074 // {WasmEngine::had_nondeterminism_} flag if growing failed
4075 // nondeterministically. So we do not have to handle this here by looking at
4076 // the return value.
4077
4078 if (imm.memory->is_memory64()) {
4079 LiftoffRegister result64 = result;
4080 if (kNeedI64RegPair) result64 = __ GetUnusedRegister(kGpRegPair, pinned);
4081 __ emit_type_conversion(kExprI64SConvertI32, result64, result, nullptr);
4082 __ PushRegister(kI64, result64);
4083 } else {
4084 __ PushRegister(kI32, result);
4085 }
4086 }
4087
4088 base::OwnedVector<ValueType> GetStackValueTypesForDebugging(
4089 FullDecoder* decoder) {
4090 DCHECK(for_debugging_);
4091 auto stack_value_types =
4092 base::OwnedVector<ValueType>::NewForOverwrite(decoder->stack_size());
4093
4094 int depth = 0;
4095 for (ValueType& type : base::Reversed(stack_value_types)) {
4096 type = decoder->stack_value(++depth)->type;
4097 }
4098 return stack_value_types;
4099 }
4100
4101 base::OwnedVector<DebugSideTable::Entry::Value>
4102 GetCurrentDebugSideTableEntries(
4103 FullDecoder* decoder,
4104 DebugSideTableBuilder::AssumeSpilling assume_spilling) {
4105 auto& stack_state = __ cache_state()->stack_state;
4106
4107#ifdef DEBUG
4108 // For value types, we use the cached {stack_value_types_for_debugging_}
4109 // vector (gathered in {NextInstruction}). This still includes call
4110 // arguments, which Liftoff has already popped at this point. Hence the size
4111 // of this vector can be larger than the Liftoff stack size. Just ignore
4112 // that and use the lower part only.
4113 size_t expected_value_stack_size =
4114 stack_state.size() - num_exceptions_ - __ num_locals();
4115 DCHECK_LE(expected_value_stack_size,
4117#endif
4118
4119 auto values =
4121 stack_state.size());
4122
4123 int index = 0;
4124 ValueType* stack_value_type_ptr = stack_value_types_for_debugging_.begin();
4125 // Iterate the operand stack control block by control block, so that we can
4126 // handle the implicit exception value for try blocks.
4127 for (int j = decoder->control_depth() - 1; j >= 0; j--) {
4128 Control* control = decoder->control_at(j);
4129 Control* next_control = j > 0 ? decoder->control_at(j - 1) : nullptr;
4130 int end_index = next_control
4131 ? next_control->stack_depth + __ num_locals() +
4132 next_control->num_exceptions
4133 : __ cache_state()->stack_height();
4134 bool exception_on_stack =
4135 control->is_try_catch() || control->is_try_catchall();
4136 for (; index < end_index; ++index) {
4137 const LiftoffVarState& slot = stack_state[index];
4138 DebugSideTable::Entry::Value& value = values[index];
4139 value.module = decoder->module_;
4140 value.index = index;
4141 if (exception_on_stack) {
4142 value.type = kWasmAnyRef.AsNonNull();
4143 exception_on_stack = false;
4144 } else if (index < static_cast<int>(__ num_locals())) {
4145 value.type = decoder->local_type(index);
4146 } else {
4147 DCHECK_LT(stack_value_type_ptr,
4149 value.type = *stack_value_type_ptr++;
4150 }
4151 DCHECK(CompatibleStackSlotTypes(slot.kind(), value.type.kind()));
4152 switch (slot.loc()) {
4153 case kIntConst:
4154 value.storage = DebugSideTable::Entry::kConstant;
4155 value.i32_const = slot.i32_const();
4156 break;
4157 case kRegister:
4158 DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling);
4159 if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) {
4160 value.storage = DebugSideTable::Entry::kRegister;
4161 value.reg_code = slot.reg().liftoff_code();
4162 break;
4163 }
4164 DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling);
4165 [[fallthrough]];
4166 case kStack:
4167 value.storage = DebugSideTable::Entry::kStack;
4168 value.stack_offset = slot.offset();
4169 break;
4170 }
4171 }
4172 }
4173 DCHECK_EQ(values.size(), index);
4174 DCHECK_EQ(
4175 stack_value_types_for_debugging_.data() + expected_value_stack_size,
4176 stack_value_type_ptr);
4177 return values;
4178 }
4179
4180 // Call this after emitting a runtime call that can show up in a stack trace
4181 // (e.g. because it can trap).
4182 void RegisterDebugSideTableEntry(
4183 FullDecoder* decoder,
4184 DebugSideTableBuilder::AssumeSpilling assume_spilling) {
4185 if (V8_LIKELY(!debug_sidetable_builder_)) return;
4186 debug_sidetable_builder_->NewEntry(
4187 __ pc_offset(),
4188 GetCurrentDebugSideTableEntries(decoder, assume_spilling).as_vector());
4189 }
4190
4191 DebugSideTableBuilder::EntryBuilder* RegisterOOLDebugSideTableEntry(
4192 FullDecoder* decoder) {
4193 if (V8_LIKELY(!debug_sidetable_builder_)) return nullptr;
4194 return debug_sidetable_builder_->NewOOLEntry(
4195 GetCurrentDebugSideTableEntries(decoder,
4196 DebugSideTableBuilder::kAssumeSpilling)
4197 .as_vector());
4198 }
4199
4200 void CallDirect(FullDecoder* decoder, const CallFunctionImmediate& imm,
4201 const Value args[], Value[]) {
4202 CallDirect(decoder, imm, args, nullptr, CallJumpMode::kCall);
4203 }
4204
4205 void CallIndirect(FullDecoder* decoder, const Value& index_val,
4206 const CallIndirectImmediate& imm, const Value args[],
4207 Value returns[]) {
4208 CallIndirectImpl(decoder, imm, CallJumpMode::kCall);
4209 }
4210
4211 void CallRef(FullDecoder* decoder, const Value& func_ref,
4212 const FunctionSig* sig, const Value args[], Value returns[]) {
4213 CallRefImpl(decoder, func_ref.type, sig, CallJumpMode::kCall);
4214 }
4215
4216 void ReturnCall(FullDecoder* decoder, const CallFunctionImmediate& imm,
4217 const Value args[]) {
4218 TierupCheckOnTailCall(decoder);
4219 CallDirect(decoder, imm, args, nullptr, CallJumpMode::kTailCall);
4220 }
4221
4222 void ReturnCallIndirect(FullDecoder* decoder, const Value& index_val,
4223 const CallIndirectImmediate& imm,
4224 const Value args[]) {
4225 TierupCheckOnTailCall(decoder);
4226 CallIndirectImpl(decoder, imm, CallJumpMode::kTailCall);
4227 }
4228
4229 void ReturnCallRef(FullDecoder* decoder, const Value& func_ref,
4230 const FunctionSig* sig, const Value args[]) {
4231 TierupCheckOnTailCall(decoder);
4232 CallRefImpl(decoder, func_ref.type, sig, CallJumpMode::kTailCall);
4233 }
4234
4235 void BrOnNull(FullDecoder* decoder, const Value& ref_object, uint32_t depth,
4236 bool pass_null_along_branch,
4237 Value* /* result_on_fallthrough */) {
4238 // Avoid having sequences of branches do duplicate work.
4239 if (depth != decoder->control_depth() - 1) {
4240 __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {});
4241 }
4242
4243 Label cont_false;
4244 LiftoffRegList pinned;
4245 LiftoffRegister ref =
4246 pinned.set(pass_null_along_branch ? __ PeekToRegister(0, pinned)
4247 : __ PopToRegister(pinned));
4248 Register null = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
4249 LoadNullValueForCompare(null, pinned, ref_object.type);
4250 {
4251 FREEZE_STATE(frozen);
4252 __ emit_cond_jump(kNotEqual, &cont_false, ref_object.type.kind(),
4253 ref.gp(), null, frozen);
4254 BrOrRet(decoder, depth);
4255 }
4256 __ bind(&cont_false);
4257 if (!pass_null_along_branch) {
4258 // We popped the value earlier, must push it back now.
4259 __ PushRegister(kRef, ref);
4260 }
4261 }
4262
4263 void BrOnNonNull(FullDecoder* decoder, const Value& ref_object,
4264 Value* /* result */, uint32_t depth,
4265 bool drop_null_on_fallthrough) {
4266 // Avoid having sequences of branches do duplicate work.
4267 if (depth != decoder->control_depth() - 1) {
4268 __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {});
4269 }
4270
4271 Label cont_false;
4272 LiftoffRegList pinned;
4273 LiftoffRegister ref = pinned.set(__ PeekToRegister(0, pinned));
4274
4275 Register null = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
4276 LoadNullValueForCompare(null, pinned, ref_object.type);
4277 {
4278 FREEZE_STATE(frozen);
4279 __ emit_cond_jump(kEqual, &cont_false, ref_object.type.kind(), ref.gp(),
4280 null, frozen);
4281
4282 BrOrRet(decoder, depth);
4283 }
4284 // Drop the reference if we are not branching.
4285 if (drop_null_on_fallthrough) __ DropValues(1);
4286 __ bind(&cont_false);
4287 }
4288
4289 template <ValueKind src_kind, ValueKind result_kind,
4290 ValueKind result_lane_kind = kVoid, typename EmitFn,
4291 typename... ExtraArgs>
4292 void EmitTerOp(EmitFn fn, LiftoffRegister dst, LiftoffRegister src1,
4293 LiftoffRegister src2, LiftoffRegister src3,
4294 ExtraArgs... extra_args) {
4295 CallEmitFn(fn, dst, src1, src2, src3, extra_args...);
4296 if (V8_UNLIKELY(detect_nondeterminism_)) {
4297 LiftoffRegList pinned{dst};
4298 if (result_kind == ValueKind::kF32 || result_kind == ValueKind::kF64) {
4299 CheckNan(dst, pinned, result_kind);
4300 } else if (result_kind == ValueKind::kS128 &&
4301 (result_lane_kind == kF32 || result_lane_kind == kF64)) {
4302 CheckS128Nan(dst, LiftoffRegList{src1, src2, src3, dst},
4303 result_lane_kind);
4304 }
4305 }
4306 __ PushRegister(result_kind, dst);
4307 }
4308
4309 template <ValueKind src_kind, ValueKind result_kind,
4310 ValueKind result_lane_kind = kVoid, typename EmitFn>
4311 void EmitTerOp(EmitFn fn) {
4312 LiftoffRegister src3 = __ PopToRegister();
4313 LiftoffRegister src2 = __ PopToRegister(LiftoffRegList{src3});
4314 LiftoffRegister src1 = __ PopToRegister(LiftoffRegList{src3, src2});
4315 static constexpr RegClass result_rc = reg_class_for(result_kind);
4316 // Reusing src1 and src2 will complicate codegen for select for some
4317 // backend, so we allow only reusing src3 (the mask), and pin src1 and src2.
4318 // Additionally, only reuse src3 if it does not alias src1/src2,
4319 // otherwise dst will also alias it src1/src2.
4320 LiftoffRegister dst =
4321 (src2 == src3 || src1 == src3)
4322 ? __ GetUnusedRegister(result_rc, LiftoffRegList{src1, src2})
4323 : __ GetUnusedRegister(result_rc, {src3},
4324 LiftoffRegList{src1, src2});
4325 EmitTerOp<src_kind, result_kind, result_lane_kind, EmitFn>(fn, dst, src1,
4326 src2, src3);
4327 }
4328
4329 void EmitRelaxedLaneSelect(int lane_width) {
4330 DCHECK(lane_width == 8 || lane_width == 32 || lane_width == 64);
4331#if defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64)
4332 if (!CpuFeatures::IsSupported(AVX)) {
4333#if defined(V8_TARGET_ARCH_IA32)
4334 // On ia32 xmm0 is not a cached register.
4335 LiftoffRegister mask = LiftoffRegister::from_uncached(xmm0);
4336#else
4337 LiftoffRegister mask(xmm0);
4338#endif
4339 __ PopToFixedRegister(mask);
4340 LiftoffRegister src2 = __ PopToModifiableRegister(LiftoffRegList{mask});
4341 LiftoffRegister src1 = __ PopToRegister(LiftoffRegList{src2, mask});
4342 EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_relaxed_laneselect,
4343 src2, src1, src2, mask, lane_width);
4344 return;
4345 }
4346#endif
4347 LiftoffRegList pinned;
4348 LiftoffRegister mask = pinned.set(__ PopToRegister(pinned));
4349 LiftoffRegister src2 = pinned.set(__ PopToRegister(pinned));
4350 LiftoffRegister src1 = pinned.set(__ PopToRegister(pinned));
4351 LiftoffRegister dst =
4352 __ GetUnusedRegister(reg_class_for(kS128), {}, pinned);
4353 EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_relaxed_laneselect,
4354 dst, src1, src2, mask, lane_width);
4355 }
4356
4357 template <typename EmitFn, typename EmitFnImm>
4358 void EmitSimdShiftOp(EmitFn fn, EmitFnImm fnImm) {
4359 static constexpr RegClass result_rc = reg_class_for(kS128);
4360
4361 VarState rhs_slot = __ cache_state()->stack_state.back();
4362 // Check if the RHS is an immediate.
4363 if (rhs_slot.is_const()) {
4364 __ cache_state()->stack_state.pop_back();
4365 int32_t imm = rhs_slot.i32_const();
4366
4367 LiftoffRegister operand = __ PopToRegister();
4368 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {operand}, {});
4369
4370 CallEmitFn(fnImm, dst, operand, imm);
4371 __ PushRegister(kS128, dst);
4372 } else {
4373 LiftoffRegister count = __ PopToRegister();
4374 LiftoffRegister operand = __ PopToRegister();
4375 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {operand}, {});
4376
4377 CallEmitFn(fn, dst, operand, count);
4378 __ PushRegister(kS128, dst);
4379 }
4380 }
4381
4382 template <ValueKind result_lane_kind>
4383 void EmitSimdFloatRoundingOpWithCFallback(
4384 bool (LiftoffAssembler::*emit_fn)(LiftoffRegister, LiftoffRegister),
4385 ExternalReference (*ext_ref)()) {
4386 static constexpr RegClass rc = reg_class_for(kS128);
4387 LiftoffRegister src = __ PopToRegister();
4388 LiftoffRegister dst = __ GetUnusedRegister(rc, {src}, {});
4389 if (!(asm_.*emit_fn)(dst, src)) {
4390 // Return v128 via stack for ARM.
4391 GenerateCCallWithStackBuffer(&dst, kVoid, kS128,
4392 {VarState{kS128, src, 0}}, ext_ref());
4393 }
4394 if (V8_UNLIKELY(detect_nondeterminism_)) {
4395 LiftoffRegList pinned{dst};
4396 CheckS128Nan(dst, pinned, result_lane_kind);
4397 }
4398 __ PushRegister(kS128, dst);
4399 }
4400
4401 template <ValueKind result_lane_kind, bool swap_lhs_rhs = false>
4402 void EmitSimdFloatBinOpWithCFallback(
4403 bool (LiftoffAssembler::*emit_fn)(LiftoffRegister, LiftoffRegister,
4404 LiftoffRegister),
4405 ExternalReference (*ext_ref)()) {
4406 static constexpr RegClass rc = reg_class_for(kS128);
4407 LiftoffRegister src2 = __ PopToRegister();
4408 LiftoffRegister src1 = __ PopToRegister(LiftoffRegList{src2});
4409 LiftoffRegister dst = __ GetUnusedRegister(rc, {src1, src2}, {});
4410
4411 if (swap_lhs_rhs) std::swap(src1, src2);
4412
4413 if (!(asm_.*emit_fn)(dst, src1, src2)) {
4414 // Return v128 via stack for ARM.
4415 GenerateCCallWithStackBuffer(
4416 &dst, kVoid, kS128,
4417 {VarState{kS128, src1, 0}, VarState{kS128, src2, 0}}, ext_ref());
4418 }
4419 if (V8_UNLIKELY(detect_nondeterminism_)) {
4420 LiftoffRegList pinned{dst};
4421 CheckS128Nan(dst, pinned, result_lane_kind);
4422 }
4423 __ PushRegister(kS128, dst);
4424 }
4425
4426 template <ValueKind result_lane_kind, typename EmitFn>
4427 void EmitSimdFmaOp(EmitFn emit_fn) {
4428 LiftoffRegList pinned;
4429 LiftoffRegister src3 = pinned.set(__ PopToRegister(pinned));
4430 LiftoffRegister src2 = pinned.set(__ PopToRegister(pinned));
4431 LiftoffRegister src1 = pinned.set(__ PopToRegister(pinned));
4432 RegClass dst_rc = reg_class_for(kS128);
4433 LiftoffRegister dst = __ GetUnusedRegister(dst_rc, {});
4434 (asm_.*emit_fn)(dst, src1, src2, src3);
4435 if (V8_UNLIKELY(detect_nondeterminism_)) {
4436 LiftoffRegList pinned_inner{dst};
4437 CheckS128Nan(dst, pinned_inner, result_lane_kind);
4438 }
4439 __ PushRegister(kS128, dst);
4440 }
4441
4442 template <ValueKind result_lane_kind, typename EmitFn>
4443 void EmitSimdFmaOpWithCFallback(EmitFn emit_fn,
4444 ExternalReference (*ext_ref)()) {
4445 LiftoffRegList pinned;
4446 LiftoffRegister src3 = pinned.set(__ PopToRegister(pinned));
4447 LiftoffRegister src2 = pinned.set(__ PopToRegister(pinned));
4448 LiftoffRegister src1 = pinned.set(__ PopToRegister(pinned));
4449 static constexpr RegClass dst_rc = reg_class_for(kS128);
4450 LiftoffRegister dst = __ GetUnusedRegister(dst_rc, {});
4451 if (!(asm_.*emit_fn)(dst, src1, src2, src3)) {
4452 // Return v128 via stack for ARM.
4453 GenerateCCallWithStackBuffer(
4454 &dst, kVoid, kS128,
4455 {VarState{kS128, src1, 0}, VarState{kS128, src2, 0},
4456 VarState{kS128, src3, 0}},
4457 ext_ref());
4458 }
4459 if (V8_UNLIKELY(detect_nondeterminism_)) {
4460 LiftoffRegList pinned_inner{dst};
4461 CheckS128Nan(dst, pinned_inner, result_lane_kind);
4462 }
4463 __ PushRegister(kS128, dst);
4464 }
4465
4466 void SimdOp(FullDecoder* decoder, WasmOpcode opcode, const Value* /* args */,
4467 Value* /* result */) {
4469 switch (opcode) {
4470 case wasm::kExprI8x16Swizzle:
4471 return EmitI8x16Swizzle(false);
4472 case wasm::kExprI8x16RelaxedSwizzle:
4473 return EmitI8x16Swizzle(true);
4474 case wasm::kExprI8x16Popcnt:
4475 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_popcnt);
4476 case wasm::kExprI8x16Splat:
4477 return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i8x16_splat);
4478 case wasm::kExprI16x8Splat:
4479 return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i16x8_splat);
4480 case wasm::kExprI32x4Splat:
4481 return EmitUnOp<kI32, kS128>(&LiftoffAssembler::emit_i32x4_splat);
4482 case wasm::kExprI64x2Splat:
4483 return EmitUnOp<kI64, kS128>(&LiftoffAssembler::emit_i64x2_splat);
4484 case wasm::kExprF16x8Splat: {
4485 auto emit_with_c_fallback = [this](LiftoffRegister dst,
4486 LiftoffRegister src) {
4487 if (asm_.emit_f16x8_splat(dst, src)) return;
4488 LiftoffRegister value = __ GetUnusedRegister(kGpReg, {});
4489 auto conv_ref = ExternalReference::wasm_float32_to_float16();
4490 GenerateCCallWithStackBuffer(&value, kVoid, kI16,
4491 {VarState{kF32, src, 0}}, conv_ref);
4492 __ emit_i16x8_splat(dst, value);
4493 };
4494 return EmitUnOp<kF32, kS128>(emit_with_c_fallback);
4495 }
4496 case wasm::kExprF32x4Splat:
4497 return EmitUnOp<kF32, kS128, kF32>(&LiftoffAssembler::emit_f32x4_splat);
4498 case wasm::kExprF64x2Splat:
4499 return EmitUnOp<kF64, kS128, kF64>(&LiftoffAssembler::emit_f64x2_splat);
4500 case wasm::kExprI8x16Eq:
4501 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_eq);
4502 case wasm::kExprI8x16Ne:
4503 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ne);
4504 case wasm::kExprI8x16LtS:
4505 return EmitBinOp<kS128, kS128, true>(
4507 case wasm::kExprI8x16LtU:
4508 return EmitBinOp<kS128, kS128, true>(
4510 case wasm::kExprI8x16GtS:
4511 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_gt_s);
4512 case wasm::kExprI8x16GtU:
4513 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_gt_u);
4514 case wasm::kExprI8x16LeS:
4515 return EmitBinOp<kS128, kS128, true>(
4517 case wasm::kExprI8x16LeU:
4518 return EmitBinOp<kS128, kS128, true>(
4520 case wasm::kExprI8x16GeS:
4521 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ge_s);
4522 case wasm::kExprI8x16GeU:
4523 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_ge_u);
4524 case wasm::kExprI16x8Eq:
4525 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_eq);
4526 case wasm::kExprI16x8Ne:
4527 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ne);
4528 case wasm::kExprI16x8LtS:
4529 return EmitBinOp<kS128, kS128, true>(
4531 case wasm::kExprI16x8LtU:
4532 return EmitBinOp<kS128, kS128, true>(
4534 case wasm::kExprI16x8GtS:
4535 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_gt_s);
4536 case wasm::kExprI16x8GtU:
4537 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_gt_u);
4538 case wasm::kExprI16x8LeS:
4539 return EmitBinOp<kS128, kS128, true>(
4541 case wasm::kExprI16x8LeU:
4542 return EmitBinOp<kS128, kS128, true>(
4544 case wasm::kExprI16x8GeS:
4545 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ge_s);
4546 case wasm::kExprI16x8GeU:
4547 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_ge_u);
4548 case wasm::kExprI32x4Eq:
4549 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_eq);
4550 case wasm::kExprI32x4Ne:
4551 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ne);
4552 case wasm::kExprI32x4LtS:
4553 return EmitBinOp<kS128, kS128, true>(
4555 case wasm::kExprI32x4LtU:
4556 return EmitBinOp<kS128, kS128, true>(
4558 case wasm::kExprI32x4GtS:
4559 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_gt_s);
4560 case wasm::kExprI32x4GtU:
4561 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_gt_u);
4562 case wasm::kExprI32x4LeS:
4563 return EmitBinOp<kS128, kS128, true>(
4565 case wasm::kExprI32x4LeU:
4566 return EmitBinOp<kS128, kS128, true>(
4568 case wasm::kExprI32x4GeS:
4569 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ge_s);
4570 case wasm::kExprI32x4GeU:
4571 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_ge_u);
4572 case wasm::kExprI64x2Eq:
4573 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_eq);
4574 case wasm::kExprI64x2Ne:
4575 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_ne);
4576 case wasm::kExprI64x2LtS:
4577 return EmitBinOp<kS128, kS128, true>(
4579 case wasm::kExprI64x2GtS:
4580 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_gt_s);
4581 case wasm::kExprI64x2LeS:
4582 return EmitBinOp<kS128, kS128, true>(
4584 case wasm::kExprI64x2GeS:
4585 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_ge_s);
4586 case wasm::kExprF16x8Eq:
4587 return EmitSimdFloatBinOpWithCFallback<kI16>(
4588 &LiftoffAssembler::emit_f16x8_eq, ExternalReference::wasm_f16x8_eq);
4589 case wasm::kExprF16x8Ne:
4590 return EmitSimdFloatBinOpWithCFallback<kI16>(
4591 &LiftoffAssembler::emit_f16x8_ne, ExternalReference::wasm_f16x8_ne);
4592 case wasm::kExprF16x8Lt:
4593 return EmitSimdFloatBinOpWithCFallback<kI16>(
4594 &LiftoffAssembler::emit_f16x8_lt, ExternalReference::wasm_f16x8_lt);
4595 case wasm::kExprF16x8Gt:
4596 return EmitSimdFloatBinOpWithCFallback<kI16, true>(
4597 &LiftoffAssembler::emit_f16x8_lt, ExternalReference::wasm_f16x8_lt);
4598 case wasm::kExprF16x8Le:
4599 return EmitSimdFloatBinOpWithCFallback<kI16>(
4600 &LiftoffAssembler::emit_f16x8_le, ExternalReference::wasm_f16x8_le);
4601 case wasm::kExprF16x8Ge:
4602 return EmitSimdFloatBinOpWithCFallback<kI16, true>(
4603 &LiftoffAssembler::emit_f16x8_le, ExternalReference::wasm_f16x8_le);
4604 case wasm::kExprF32x4Eq:
4605 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_eq);
4606 case wasm::kExprF32x4Ne:
4607 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_ne);
4608 case wasm::kExprF32x4Lt:
4609 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_lt);
4610 case wasm::kExprF32x4Gt:
4611 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f32x4_lt);
4612 case wasm::kExprF32x4Le:
4613 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f32x4_le);
4614 case wasm::kExprF32x4Ge:
4615 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f32x4_le);
4616 case wasm::kExprF64x2Eq:
4617 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_eq);
4618 case wasm::kExprF64x2Ne:
4619 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_ne);
4620 case wasm::kExprF64x2Lt:
4621 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_lt);
4622 case wasm::kExprF64x2Gt:
4623 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f64x2_lt);
4624 case wasm::kExprF64x2Le:
4625 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_f64x2_le);
4626 case wasm::kExprF64x2Ge:
4627 return EmitBinOp<kS128, kS128, true>(&LiftoffAssembler::emit_f64x2_le);
4628 case wasm::kExprS128Not:
4629 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_s128_not);
4630 case wasm::kExprS128And:
4631 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_and);
4632 case wasm::kExprS128Or:
4633 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_or);
4634 case wasm::kExprS128Xor:
4635 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_xor);
4636 case wasm::kExprS128Select:
4637 return EmitTerOp<kS128, kS128>(&LiftoffAssembler::emit_s128_select);
4638 case wasm::kExprI8x16Neg:
4639 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_neg);
4640 case wasm::kExprV128AnyTrue:
4641 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_v128_anytrue);
4642 case wasm::kExprI8x16AllTrue:
4643 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i8x16_alltrue);
4644 case wasm::kExprI8x16BitMask:
4645 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i8x16_bitmask);
4646 case wasm::kExprI8x16Shl:
4649 case wasm::kExprI8x16ShrS:
4652 case wasm::kExprI8x16ShrU:
4655 case wasm::kExprI8x16Add:
4656 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add);
4657 case wasm::kExprI8x16AddSatS:
4658 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add_sat_s);
4659 case wasm::kExprI8x16AddSatU:
4660 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_add_sat_u);
4661 case wasm::kExprI8x16Sub:
4662 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub);
4663 case wasm::kExprI8x16SubSatS:
4664 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub_sat_s);
4665 case wasm::kExprI8x16SubSatU:
4666 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_sub_sat_u);
4667 case wasm::kExprI8x16MinS:
4668 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_min_s);
4669 case wasm::kExprI8x16MinU:
4670 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_min_u);
4671 case wasm::kExprI8x16MaxS:
4672 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_max_s);
4673 case wasm::kExprI8x16MaxU:
4674 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_max_u);
4675 case wasm::kExprI16x8Neg:
4676 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_neg);
4677 case wasm::kExprI16x8AllTrue:
4678 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i16x8_alltrue);
4679 case wasm::kExprI16x8BitMask:
4680 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i16x8_bitmask);
4681 case wasm::kExprI16x8Shl:
4684 case wasm::kExprI16x8ShrS:
4687 case wasm::kExprI16x8ShrU:
4690 case wasm::kExprI16x8Add:
4691 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add);
4692 case wasm::kExprI16x8AddSatS:
4693 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add_sat_s);
4694 case wasm::kExprI16x8AddSatU:
4695 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_add_sat_u);
4696 case wasm::kExprI16x8Sub:
4697 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub);
4698 case wasm::kExprI16x8SubSatS:
4699 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub_sat_s);
4700 case wasm::kExprI16x8SubSatU:
4701 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_sub_sat_u);
4702 case wasm::kExprI16x8Mul:
4703 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_mul);
4704 case wasm::kExprI16x8MinS:
4705 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_min_s);
4706 case wasm::kExprI16x8MinU:
4707 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_min_u);
4708 case wasm::kExprI16x8MaxS:
4709 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_max_s);
4710 case wasm::kExprI16x8MaxU:
4711 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_max_u);
4712 case wasm::kExprI16x8ExtAddPairwiseI8x16S:
4713 return EmitUnOp<kS128, kS128>(
4715 case wasm::kExprI16x8ExtAddPairwiseI8x16U:
4716 return EmitUnOp<kS128, kS128>(
4718 case wasm::kExprI16x8ExtMulLowI8x16S:
4719 return EmitBinOp<kS128, kS128>(
4721 case wasm::kExprI16x8ExtMulLowI8x16U:
4722 return EmitBinOp<kS128, kS128>(
4724 case wasm::kExprI16x8ExtMulHighI8x16S:
4725 return EmitBinOp<kS128, kS128>(
4727 case wasm::kExprI16x8ExtMulHighI8x16U:
4728 return EmitBinOp<kS128, kS128>(
4730 case wasm::kExprI16x8Q15MulRSatS:
4731 return EmitBinOp<kS128, kS128>(
4733 case wasm::kExprI32x4Neg:
4734 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_neg);
4735 case wasm::kExprI32x4AllTrue:
4736 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i32x4_alltrue);
4737 case wasm::kExprI32x4BitMask:
4738 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i32x4_bitmask);
4739 case wasm::kExprI32x4Shl:
4742 case wasm::kExprI32x4ShrS:
4745 case wasm::kExprI32x4ShrU:
4748 case wasm::kExprI32x4Add:
4749 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_add);
4750 case wasm::kExprI32x4Sub:
4751 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_sub);
4752 case wasm::kExprI32x4Mul:
4753 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_mul);
4754 case wasm::kExprI32x4MinS:
4755 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_min_s);
4756 case wasm::kExprI32x4MinU:
4757 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_min_u);
4758 case wasm::kExprI32x4MaxS:
4759 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_max_s);
4760 case wasm::kExprI32x4MaxU:
4761 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_max_u);
4762 case wasm::kExprI32x4DotI16x8S:
4763 return EmitBinOp<kS128, kS128>(
4765 case wasm::kExprI32x4ExtAddPairwiseI16x8S:
4766 return EmitUnOp<kS128, kS128>(
4768 case wasm::kExprI32x4ExtAddPairwiseI16x8U:
4769 return EmitUnOp<kS128, kS128>(
4771 case wasm::kExprI32x4ExtMulLowI16x8S:
4772 return EmitBinOp<kS128, kS128>(
4774 case wasm::kExprI32x4ExtMulLowI16x8U:
4775 return EmitBinOp<kS128, kS128>(
4777 case wasm::kExprI32x4ExtMulHighI16x8S:
4778 return EmitBinOp<kS128, kS128>(
4780 case wasm::kExprI32x4ExtMulHighI16x8U:
4781 return EmitBinOp<kS128, kS128>(
4783 case wasm::kExprI64x2Neg:
4784 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_neg);
4785 case wasm::kExprI64x2AllTrue:
4786 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i64x2_alltrue);
4787 case wasm::kExprI64x2Shl:
4790 case wasm::kExprI64x2ShrS:
4793 case wasm::kExprI64x2ShrU:
4796 case wasm::kExprI64x2Add:
4797 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_add);
4798 case wasm::kExprI64x2Sub:
4799 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_sub);
4800 case wasm::kExprI64x2Mul:
4801 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_mul);
4802 case wasm::kExprI64x2ExtMulLowI32x4S:
4803 return EmitBinOp<kS128, kS128>(
4805 case wasm::kExprI64x2ExtMulLowI32x4U:
4806 return EmitBinOp<kS128, kS128>(
4808 case wasm::kExprI64x2ExtMulHighI32x4S:
4809 return EmitBinOp<kS128, kS128>(
4811 case wasm::kExprI64x2ExtMulHighI32x4U:
4812 return EmitBinOp<kS128, kS128>(
4814 case wasm::kExprI64x2BitMask:
4815 return EmitUnOp<kS128, kI32>(&LiftoffAssembler::emit_i64x2_bitmask);
4816 case wasm::kExprI64x2SConvertI32x4Low:
4817 return EmitUnOp<kS128, kS128>(
4819 case wasm::kExprI64x2SConvertI32x4High:
4820 return EmitUnOp<kS128, kS128>(
4822 case wasm::kExprI64x2UConvertI32x4Low:
4823 return EmitUnOp<kS128, kS128>(
4825 case wasm::kExprI64x2UConvertI32x4High:
4826 return EmitUnOp<kS128, kS128>(
4828 case wasm::kExprF16x8Abs:
4829 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4831 &ExternalReference::wasm_f16x8_abs);
4832 case wasm::kExprF16x8Neg:
4833 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4835 &ExternalReference::wasm_f16x8_neg);
4836 case wasm::kExprF16x8Sqrt:
4837 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4839 &ExternalReference::wasm_f16x8_sqrt);
4840 case wasm::kExprF16x8Ceil:
4841 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4843 &ExternalReference::wasm_f16x8_ceil);
4844 case wasm::kExprF16x8Floor:
4845 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4847 ExternalReference::wasm_f16x8_floor);
4848 case wasm::kExprF16x8Trunc:
4849 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4851 ExternalReference::wasm_f16x8_trunc);
4852 case wasm::kExprF16x8NearestInt:
4853 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4855 ExternalReference::wasm_f16x8_nearest_int);
4856 case wasm::kExprF16x8Add:
4857 return EmitSimdFloatBinOpWithCFallback<kF16>(
4859 ExternalReference::wasm_f16x8_add);
4860 case wasm::kExprF16x8Sub:
4861 return EmitSimdFloatBinOpWithCFallback<kF16>(
4863 ExternalReference::wasm_f16x8_sub);
4864 case wasm::kExprF16x8Mul:
4865 return EmitSimdFloatBinOpWithCFallback<kF16>(
4867 ExternalReference::wasm_f16x8_mul);
4868 case wasm::kExprF16x8Div:
4869 return EmitSimdFloatBinOpWithCFallback<kF16>(
4871 ExternalReference::wasm_f16x8_div);
4872 case wasm::kExprF16x8Min:
4873 return EmitSimdFloatBinOpWithCFallback<kF16>(
4875 ExternalReference::wasm_f16x8_min);
4876 case wasm::kExprF16x8Max:
4877 return EmitSimdFloatBinOpWithCFallback<kF16>(
4879 ExternalReference::wasm_f16x8_max);
4880 case wasm::kExprF16x8Pmin:
4881 return EmitSimdFloatBinOpWithCFallback<kF16>(
4883 ExternalReference::wasm_f16x8_pmin);
4884 case wasm::kExprF16x8Pmax:
4885 return EmitSimdFloatBinOpWithCFallback<kF16>(
4887 ExternalReference::wasm_f16x8_pmax);
4888 case wasm::kExprF32x4Abs:
4889 return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_abs);
4890 case wasm::kExprF32x4Neg:
4891 return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_neg);
4892 case wasm::kExprF32x4Sqrt:
4893 return EmitUnOp<kS128, kS128, kF32>(&LiftoffAssembler::emit_f32x4_sqrt);
4894 case wasm::kExprF32x4Ceil:
4895 return EmitSimdFloatRoundingOpWithCFallback<kF32>(
4897 &ExternalReference::wasm_f32x4_ceil);
4898 case wasm::kExprF32x4Floor:
4899 return EmitSimdFloatRoundingOpWithCFallback<kF32>(
4901 ExternalReference::wasm_f32x4_floor);
4902 case wasm::kExprF32x4Trunc:
4903 return EmitSimdFloatRoundingOpWithCFallback<kF32>(
4905 ExternalReference::wasm_f32x4_trunc);
4906 case wasm::kExprF32x4NearestInt:
4907 return EmitSimdFloatRoundingOpWithCFallback<kF32>(
4909 ExternalReference::wasm_f32x4_nearest_int);
4910 case wasm::kExprF32x4Add:
4911 return EmitBinOp<kS128, kS128, false, kF32>(
4913 case wasm::kExprF32x4Sub:
4914 return EmitBinOp<kS128, kS128, false, kF32>(
4916 case wasm::kExprF32x4Mul:
4917 return EmitBinOp<kS128, kS128, false, kF32>(
4919 case wasm::kExprF32x4Div:
4920 return EmitBinOp<kS128, kS128, false, kF32>(
4922 case wasm::kExprF32x4Min:
4923 return EmitBinOp<kS128, kS128, false, kF32>(
4925 case wasm::kExprF32x4Max:
4926 return EmitBinOp<kS128, kS128, false, kF32>(
4928 case wasm::kExprF32x4Pmin:
4929 return EmitBinOp<kS128, kS128, false, kF32>(
4931 case wasm::kExprF32x4Pmax:
4932 return EmitBinOp<kS128, kS128, false, kF32>(
4934 case wasm::kExprF64x2Abs:
4935 return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_abs);
4936 case wasm::kExprF64x2Neg:
4937 return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_neg);
4938 case wasm::kExprF64x2Sqrt:
4939 return EmitUnOp<kS128, kS128, kF64>(&LiftoffAssembler::emit_f64x2_sqrt);
4940 case wasm::kExprF64x2Ceil:
4941 return EmitSimdFloatRoundingOpWithCFallback<kF64>(
4943 &ExternalReference::wasm_f64x2_ceil);
4944 case wasm::kExprF64x2Floor:
4945 return EmitSimdFloatRoundingOpWithCFallback<kF64>(
4947 ExternalReference::wasm_f64x2_floor);
4948 case wasm::kExprF64x2Trunc:
4949 return EmitSimdFloatRoundingOpWithCFallback<kF64>(
4951 ExternalReference::wasm_f64x2_trunc);
4952 case wasm::kExprF64x2NearestInt:
4953 return EmitSimdFloatRoundingOpWithCFallback<kF64>(
4955 ExternalReference::wasm_f64x2_nearest_int);
4956 case wasm::kExprF64x2Add:
4957 return EmitBinOp<kS128, kS128, false, kF64>(
4959 case wasm::kExprF64x2Sub:
4960 return EmitBinOp<kS128, kS128, false, kF64>(
4962 case wasm::kExprF64x2Mul:
4963 return EmitBinOp<kS128, kS128, false, kF64>(
4965 case wasm::kExprF64x2Div:
4966 return EmitBinOp<kS128, kS128, false, kF64>(
4968 case wasm::kExprF64x2Min:
4969 return EmitBinOp<kS128, kS128, false, kF64>(
4971 case wasm::kExprF64x2Max:
4972 return EmitBinOp<kS128, kS128, false, kF64>(
4974 case wasm::kExprF64x2Pmin:
4975 return EmitBinOp<kS128, kS128, false, kF64>(
4977 case wasm::kExprF64x2Pmax:
4978 return EmitBinOp<kS128, kS128, false, kF64>(
4980 case wasm::kExprI32x4SConvertF32x4:
4981 return EmitUnOp<kS128, kS128, kF32>(
4983 case wasm::kExprI32x4UConvertF32x4:
4984 return EmitUnOp<kS128, kS128, kF32>(
4986 case wasm::kExprF32x4SConvertI32x4:
4987 return EmitUnOp<kS128, kS128, kF32>(
4989 case wasm::kExprF32x4UConvertI32x4:
4990 return EmitUnOp<kS128, kS128, kF32>(
4992 case wasm::kExprF32x4PromoteLowF16x8:
4993 return EmitSimdFloatRoundingOpWithCFallback<kF32>(
4995 &ExternalReference::wasm_f32x4_promote_low_f16x8);
4996 case wasm::kExprF16x8DemoteF32x4Zero:
4997 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
4999 &ExternalReference::wasm_f16x8_demote_f32x4_zero);
5000 case wasm::kExprF16x8DemoteF64x2Zero:
5001 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
5003 &ExternalReference::wasm_f16x8_demote_f64x2_zero);
5004 case wasm::kExprI16x8SConvertF16x8:
5005 return EmitSimdFloatRoundingOpWithCFallback<kI16>(
5007 &ExternalReference::wasm_i16x8_sconvert_f16x8);
5008 case wasm::kExprI16x8UConvertF16x8:
5009 return EmitSimdFloatRoundingOpWithCFallback<kI16>(
5011 &ExternalReference::wasm_i16x8_uconvert_f16x8);
5012 case wasm::kExprF16x8SConvertI16x8:
5013 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
5015 &ExternalReference::wasm_f16x8_sconvert_i16x8);
5016 case wasm::kExprF16x8UConvertI16x8:
5017 return EmitSimdFloatRoundingOpWithCFallback<kF16>(
5019 &ExternalReference::wasm_f16x8_uconvert_i16x8);
5020 case wasm::kExprI8x16SConvertI16x8:
5021 return EmitBinOp<kS128, kS128>(
5023 case wasm::kExprI8x16UConvertI16x8:
5024 return EmitBinOp<kS128, kS128>(
5026 case wasm::kExprI16x8SConvertI32x4:
5027 return EmitBinOp<kS128, kS128>(
5029 case wasm::kExprI16x8UConvertI32x4:
5030 return EmitBinOp<kS128, kS128>(
5032 case wasm::kExprI16x8SConvertI8x16Low:
5033 return EmitUnOp<kS128, kS128>(
5035 case wasm::kExprI16x8SConvertI8x16High:
5036 return EmitUnOp<kS128, kS128>(
5038 case wasm::kExprI16x8UConvertI8x16Low:
5039 return EmitUnOp<kS128, kS128>(
5041 case wasm::kExprI16x8UConvertI8x16High:
5042 return EmitUnOp<kS128, kS128>(
5044 case wasm::kExprI32x4SConvertI16x8Low:
5045 return EmitUnOp<kS128, kS128>(
5047 case wasm::kExprI32x4SConvertI16x8High:
5048 return EmitUnOp<kS128, kS128>(
5050 case wasm::kExprI32x4UConvertI16x8Low:
5051 return EmitUnOp<kS128, kS128>(
5053 case wasm::kExprI32x4UConvertI16x8High:
5054 return EmitUnOp<kS128, kS128>(
5056 case wasm::kExprS128AndNot:
5057 return EmitBinOp<kS128, kS128>(&LiftoffAssembler::emit_s128_and_not);
5058 case wasm::kExprI8x16RoundingAverageU:
5059 return EmitBinOp<kS128, kS128>(
5061 case wasm::kExprI16x8RoundingAverageU:
5062 return EmitBinOp<kS128, kS128>(
5064 case wasm::kExprI8x16Abs:
5065 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i8x16_abs);
5066 case wasm::kExprI16x8Abs:
5067 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i16x8_abs);
5068 case wasm::kExprI32x4Abs:
5069 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i32x4_abs);
5070 case wasm::kExprI64x2Abs:
5071 return EmitUnOp<kS128, kS128>(&LiftoffAssembler::emit_i64x2_abs);
5072 case wasm::kExprF64x2ConvertLowI32x4S:
5073 return EmitUnOp<kS128, kS128, kF64>(
5075 case wasm::kExprF64x2ConvertLowI32x4U:
5076 return EmitUnOp<kS128, kS128, kF64>(
5078 case wasm::kExprF64x2PromoteLowF32x4:
5079 return EmitUnOp<kS128, kS128, kF64>(
5081 case wasm::kExprF32x4DemoteF64x2Zero:
5082 return EmitUnOp<kS128, kS128, kF32>(
5084 case wasm::kExprI32x4TruncSatF64x2SZero:
5085 return EmitUnOp<kS128, kS128>(
5087 case wasm::kExprI32x4TruncSatF64x2UZero:
5088 return EmitUnOp<kS128, kS128>(
5090 case wasm::kExprF16x8Qfma:
5091 return EmitSimdFmaOpWithCFallback<kF16>(
5093 &ExternalReference::wasm_f16x8_qfma);
5094 case wasm::kExprF16x8Qfms:
5095 return EmitSimdFmaOpWithCFallback<kF16>(
5097 &ExternalReference::wasm_f16x8_qfms);
5098 case wasm::kExprF32x4Qfma:
5099 return EmitSimdFmaOp<kF32>(&LiftoffAssembler::emit_f32x4_qfma);
5100 case wasm::kExprF32x4Qfms:
5101 return EmitSimdFmaOp<kF32>(&LiftoffAssembler::emit_f32x4_qfms);
5102 case wasm::kExprF64x2Qfma:
5103 return EmitSimdFmaOp<kF64>(&LiftoffAssembler::emit_f64x2_qfma);
5104 case wasm::kExprF64x2Qfms:
5105 return EmitSimdFmaOp<kF64>(&LiftoffAssembler::emit_f64x2_qfms);
5106 case wasm::kExprI16x8RelaxedLaneSelect:
5107 case wasm::kExprI8x16RelaxedLaneSelect:
5108 // There is no special hardware instruction for 16-bit wide lanes on
5109 // any of our platforms, so fall back to bytewise selection for i16x8.
5110 return EmitRelaxedLaneSelect(8);
5111 case wasm::kExprI32x4RelaxedLaneSelect:
5112 return EmitRelaxedLaneSelect(32);
5113 case wasm::kExprI64x2RelaxedLaneSelect:
5114 return EmitRelaxedLaneSelect(64);
5115 case wasm::kExprF32x4RelaxedMin:
5116 return EmitBinOp<kS128, kS128, false, kF32>(
5118 case wasm::kExprF32x4RelaxedMax:
5119 return EmitBinOp<kS128, kS128, false, kF32>(
5121 case wasm::kExprF64x2RelaxedMin:
5122 return EmitBinOp<kS128, kS128, false, kF64>(
5124 case wasm::kExprF64x2RelaxedMax:
5125 return EmitBinOp<kS128, kS128, false, kF64>(
5127 case wasm::kExprI16x8RelaxedQ15MulRS:
5128 return EmitBinOp<kS128, kS128>(
5130 case wasm::kExprI32x4RelaxedTruncF32x4S:
5131 return EmitUnOp<kS128, kS128>(
5133 case wasm::kExprI32x4RelaxedTruncF32x4U:
5134 return EmitUnOp<kS128, kS128>(
5136 case wasm::kExprI32x4RelaxedTruncF64x2SZero:
5137 return EmitUnOp<kS128, kS128>(
5139 case wasm::kExprI32x4RelaxedTruncF64x2UZero:
5140 return EmitUnOp<kS128, kS128>(
5142 case wasm::kExprI16x8DotI8x16I7x16S:
5143 return EmitBinOp<kS128, kS128>(
5145 case wasm::kExprI32x4DotI8x16I7x16AddS: {
5146 // There is no helper for an instruction with 3 SIMD operands
5147 // and we do not expect to add any more, so inlining it here.
5148 static constexpr RegClass res_rc = reg_class_for(kS128);
5149 LiftoffRegList pinned;
5150 LiftoffRegister acc = pinned.set(__ PopToRegister(pinned));
5151 LiftoffRegister rhs = pinned.set(__ PopToRegister(pinned));
5152 LiftoffRegister lhs = pinned.set(__ PopToRegister(pinned));
5153#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32
5154 // x86 platforms save a move when dst == acc, so prefer that.
5155 LiftoffRegister dst =
5156 __ GetUnusedRegister(res_rc, {acc}, LiftoffRegList{lhs, rhs});
5157#else
5158 // On other platforms, for simplicity, we ensure that none of the
5159 // registers alias. (If we cared, it would probably be feasible to
5160 // allow {dst} to alias with {lhs} or {rhs}, but that'd be brittle.)
5161 LiftoffRegister dst = __ GetUnusedRegister(res_rc, pinned);
5162#endif
5163
5164 __ emit_i32x4_dot_i8x16_i7x16_add_s(dst, lhs, rhs, acc);
5165 __ PushRegister(kS128, dst);
5166 return;
5167 }
5168 default:
5169 UNREACHABLE();
5170 }
5171 }
5172
5173 template <ValueKind src_kind, ValueKind result_kind, typename EmitFn>
5174 void EmitSimdExtractLaneOp(EmitFn fn, const SimdLaneImmediate& imm) {
5175 static constexpr RegClass src_rc = reg_class_for(src_kind);
5176 static constexpr RegClass result_rc = reg_class_for(result_kind);
5177 LiftoffRegister lhs = __ PopToRegister();
5178 LiftoffRegister dst = src_rc == result_rc
5179 ? __ GetUnusedRegister(result_rc, {lhs}, {})
5180 : __ GetUnusedRegister(result_rc, {});
5181 fn(dst, lhs, imm.lane);
5182 __ PushRegister(result_kind, dst);
5183 }
5184
5185 template <ValueKind src2_kind, typename EmitFn>
5186 void EmitSimdReplaceLaneOp(EmitFn fn, const SimdLaneImmediate& imm) {
5187 static constexpr RegClass src1_rc = reg_class_for(kS128);
5188 static constexpr RegClass src2_rc = reg_class_for(src2_kind);
5189 static constexpr RegClass result_rc = reg_class_for(kS128);
5190 // On backends which need fp pair, src1_rc and result_rc end up being
5191 // kFpRegPair, which is != kFpReg, but we still want to pin src2 when it is
5192 // kFpReg, since it can overlap with those pairs.
5193 static constexpr bool pin_src2 = kNeedS128RegPair && src2_rc == kFpReg;
5194
5195 // Does not work for arm
5196 LiftoffRegister src2 = __ PopToRegister();
5197 LiftoffRegister src1 = (src1_rc == src2_rc || pin_src2)
5198 ? __ PopToRegister(LiftoffRegList{src2})
5199 : __
5200 PopToRegister();
5201 LiftoffRegister dst =
5202 (src2_rc == result_rc || pin_src2)
5203 ? __ GetUnusedRegister(result_rc, {src1}, LiftoffRegList{src2})
5204 : __ GetUnusedRegister(result_rc, {src1}, {});
5205 fn(dst, src1, src2, imm.lane);
5206 __ PushRegister(kS128, dst);
5207 }
5208
5209 void SimdLaneOp(FullDecoder* decoder, WasmOpcode opcode,
5210 const SimdLaneImmediate& imm,
5211 base::Vector<const Value> inputs, Value* result) {
5213 switch (opcode) {
5214#define CASE_SIMD_EXTRACT_LANE_OP(opcode, kind, fn) \
5215 case wasm::kExpr##opcode: \
5216 EmitSimdExtractLaneOp<kS128, k##kind>( \
5217 [this](LiftoffRegister dst, LiftoffRegister lhs, \
5218 uint8_t imm_lane_idx) { \
5219 __ emit_##fn(dst, lhs, imm_lane_idx); \
5220 }, \
5221 imm); \
5222 break;
5223 CASE_SIMD_EXTRACT_LANE_OP(I8x16ExtractLaneS, I32, i8x16_extract_lane_s)
5224 CASE_SIMD_EXTRACT_LANE_OP(I8x16ExtractLaneU, I32, i8x16_extract_lane_u)
5225 CASE_SIMD_EXTRACT_LANE_OP(I16x8ExtractLaneS, I32, i16x8_extract_lane_s)
5226 CASE_SIMD_EXTRACT_LANE_OP(I16x8ExtractLaneU, I32, i16x8_extract_lane_u)
5227 CASE_SIMD_EXTRACT_LANE_OP(I32x4ExtractLane, I32, i32x4_extract_lane)
5228 CASE_SIMD_EXTRACT_LANE_OP(I64x2ExtractLane, I64, i64x2_extract_lane)
5229 CASE_SIMD_EXTRACT_LANE_OP(F32x4ExtractLane, F32, f32x4_extract_lane)
5230 CASE_SIMD_EXTRACT_LANE_OP(F64x2ExtractLane, F64, f64x2_extract_lane)
5231#undef CASE_SIMD_EXTRACT_LANE_OP
5232 case wasm::kExprF16x8ExtractLane:
5233 EmitSimdExtractLaneOp<kS128, kF32>(
5234 [this](LiftoffRegister dst, LiftoffRegister lhs,
5235 uint8_t imm_lane_idx) {
5236 if (asm_.emit_f16x8_extract_lane(dst, lhs, imm_lane_idx)) return;
5237 LiftoffRegister value = __ GetUnusedRegister(kGpReg, {});
5238 __ emit_i16x8_extract_lane_u(value, lhs, imm_lane_idx);
5239 auto conv_ref = ExternalReference::wasm_float16_to_float32();
5240 GenerateCCallWithStackBuffer(
5241 &dst, kVoid, kF32, {VarState{kI16, value, 0}}, conv_ref);
5242 },
5243 imm);
5244 break;
5245#define CASE_SIMD_REPLACE_LANE_OP(opcode, kind, fn) \
5246 case wasm::kExpr##opcode: \
5247 EmitSimdReplaceLaneOp<k##kind>( \
5248 [this](LiftoffRegister dst, LiftoffRegister src1, \
5249 LiftoffRegister src2, uint8_t imm_lane_idx) { \
5250 __ emit_##fn(dst, src1, src2, imm_lane_idx); \
5251 }, \
5252 imm); \
5253 break;
5254 CASE_SIMD_REPLACE_LANE_OP(I8x16ReplaceLane, I32, i8x16_replace_lane)
5255 CASE_SIMD_REPLACE_LANE_OP(I16x8ReplaceLane, I32, i16x8_replace_lane)
5256 CASE_SIMD_REPLACE_LANE_OP(I32x4ReplaceLane, I32, i32x4_replace_lane)
5257 CASE_SIMD_REPLACE_LANE_OP(I64x2ReplaceLane, I64, i64x2_replace_lane)
5258 CASE_SIMD_REPLACE_LANE_OP(F32x4ReplaceLane, F32, f32x4_replace_lane)
5259 CASE_SIMD_REPLACE_LANE_OP(F64x2ReplaceLane, F64, f64x2_replace_lane)
5260#undef CASE_SIMD_REPLACE_LANE_OP
5261 case wasm::kExprF16x8ReplaceLane: {
5262 EmitSimdReplaceLaneOp<kI32>(
5263 [this](LiftoffRegister dst, LiftoffRegister src1,
5264 LiftoffRegister src2, uint8_t imm_lane_idx) {
5265 if (asm_.emit_f16x8_replace_lane(dst, src1, src2, imm_lane_idx)) {
5266 return;
5267 }
5268 __ PushRegister(kS128, src1);
5269 LiftoffRegister value = __ GetUnusedRegister(kGpReg, {});
5270 auto conv_ref = ExternalReference::wasm_float32_to_float16();
5271 GenerateCCallWithStackBuffer(&value, kVoid, kI16,
5272 {VarState{kF32, src2, 0}}, conv_ref);
5273 __ PopToFixedRegister(src1);
5274 __ emit_i16x8_replace_lane(dst, src1, value, imm_lane_idx);
5275 },
5276 imm);
5277 break;
5278 }
5279 default:
5280 UNREACHABLE();
5281 }
5282 }
5283
5284 void S128Const(FullDecoder* decoder, const Simd128Immediate& imm,
5285 Value* result) {
5287 constexpr RegClass result_rc = reg_class_for(kS128);
5288 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {});
5289 bool all_zeroes = std::all_of(std::begin(imm.value), std::end(imm.value),
5290 [](uint8_t v) { return v == 0; });
5291 bool all_ones = std::all_of(std::begin(imm.value), std::end(imm.value),
5292 [](uint8_t v) { return v == 0xff; });
5293 if (all_zeroes) {
5294 __ LiftoffAssembler::emit_s128_xor(dst, dst, dst);
5295 } else if (all_ones) {
5296 // Any SIMD eq will work, i32x4 is efficient on all archs.
5297 __ LiftoffAssembler::emit_i32x4_eq(dst, dst, dst);
5298 } else {
5299 __ LiftoffAssembler::emit_s128_const(dst, imm.value);
5300 }
5301 __ PushRegister(kS128, dst);
5302 }
5303
5304 void Simd8x16ShuffleOp(FullDecoder* decoder, const Simd128Immediate& imm,
5305 const Value& input0, const Value& input1,
5306 Value* result) {
5308 static constexpr RegClass result_rc = reg_class_for(kS128);
5309 LiftoffRegList pinned;
5310 LiftoffRegister rhs = pinned.set(__ PopToRegister(pinned));
5311 LiftoffRegister lhs = pinned.set(__ PopToRegister(pinned));
5312 LiftoffRegister dst = __ GetUnusedRegister(result_rc, {lhs, rhs}, {});
5313
5314 uint8_t shuffle[kSimd128Size];
5315 memcpy(shuffle, imm.value, sizeof(shuffle));
5316 bool is_swizzle;
5317 bool needs_swap;
5318 wasm::SimdShuffle::CanonicalizeShuffle(lhs == rhs, shuffle, &needs_swap,
5319 &is_swizzle);
5320 if (needs_swap) {
5321 std::swap(lhs, rhs);
5322 }
5323 __ LiftoffAssembler::emit_i8x16_shuffle(dst, lhs, rhs, shuffle, is_swizzle);
5324 __ PushRegister(kS128, dst);
5325 }
5326
5327 void ToSmi(Register reg) {
5329 __ emit_i32_shli(reg, reg, kSmiShiftSize + kSmiTagSize);
5330 } else {
5331 __ emit_i64_shli(LiftoffRegister{reg}, LiftoffRegister{reg},
5333 }
5334 }
5335
5336 void Store32BitExceptionValue(Register values_array, int* index_in_array,
5337 Register value, LiftoffRegList pinned) {
5338 Register tmp_reg = __ GetUnusedRegister(kGpReg, pinned).gp();
5339 // Get the lower half word into tmp_reg and extend to a Smi.
5340 --*index_in_array;
5341 __ emit_i32_andi(tmp_reg, value, 0xffff);
5342 ToSmi(tmp_reg);
5343 __ StoreTaggedPointer(
5344 values_array, no_reg,
5346 tmp_reg, pinned, nullptr, LiftoffAssembler::kSkipWriteBarrier);
5347
5348 // Get the upper half word into tmp_reg and extend to a Smi.
5349 --*index_in_array;
5350 __ emit_i32_shri(tmp_reg, value, 16);
5351 ToSmi(tmp_reg);
5352 __ StoreTaggedPointer(
5353 values_array, no_reg,
5355 tmp_reg, pinned, nullptr, LiftoffAssembler::kSkipWriteBarrier);
5356 }
5357
5358 void Store64BitExceptionValue(Register values_array, int* index_in_array,
5359 LiftoffRegister value, LiftoffRegList pinned) {
5360 if (kNeedI64RegPair) {
5361 Store32BitExceptionValue(values_array, index_in_array, value.low_gp(),
5362 pinned);
5363 Store32BitExceptionValue(values_array, index_in_array, value.high_gp(),
5364 pinned);
5365 } else {
5366 Store32BitExceptionValue(values_array, index_in_array, value.gp(),
5367 pinned);
5368 __ emit_i64_shri(value, value, 32);
5369 Store32BitExceptionValue(values_array, index_in_array, value.gp(),
5370 pinned);
5371 }
5372 }
5373
5374 void Load16BitExceptionValue(LiftoffRegister dst,
5375 LiftoffRegister values_array, uint32_t* index,
5376 LiftoffRegList pinned) {
5377 __ LoadSmiAsInt32(
5378 dst, values_array.gp(),
5380 (*index)++;
5381 }
5382
5383 void Load32BitExceptionValue(Register dst, LiftoffRegister values_array,
5384 uint32_t* index, LiftoffRegList pinned) {
5385 LiftoffRegister upper = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5386 Load16BitExceptionValue(upper, values_array, index, pinned);
5387 __ emit_i32_shli(upper.gp(), upper.gp(), 16);
5388 Load16BitExceptionValue(LiftoffRegister(dst), values_array, index, pinned);
5389 __ emit_i32_or(dst, upper.gp(), dst);
5390 }
5391
5392 void Load64BitExceptionValue(LiftoffRegister dst,
5393 LiftoffRegister values_array, uint32_t* index,
5394 LiftoffRegList pinned) {
5395 if (kNeedI64RegPair) {
5396 Load32BitExceptionValue(dst.high_gp(), values_array, index, pinned);
5397 Load32BitExceptionValue(dst.low_gp(), values_array, index, pinned);
5398 } else {
5399 Load16BitExceptionValue(dst, values_array, index, pinned);
5400 __ emit_i64_shli(dst, dst, 48);
5401 LiftoffRegister tmp_reg =
5402 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5403 Load16BitExceptionValue(tmp_reg, values_array, index, pinned);
5404 __ emit_i64_shli(tmp_reg, tmp_reg, 32);
5405 __ emit_i64_or(dst, tmp_reg, dst);
5406 Load16BitExceptionValue(tmp_reg, values_array, index, pinned);
5407 __ emit_i64_shli(tmp_reg, tmp_reg, 16);
5408 __ emit_i64_or(dst, tmp_reg, dst);
5409 Load16BitExceptionValue(tmp_reg, values_array, index, pinned);
5410 __ emit_i64_or(dst, tmp_reg, dst);
5411 }
5412 }
5413
5414 void StoreExceptionValue(ValueType type, Register values_array,
5415 int* index_in_array, LiftoffRegList pinned) {
5416 LiftoffRegister value = pinned.set(__ PopToRegister(pinned));
5417 switch (type.kind()) {
5418 case kI32:
5419 Store32BitExceptionValue(values_array, index_in_array, value.gp(),
5420 pinned);
5421 break;
5422 case kF32: {
5423 LiftoffRegister gp_reg =
5424 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5425 __ emit_type_conversion(kExprI32ReinterpretF32, gp_reg, value, nullptr);
5426 Store32BitExceptionValue(values_array, index_in_array, gp_reg.gp(),
5427 pinned);
5428 break;
5429 }
5430 case kI64:
5431 Store64BitExceptionValue(values_array, index_in_array, value, pinned);
5432 break;
5433 case kF64: {
5434 LiftoffRegister tmp_reg =
5435 pinned.set(__ GetUnusedRegister(reg_class_for(kI64), pinned));
5436 __ emit_type_conversion(kExprI64ReinterpretF64, tmp_reg, value,
5437 nullptr);
5438 Store64BitExceptionValue(values_array, index_in_array, tmp_reg, pinned);
5439 break;
5440 }
5441 case kS128: {
5442 LiftoffRegister tmp_reg =
5443 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5444 for (int i : {3, 2, 1, 0}) {
5445 __ emit_i32x4_extract_lane(tmp_reg, value, i);
5446 Store32BitExceptionValue(values_array, index_in_array, tmp_reg.gp(),
5447 pinned);
5448 }
5449 break;
5450 }
5451 case wasm::kRef:
5452 case wasm::kRefNull: {
5453 --(*index_in_array);
5454 __ StoreTaggedPointer(
5455 values_array, no_reg,
5457 *index_in_array),
5458 value.gp(), pinned);
5459 break;
5460 }
5461 case wasm::kI8:
5462 case wasm::kI16:
5463 case wasm::kF16:
5464 case wasm::kVoid:
5465 case wasm::kTop:
5466 case wasm::kBottom:
5467 UNREACHABLE();
5468 }
5469 }
5470
5471 void LoadExceptionValue(ValueKind kind, LiftoffRegister values_array,
5472 uint32_t* index, LiftoffRegList pinned) {
5474 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned));
5475 switch (kind) {
5476 case kI32:
5477 Load32BitExceptionValue(value.gp(), values_array, index, pinned);
5478 break;
5479 case kF32: {
5480 LiftoffRegister tmp_reg =
5481 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5482 Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned);
5483 __ emit_type_conversion(kExprF32ReinterpretI32, value, tmp_reg,
5484 nullptr);
5485 break;
5486 }
5487 case kI64:
5488 Load64BitExceptionValue(value, values_array, index, pinned);
5489 break;
5490 case kF64: {
5491 RegClass rc_i64 = reg_class_for(kI64);
5492 LiftoffRegister tmp_reg =
5493 pinned.set(__ GetUnusedRegister(rc_i64, pinned));
5494 Load64BitExceptionValue(tmp_reg, values_array, index, pinned);
5495 __ emit_type_conversion(kExprF64ReinterpretI64, value, tmp_reg,
5496 nullptr);
5497 break;
5498 }
5499 case kS128: {
5500 LiftoffRegister tmp_reg =
5501 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5502 Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned);
5503 __ emit_i32x4_splat(value, tmp_reg);
5504 for (int lane : {1, 2, 3}) {
5505 Load32BitExceptionValue(tmp_reg.gp(), values_array, index, pinned);
5506 __ emit_i32x4_replace_lane(value, value, tmp_reg, lane);
5507 }
5508 break;
5509 }
5510 case wasm::kRef:
5511 case wasm::kRefNull: {
5512 __ LoadTaggedPointer(
5513 value.gp(), values_array.gp(), no_reg,
5515 (*index)++;
5516 break;
5517 }
5518 case wasm::kI8:
5519 case wasm::kI16:
5520 case wasm::kF16:
5521 case wasm::kVoid:
5522 case wasm::kTop:
5523 case wasm::kBottom:
5524 UNREACHABLE();
5525 }
5526 __ PushRegister(kind, value);
5527 }
5528
5529 void GetExceptionValues(FullDecoder* decoder, const VarState& exception_var,
5530 const WasmTag* tag) {
5531 LiftoffRegList pinned;
5532 CODE_COMMENT("get exception values");
5533 LiftoffRegister values_array = GetExceptionProperty(
5534 exception_var, RootIndex::kwasm_exception_values_symbol);
5535 pinned.set(values_array);
5536 uint32_t index = 0;
5537 const WasmTagSig* sig = tag->sig;
5538 for (ValueType param : sig->parameters()) {
5539 LoadExceptionValue(param.kind(), values_array, &index, pinned);
5540 }
5542 }
5543
5544 void EmitLandingPad(FullDecoder* decoder, int handler_offset) {
5545 if (decoder->current_catch() == -1) return;
5546 MovableLabel handler{zone_};
5547
5548 // If we return from the throwing code normally, just skip over the handler.
5549 Label skip_handler;
5550 __ emit_jump(&skip_handler);
5551
5552 // Handler: merge into the catch state, and jump to the catch body.
5553 CODE_COMMENT("-- landing pad --");
5554 __ bind(handler.get());
5555 __ ExceptionHandler();
5556 __ PushException();
5557 handlers_.push_back({std::move(handler), handler_offset});
5558 Control* current_try =
5559 decoder->control_at(decoder->control_depth_of_current_catch());
5560 DCHECK_NOT_NULL(current_try->try_info);
5561 if (current_try->try_info->catch_reached) {
5562 __ MergeStackWith(current_try->try_info->catch_state, 1,
5564 } else {
5565 current_try->try_info->catch_state = __ MergeIntoNewState(
5566 __ num_locals(), 1,
5567 current_try->stack_depth + current_try->num_exceptions);
5568 current_try->try_info->catch_reached = true;
5569 }
5570 __ emit_jump(&current_try->try_info->catch_label);
5571
5572 __ bind(&skip_handler);
5573 // Drop the exception.
5574 __ DropValues(1);
5575 }
5576
5577 void Throw(FullDecoder* decoder, const TagIndexImmediate& imm,
5578 const Value* /* args */) {
5579 LiftoffRegList pinned;
5580
5581 // Load the encoded size in a register for the builtin call.
5582 int encoded_size = WasmExceptionPackage::GetEncodedSize(imm.tag);
5583 LiftoffRegister encoded_size_reg =
5584 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5585 __ LoadConstant(encoded_size_reg, WasmValue::ForUintPtr(encoded_size));
5586
5587 // Call the WasmAllocateFixedArray builtin to create the values array.
5588 CallBuiltin(Builtin::kWasmAllocateFixedArray,
5589 MakeSig::Returns(kIntPtrKind).Params(kIntPtrKind),
5590 {VarState{kIntPtrKind, LiftoffRegister{encoded_size_reg}, 0}},
5591 decoder->position());
5592 MaybeOSR();
5593
5594 // The FixedArray for the exception values is now in the first gp return
5595 // register.
5596 LiftoffRegister values_array{kReturnRegister0};
5597 pinned.set(values_array);
5598
5599 // Now store the exception values in the FixedArray. Do this from last to
5600 // first value, such that we can just pop them from the value stack.
5601 CODE_COMMENT("fill values array");
5602 int index = encoded_size;
5603 auto* sig = imm.tag->sig;
5604 for (size_t param_idx = sig->parameter_count(); param_idx > 0;
5605 --param_idx) {
5606 ValueType type = sig->GetParam(param_idx - 1);
5607 StoreExceptionValue(type, values_array.gp(), &index, pinned);
5608 }
5609 DCHECK_EQ(0, index);
5610
5611 // Load the exception tag.
5612 CODE_COMMENT("load exception tag");
5613 LiftoffRegister exception_tag =
5614 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
5615 LOAD_TAGGED_PTR_INSTANCE_FIELD(exception_tag.gp(), TagsTable, pinned);
5616 __ LoadTaggedPointer(
5617 exception_tag.gp(), exception_tag.gp(), no_reg,
5619
5620 // Finally, call WasmThrow.
5621 CallBuiltin(Builtin::kWasmThrow, MakeSig::Params(kIntPtrKind, kIntPtrKind),
5622 {VarState{kIntPtrKind, exception_tag, 0},
5623 VarState{kIntPtrKind, values_array, 0}},
5624 decoder->position());
5625
5626 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
5627
5628 int pc_offset = __ pc_offset();
5629 MaybeOSR();
5630 EmitLandingPad(decoder, pc_offset);
5631 }
5632
5633 void AtomicStoreMem(FullDecoder* decoder, StoreType type,
5634 const MemoryAccessImmediate& imm) {
5635 LiftoffRegList pinned;
5636 LiftoffRegister value = pinned.set(__ PopToRegister());
5637 bool i64_offset = imm.memory->is_memory64();
5638 auto& index_slot = __ cache_state() -> stack_state.back();
5639 DCHECK_EQ(i64_offset ? kI64 : kI32, index_slot.kind());
5640 uintptr_t offset = imm.offset;
5641 LiftoffRegList outer_pinned;
5642 Register index = no_reg;
5643
5644 if (IndexStaticallyInBoundsAndAligned(imm.memory, index_slot, type.size(),
5645 &offset)) {
5646 __ cache_state() -> stack_state.pop_back(); // Pop index.
5647 CODE_COMMENT("atomic store (constant offset)");
5648 } else {
5649 LiftoffRegister full_index = __ PopToRegister(pinned);
5650 index =
5651 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset,
5652 full_index, pinned, kDoForceCheck, kCheckAlignment);
5653 pinned.set(index);
5654 CODE_COMMENT("atomic store");
5655 }
5656 Register addr = pinned.set(GetMemoryStart(imm.mem_index, pinned));
5657 if (V8_UNLIKELY(v8_flags.trace_wasm_memory) && index != no_reg) {
5658 outer_pinned.set(index);
5659 }
5660 __ AtomicStore(addr, index, offset, value, type, outer_pinned, i64_offset);
5661 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
5662 // TODO(14259): Implement memory tracing for multiple memories.
5663 CHECK_EQ(0, imm.memory->index);
5664 TraceMemoryOperation(true, type.mem_rep(), index, offset,
5665 decoder->position());
5666 }
5667 }
5668
5669 void AtomicLoadMem(FullDecoder* decoder, LoadType type,
5670 const MemoryAccessImmediate& imm) {
5671 ValueKind kind = type.value_type().kind();
5672 bool i64_offset = imm.memory->is_memory64();
5673 auto& index_slot = __ cache_state() -> stack_state.back();
5674 DCHECK_EQ(i64_offset ? kI64 : kI32, index_slot.kind());
5675 uintptr_t offset = imm.offset;
5676 Register index = no_reg;
5677 LiftoffRegList pinned;
5678
5679 if (IndexStaticallyInBoundsAndAligned(imm.memory, index_slot, type.size(),
5680 &offset)) {
5681 __ cache_state() -> stack_state.pop_back(); // Pop index.
5682 CODE_COMMENT("atomic load (constant offset)");
5683 } else {
5684 LiftoffRegister full_index = __ PopToRegister();
5685 index = BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset,
5686 full_index, {}, kDoForceCheck, kCheckAlignment);
5687 pinned.set(index);
5688 CODE_COMMENT("atomic load");
5689 }
5690
5691 Register addr = pinned.set(GetMemoryStart(imm.mem_index, pinned));
5693 LiftoffRegister value = pinned.set(__ GetUnusedRegister(rc, pinned));
5694 __ AtomicLoad(value, addr, index, offset, type, pinned, i64_offset);
5695 __ PushRegister(kind, value);
5696
5697 if (V8_UNLIKELY(v8_flags.trace_wasm_memory)) {
5698 // TODO(14259): Implement memory tracing for multiple memories.
5699 CHECK_EQ(0, imm.memory->index);
5700 TraceMemoryOperation(false, type.mem_type().representation(), index,
5701 offset, decoder->position());
5702 }
5703 }
5704
5705 void AtomicBinop(FullDecoder* decoder, StoreType type,
5706 const MemoryAccessImmediate& imm,
5707 void (LiftoffAssembler::*emit_fn)(Register, Register,
5708 uintptr_t, LiftoffRegister,
5709 LiftoffRegister, StoreType,
5710 bool)) {
5711 ValueKind result_kind = type.value_type().kind();
5712 LiftoffRegList pinned;
5713 LiftoffRegister value = pinned.set(__ PopToRegister());
5714#ifdef V8_TARGET_ARCH_IA32
5715 // We have to reuse the value register as the result register so that we
5716 // don't run out of registers on ia32. For this we use the value register as
5717 // the result register if it has no other uses. Otherwise we allocate a new
5718 // register and let go of the value register to get spilled.
5719 LiftoffRegister result = value;
5720 if (__ cache_state()->is_used(value)) {
5721 result = pinned.set(__ GetUnusedRegister(value.reg_class(), pinned));
5722 __ Move(result, value, result_kind);
5723 pinned.clear(value);
5724 value = result;
5725 }
5726#else
5727 LiftoffRegister result =
5728 pinned.set(__ GetUnusedRegister(value.reg_class(), pinned));
5729#endif
5730 auto& index_slot = __ cache_state() -> stack_state.back();
5731 uintptr_t offset = imm.offset;
5732 bool i64_offset = imm.memory->is_memory64();
5733 DCHECK_EQ(i64_offset ? kI64 : kI32, index_slot.kind());
5734 Register index = no_reg;
5735
5736 if (IndexStaticallyInBoundsAndAligned(imm.memory, index_slot, type.size(),
5737 &offset)) {
5738 __ cache_state() -> stack_state.pop_back(); // Pop index.
5739 CODE_COMMENT("atomic binop (constant offset)");
5740 } else {
5741 LiftoffRegister full_index = __ PopToRegister(pinned);
5742 index =
5743 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset,
5744 full_index, pinned, kDoForceCheck, kCheckAlignment);
5745
5746 pinned.set(index);
5747 CODE_COMMENT("atomic binop");
5748 }
5749
5750 Register addr = pinned.set(GetMemoryStart(imm.mem_index, pinned));
5751 (asm_.*emit_fn)(addr, index, offset, value, result, type, i64_offset);
5752 __ PushRegister(result_kind, result);
5753 }
5754
5755 void AtomicCompareExchange(FullDecoder* decoder, StoreType type,
5756 const MemoryAccessImmediate& imm) {
5757#ifdef V8_TARGET_ARCH_IA32
5758 // On ia32 we don't have enough registers to first pop all the values off
5759 // the stack and then start with the code generation. Instead we do the
5760 // complete address calculation first, so that the address only needs a
5761 // single register. Afterwards we load all remaining values into the
5762 // other registers.
5763 LiftoffRegister full_index = __ PeekToRegister(2, {});
5764
5765 Register index =
5766 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset, full_index,
5767 {}, kDoForceCheck, kCheckAlignment);
5768 LiftoffRegList pinned{index};
5769
5770 uintptr_t offset = imm.offset;
5771 Register addr = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
5772 if (imm.memory->index == 0) {
5773 LOAD_INSTANCE_FIELD(addr, Memory0Start, kSystemPointerSize, pinned);
5774 } else {
5775 LOAD_PROTECTED_PTR_INSTANCE_FIELD(addr, MemoryBasesAndSizes, pinned);
5776 int buffer_offset =
5778 kSystemPointerSize * imm.memory->index * 2;
5779 __ LoadFullPointer(addr, addr, buffer_offset);
5780 }
5781 __ emit_i32_add(addr, addr, index);
5782 pinned.clear(LiftoffRegister(index));
5783 LiftoffRegister new_value = pinned.set(__ PopToRegister(pinned));
5784 LiftoffRegister expected = pinned.set(__ PopToRegister(pinned));
5785
5786 // Pop the index from the stack.
5787 bool i64_offset = imm.memory->is_memory64();
5788 DCHECK_EQ(i64_offset ? kI64 : kI32,
5789 __ cache_state()->stack_state.back().kind());
5790 __ DropValues(1);
5791
5792 LiftoffRegister result = expected;
5793 if (__ cache_state()->is_used(result)) __ SpillRegister(result);
5794
5795 // We already added the index to addr, so we can just pass no_reg to the
5796 // assembler now.
5797 __ AtomicCompareExchange(addr, no_reg, offset, expected, new_value, result,
5798 type, i64_offset);
5799 __ PushRegister(type.value_type().kind(), result);
5800 return;
5801#else
5802 ValueKind result_kind = type.value_type().kind();
5803 LiftoffRegList pinned;
5804 LiftoffRegister new_value = pinned.set(__ PopToRegister(pinned));
5805 LiftoffRegister expected = pinned.set(__ PopToRegister(pinned));
5806 LiftoffRegister result =
5807 pinned.set(__ GetUnusedRegister(reg_class_for(result_kind), pinned));
5808
5809 auto& index_slot = __ cache_state() -> stack_state.back();
5810 uintptr_t offset = imm.offset;
5811 bool i64_offset = imm.memory->is_memory64();
5812 DCHECK_EQ(i64_offset ? kI64 : kI32, index_slot.kind());
5813 Register index = no_reg;
5814
5815 if (IndexStaticallyInBoundsAndAligned(imm.memory, index_slot, type.size(),
5816 &offset)) {
5817 __ cache_state() -> stack_state.pop_back(); // Pop index.
5818 CODE_COMMENT("atomic cmpxchg (constant offset)");
5819 } else {
5820 LiftoffRegister full_index = __ PopToRegister(pinned);
5821 index =
5822 BoundsCheckMem(decoder, imm.memory, type.size(), imm.offset,
5823 full_index, pinned, kDoForceCheck, kCheckAlignment);
5824 pinned.set(index);
5825 CODE_COMMENT("atomic cmpxchg");
5826 }
5827
5828 Register addr = pinned.set(GetMemoryStart(imm.mem_index, pinned));
5829 __ AtomicCompareExchange(addr, index, offset, expected, new_value, result,
5830 type, i64_offset);
5831 __ PushRegister(result_kind, result);
5832#endif
5833 }
5834
5835 void CallBuiltin(Builtin builtin, const ValueKindSig& sig,
5836 std::initializer_list<VarState> params, int position) {
5838 (std::string{"Call builtin: "} + Builtins::name(builtin)));
5839 auto interface_descriptor = Builtins::CallInterfaceDescriptorFor(builtin);
5840 auto* call_descriptor = compiler::Linkage::GetStubCallDescriptor(
5841 zone_, // zone
5842 interface_descriptor, // descriptor
5843 interface_descriptor.GetStackParameterCount(), // stack parameter count
5846 StubCallMode::kCallWasmRuntimeStub); // stub call mode
5847
5848 __ PrepareBuiltinCall(&sig, call_descriptor, params);
5849 if (position != kNoSourcePosition) {
5851 __ pc_offset(), SourcePosition(position), true);
5852 }
5853 __ CallBuiltin(builtin);
5854 DefineSafepoint();
5855 }
5856
5857 void AtomicWait(FullDecoder* decoder, ValueKind kind,
5858 const MemoryAccessImmediate& imm) {
5860 ValueKind index_kind;
5861 {
5862 LiftoffRegList pinned;
5863 LiftoffRegister full_index = __ PeekToRegister(2, pinned);
5864
5865 Register index_reg =
5866 BoundsCheckMem(decoder, imm.memory, value_kind_size(kind), imm.offset,
5867 full_index, pinned, kDoForceCheck, kCheckAlignment);
5868 pinned.set(index_reg);
5869
5870 uintptr_t offset = imm.offset;
5871 Register index_plus_offset = index_reg;
5872
5873 if (__ cache_state()->is_used(LiftoffRegister(index_reg))) {
5874 index_plus_offset =
5875 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
5876 __ Move(index_plus_offset, index_reg, kIntPtrKind);
5877 }
5878 if (offset) {
5879 __ emit_ptrsize_addi(index_plus_offset, index_plus_offset, offset);
5880 }
5881
5882 VarState& index = __ cache_state()->stack_state.end()[-3];
5883
5884 // We replace the index on the value stack with the `index_plus_offset`
5885 // calculated above. Thereby the BigInt allocation below does not
5886 // overwrite the calculated value by accident.
5887 // The kind of `index_plus_offset has to be the same or smaller than the
5888 // original kind of `index`. The kind of index is kI32 for memory32, and
5889 // kI64 for memory64. On 64-bit platforms we can use in both cases the
5890 // kind of `index` also for `index_plus_offset`. Note that
5891 // `index_plus_offset` fits into a kI32 because we do a bounds check
5892 // first.
5893 // On 32-bit platforms, we have to use an kI32 also for memory64, because
5894 // `index_plus_offset` does not exist in a register pair.
5895 __ cache_state()->inc_used(LiftoffRegister(index_plus_offset));
5896 if (index.is_reg()) __ cache_state()->dec_used(index.reg());
5897 index_kind = index.kind() == kI32 ? kI32 : kIntPtrKind;
5898
5899 index = VarState{index_kind, LiftoffRegister{index_plus_offset},
5900 index.offset()};
5901 }
5902 {
5903 // Convert the top value of the stack (the timeout) from I64 to a BigInt,
5904 // which we can then pass to the atomic.wait builtin.
5905 VarState i64_timeout = __ cache_state()->stack_state.back();
5906 CallBuiltin(
5907 kNeedI64RegPair ? Builtin::kI32PairToBigInt : Builtin::kI64ToBigInt,
5908 MakeSig::Returns(kRef).Params(kI64), {i64_timeout},
5909 decoder->position());
5910 __ DropValues(1);
5911 // We put the result on the value stack so that it gets preserved across
5912 // a potential GC that may get triggered by the BigInt allocation below.
5913 __ PushRegister(kRef, LiftoffRegister(kReturnRegister0));
5914 }
5915
5916 Register expected = no_reg;
5917 if (kind == kI32) {
5918 expected = __ PeekToRegister(1, {}).gp();
5919 } else {
5920 VarState i64_expected = __ cache_state()->stack_state.end()[-2];
5921 CallBuiltin(
5922 kNeedI64RegPair ? Builtin::kI32PairToBigInt : Builtin::kI64ToBigInt,
5923 MakeSig::Returns(kRef).Params(kI64), {i64_expected},
5924 decoder->position());
5925 expected = kReturnRegister0;
5926 }
5927 ValueKind expected_kind = kind == kI32 ? kI32 : kRef;
5928
5929 VarState timeout = __ cache_state()->stack_state.end()[-1];
5930 VarState index = __ cache_state()->stack_state.end()[-3];
5931
5932 auto target = kind == kI32 ? Builtin::kWasmI32AtomicWait
5933 : Builtin::kWasmI64AtomicWait;
5934
5935 // The type of {index} can either by i32 or intptr, depending on whether
5936 // memory32 or memory64 is used. This is okay because both values get passed
5937 // by register.
5938 CallBuiltin(target, MakeSig::Params(kI32, index_kind, expected_kind, kRef),
5939 {{kI32, static_cast<int32_t>(imm.memory->index), 0},
5940 index,
5941 {expected_kind, LiftoffRegister{expected}, 0},
5942 timeout},
5943 decoder->position());
5944 // Pop parameters from the value stack.
5945 __ DropValues(3);
5946
5947 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
5948
5949 __ PushRegister(kI32, LiftoffRegister(kReturnRegister0));
5950 }
5951
5952 void AtomicNotify(FullDecoder* decoder, const MemoryAccessImmediate& imm) {
5953 LiftoffRegList pinned;
5954 LiftoffRegister num_waiters_to_wake = pinned.set(__ PopToRegister(pinned));
5955 LiftoffRegister full_index = __ PopToRegister(pinned);
5956 Register index_reg =
5957 BoundsCheckMem(decoder, imm.memory, kInt32Size, imm.offset, full_index,
5958 pinned, kDoForceCheck, kCheckAlignment);
5959 pinned.set(index_reg);
5960
5961 uintptr_t offset = imm.offset;
5962 Register addr = index_reg;
5963 if (__ cache_state()->is_used(LiftoffRegister(index_reg))) {
5964 addr = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
5965 __ Move(addr, index_reg, kIntPtrKind);
5966 }
5967 if (offset) {
5968 __ emit_ptrsize_addi(addr, addr, offset);
5969 }
5970
5971 Register mem_start = GetMemoryStart(imm.memory->index, pinned);
5972 __ emit_ptrsize_add(addr, addr, mem_start);
5973
5974 LiftoffRegister result =
5975 GenerateCCall(kI32,
5976 {{kIntPtrKind, LiftoffRegister{addr}, 0},
5977 {kI32, num_waiters_to_wake, 0}},
5978 ExternalReference::wasm_atomic_notify());
5979
5980 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
5981
5982 __ PushRegister(kI32, result);
5983 }
5984
5985#define ATOMIC_STORE_LIST(V) \
5986 V(I32AtomicStore, kI32Store) \
5987 V(I64AtomicStore, kI64Store) \
5988 V(I32AtomicStore8U, kI32Store8) \
5989 V(I32AtomicStore16U, kI32Store16) \
5990 V(I64AtomicStore8U, kI64Store8) \
5991 V(I64AtomicStore16U, kI64Store16) \
5992 V(I64AtomicStore32U, kI64Store32)
5993
5994#define ATOMIC_LOAD_LIST(V) \
5995 V(I32AtomicLoad, kI32Load) \
5996 V(I64AtomicLoad, kI64Load) \
5997 V(I32AtomicLoad8U, kI32Load8U) \
5998 V(I32AtomicLoad16U, kI32Load16U) \
5999 V(I64AtomicLoad8U, kI64Load8U) \
6000 V(I64AtomicLoad16U, kI64Load16U) \
6001 V(I64AtomicLoad32U, kI64Load32U)
6002
6003#define ATOMIC_BINOP_INSTRUCTION_LIST(V) \
6004 V(Add, I32AtomicAdd, kI32Store) \
6005 V(Add, I64AtomicAdd, kI64Store) \
6006 V(Add, I32AtomicAdd8U, kI32Store8) \
6007 V(Add, I32AtomicAdd16U, kI32Store16) \
6008 V(Add, I64AtomicAdd8U, kI64Store8) \
6009 V(Add, I64AtomicAdd16U, kI64Store16) \
6010 V(Add, I64AtomicAdd32U, kI64Store32) \
6011 V(Sub, I32AtomicSub, kI32Store) \
6012 V(Sub, I64AtomicSub, kI64Store) \
6013 V(Sub, I32AtomicSub8U, kI32Store8) \
6014 V(Sub, I32AtomicSub16U, kI32Store16) \
6015 V(Sub, I64AtomicSub8U, kI64Store8) \
6016 V(Sub, I64AtomicSub16U, kI64Store16) \
6017 V(Sub, I64AtomicSub32U, kI64Store32) \
6018 V(And, I32AtomicAnd, kI32Store) \
6019 V(And, I64AtomicAnd, kI64Store) \
6020 V(And, I32AtomicAnd8U, kI32Store8) \
6021 V(And, I32AtomicAnd16U, kI32Store16) \
6022 V(And, I64AtomicAnd8U, kI64Store8) \
6023 V(And, I64AtomicAnd16U, kI64Store16) \
6024 V(And, I64AtomicAnd32U, kI64Store32) \
6025 V(Or, I32AtomicOr, kI32Store) \
6026 V(Or, I64AtomicOr, kI64Store) \
6027 V(Or, I32AtomicOr8U, kI32Store8) \
6028 V(Or, I32AtomicOr16U, kI32Store16) \
6029 V(Or, I64AtomicOr8U, kI64Store8) \
6030 V(Or, I64AtomicOr16U, kI64Store16) \
6031 V(Or, I64AtomicOr32U, kI64Store32) \
6032 V(Xor, I32AtomicXor, kI32Store) \
6033 V(Xor, I64AtomicXor, kI64Store) \
6034 V(Xor, I32AtomicXor8U, kI32Store8) \
6035 V(Xor, I32AtomicXor16U, kI32Store16) \
6036 V(Xor, I64AtomicXor8U, kI64Store8) \
6037 V(Xor, I64AtomicXor16U, kI64Store16) \
6038 V(Xor, I64AtomicXor32U, kI64Store32) \
6039 V(Exchange, I32AtomicExchange, kI32Store) \
6040 V(Exchange, I64AtomicExchange, kI64Store) \
6041 V(Exchange, I32AtomicExchange8U, kI32Store8) \
6042 V(Exchange, I32AtomicExchange16U, kI32Store16) \
6043 V(Exchange, I64AtomicExchange8U, kI64Store8) \
6044 V(Exchange, I64AtomicExchange16U, kI64Store16) \
6045 V(Exchange, I64AtomicExchange32U, kI64Store32)
6046
6047#define ATOMIC_COMPARE_EXCHANGE_LIST(V) \
6048 V(I32AtomicCompareExchange, kI32Store) \
6049 V(I64AtomicCompareExchange, kI64Store) \
6050 V(I32AtomicCompareExchange8U, kI32Store8) \
6051 V(I32AtomicCompareExchange16U, kI32Store16) \
6052 V(I64AtomicCompareExchange8U, kI64Store8) \
6053 V(I64AtomicCompareExchange16U, kI64Store16) \
6054 V(I64AtomicCompareExchange32U, kI64Store32)
6055
6056 void AtomicOp(FullDecoder* decoder, WasmOpcode opcode, const Value args[],
6057 const size_t argc, const MemoryAccessImmediate& imm,
6058 Value* result) {
6059 switch (opcode) {
6060#define ATOMIC_STORE_OP(name, type) \
6061 case wasm::kExpr##name: \
6062 AtomicStoreMem(decoder, StoreType::type, imm); \
6063 break;
6064
6066#undef ATOMIC_STORE_OP
6067
6068#define ATOMIC_LOAD_OP(name, type) \
6069 case wasm::kExpr##name: \
6070 AtomicLoadMem(decoder, LoadType::type, imm); \
6071 break;
6072
6074#undef ATOMIC_LOAD_OP
6075
6076#define ATOMIC_BINOP_OP(op, name, type) \
6077 case wasm::kExpr##name: \
6078 AtomicBinop(decoder, StoreType::type, imm, &LiftoffAssembler::Atomic##op); \
6079 break;
6080
6082#undef ATOMIC_BINOP_OP
6083
6084#define ATOMIC_COMPARE_EXCHANGE_OP(name, type) \
6085 case wasm::kExpr##name: \
6086 AtomicCompareExchange(decoder, StoreType::type, imm); \
6087 break;
6088
6090#undef ATOMIC_COMPARE_EXCHANGE_OP
6091
6092 case kExprI32AtomicWait:
6093 AtomicWait(decoder, kI32, imm);
6094 break;
6095 case kExprI64AtomicWait:
6096 AtomicWait(decoder, kI64, imm);
6097 break;
6098 case kExprAtomicNotify:
6099 AtomicNotify(decoder, imm);
6100 break;
6101 default:
6102 UNREACHABLE();
6103 }
6104 }
6105
6106#undef ATOMIC_STORE_LIST
6107#undef ATOMIC_LOAD_LIST
6108#undef ATOMIC_BINOP_INSTRUCTION_LIST
6109#undef ATOMIC_COMPARE_EXCHANGE_LIST
6110
6111 void AtomicFence(FullDecoder* decoder) { __ AtomicFence(); }
6112
6113 // Pop a VarState and if needed transform it to an intptr.
6114 // When truncating from u64 to u32, the {*high_word} is updated to contain
6115 // the ORed combination of all high words.
6116 VarState PopIndexToVarState(Register* high_word, LiftoffRegList* pinned) {
6117 VarState slot = __ PopVarState();
6118 const bool is_64bit_value = slot.kind() == kI64;
6119 // For memory32 on a 32-bit system or memory64 on a 64-bit system, there is
6120 // nothing to do.
6121 if (Is64() == is_64bit_value) {
6122 if (slot.is_reg()) pinned->set(slot.reg());
6123 return slot;
6124 }
6125
6126 // {kI64} constants will be stored as 32-bit integers in the {VarState} and
6127 // will be sign-extended later. Hence we can return constants if they are
6128 // positive (such that sign-extension and zero-extension are identical).
6129 if (slot.is_const() && (kIntPtrKind == kI32 || slot.i32_const() >= 0)) {
6130 return {kIntPtrKind, slot.i32_const(), 0};
6131 }
6132
6133 // For memory32 on 64-bit hosts, zero-extend.
6134 if constexpr (Is64()) {
6135 DCHECK(!is_64bit_value); // Handled above.
6136 LiftoffRegister reg = __ LoadToModifiableRegister(slot, *pinned);
6137 __ emit_u32_to_uintptr(reg.gp(), reg.gp());
6138 pinned->set(reg);
6139 return {kIntPtrKind, reg, 0};
6140 }
6141
6142 // For memory64 on 32-bit systems, combine all high words for a zero-check
6143 // and only use the low words afterwards. This keeps the register pressure
6144 // manageable.
6145 DCHECK(is_64bit_value && !Is64()); // Other cases are handled above.
6146 LiftoffRegister reg = __ LoadToRegister(slot, *pinned);
6147 pinned->set(reg.low());
6148 if (*high_word == no_reg) {
6149 // Choose a register to hold the (combination of) high word(s). It cannot
6150 // be one of the pinned registers, and it cannot be used in the value
6151 // stack.
6152 *high_word =
6153 !pinned->has(reg.high()) && __ cache_state()->is_free(reg.high())
6154 ? reg.high().gp()
6155 : __ GetUnusedRegister(kGpReg, *pinned).gp();
6156 pinned->set(*high_word);
6157 if (*high_word != reg.high_gp()) {
6158 __ Move(*high_word, reg.high_gp(), kI32);
6159 }
6160 } else if (*high_word != reg.high_gp()) {
6161 // Combine the new high word into existing high words.
6162 __ emit_i32_or(*high_word, *high_word, reg.high_gp());
6163 }
6164 return {kIntPtrKind, reg.low(), 0};
6165 }
6166
6167 // This is a helper function that traps with TableOOB if any bit is set in
6168 // `high_word`. It is meant to be used after `PopIndexToVarState()` to check
6169 // if the conversion was valid.
6170 // Note that this is suboptimal as we add an OOL code for this special
6171 // condition, and there's also another conditional trap in the caller builtin.
6172 // However, it only applies for the rare case of 32-bit platforms with
6173 // table64.
6174 void CheckHighWordEmptyForTableType(FullDecoder* decoder,
6175 const Register high_word,
6176 LiftoffRegList* pinned) {
6177 if constexpr (Is64()) {
6178 DCHECK_EQ(no_reg, high_word);
6179 return;
6180 }
6181 if (high_word == no_reg) return;
6182
6183 OolTrapLabel trap =
6184 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapTableOutOfBounds);
6185 __ emit_cond_jump(kNotZero, trap.label(), kI32, high_word, no_reg,
6186 trap.frozen());
6187 // Clearing `high_word` is safe because this never aliases with another
6188 // in-use register, see `PopIndexToVarState()`.
6189 pinned->clear(high_word);
6190 }
6191
6192 // Same as {PopIndexToVarState}, but can take a VarState in the middle of the
6193 // stack without popping it.
6194 // For 64-bit values on 32-bit systems, the resulting VarState will contain a
6195 // single register whose value will be kMaxUint32 if the high word had any
6196 // bits set.
6197 VarState IndexToVarStateSaturating(int stack_index, LiftoffRegList* pinned) {
6198 DCHECK_LE(0, stack_index);
6199 DCHECK_LT(stack_index, __ cache_state()->stack_height());
6200 VarState& slot = __ cache_state()->stack_state.end()[-1 - stack_index];
6201 const bool is_mem64 = slot.kind() == kI64;
6202 // For memory32 on a 32-bit system or memory64 on a 64-bit system, there is
6203 // nothing to do.
6204 if ((kSystemPointerSize == kInt64Size) == is_mem64) {
6205 if (slot.is_reg()) pinned->set(slot.reg());
6206 return slot;
6207 }
6208
6209 // {kI64} constants will be stored as 32-bit integers in the {VarState} and
6210 // will be sign-extended later. Hence we can return constants if they are
6211 // positive (such that sign-extension and zero-extension are identical).
6212 if (slot.is_const() && (kIntPtrKind == kI32 || slot.i32_const() >= 0)) {
6213 return {kIntPtrKind, slot.i32_const(), 0};
6214 }
6215
6216 LiftoffRegister reg = __ LoadToModifiableRegister(slot, *pinned);
6217 // For memory32 on 64-bit hosts, zero-extend.
6218 if constexpr (Is64()) {
6219 DCHECK(!is_mem64); // Handled above.
6220 __ emit_u32_to_uintptr(reg.gp(), reg.gp());
6221 pinned->set(reg);
6222 return {kIntPtrKind, reg, 0};
6223 }
6224
6225 // For memory64 on 32-bit systems, saturate the low word.
6226 DCHECK(is_mem64); // Other cases are handled above.
6228 pinned->set(reg.low());
6229 Label ok;
6230 FREEZE_STATE(frozen);
6231 __ emit_cond_jump(kZero, &ok, kI32, reg.high().gp(), no_reg, frozen);
6232 __ LoadConstant(reg.low(), WasmValue{kMaxUInt32});
6233 __ emit_jump(&ok);
6234 __ bind(&ok);
6235 return {kIntPtrKind, reg.low(), 0};
6236 }
6237
6238 // Same as {PopIndexToVarState}, but saturates 64-bit values on 32-bit
6239 // platforms like {IndexToVarStateSaturating}.
6240 VarState PopIndexToVarStateSaturating(LiftoffRegList* pinned) {
6241 VarState result = IndexToVarStateSaturating(0, pinned);
6242 __ DropValues(1);
6243 return result;
6244 }
6245
6246 // The following functions are to be used inside a DCHECK. They always return
6247 // true and will fail internally on a detected inconsistency.
6248#ifdef DEBUG
6249 // Checks that the top-of-stack value matches the declared memory (64-bit or
6250 // 32-bit).
6251 bool MatchingMemTypeOnTopOfStack(const WasmMemory* memory) {
6252 return MatchingAddressTypeOnTopOfStack(memory->is_memory64());
6253 }
6254
6255 // Checks that the top-of-stack value matches the expected bitness.
6256 bool MatchingAddressTypeOnTopOfStack(bool expect_64bit_value) {
6257 DCHECK_LT(0, __ cache_state()->stack_height());
6258 ValueKind expected_kind = expect_64bit_value ? kI64 : kI32;
6259 DCHECK_EQ(expected_kind, __ cache_state()->stack_state.back().kind());
6260 return true;
6261 }
6262
6263 bool MatchingMemType(const WasmMemory* memory, int stack_index) {
6264 DCHECK_LE(0, stack_index);
6265 DCHECK_LT(stack_index, __ cache_state()->stack_state.size());
6266 ValueKind expected_kind = memory->is_memory64() ? kI64 : kI32;
6267 DCHECK_EQ(expected_kind,
6268 __ cache_state()->stack_state.end()[-1 - stack_index].kind());
6269 return true;
6270 }
6271#endif
6272
6273 void MemoryInit(FullDecoder* decoder, const MemoryInitImmediate& imm,
6274 const Value&, const Value&, const Value&) {
6276 Register mem_offsets_high_word = no_reg;
6277 LiftoffRegList pinned;
6278 VarState size = __ PopVarState();
6279 if (size.is_reg()) pinned.set(size.reg());
6280 VarState src = __ PopVarState();
6281 if (src.is_reg()) pinned.set(src.reg());
6282 DCHECK(MatchingMemTypeOnTopOfStack(imm.memory.memory));
6283 VarState dst = PopIndexToVarState(&mem_offsets_high_word, &pinned);
6284
6285 Register instance_data = __ cache_state() -> cached_instance_data;
6286 if (instance_data == no_reg) {
6287 instance_data = __ GetUnusedRegister(kGpReg, pinned).gp();
6288 __ LoadInstanceDataFromFrame(instance_data);
6289 }
6290 pinned.set(instance_data);
6291
6292 // TODO(crbug.com/41480344): The stack state in the OOL code should reflect
6293 // the state before popping any values (for a better debugging experience).
6294 if (mem_offsets_high_word != no_reg) {
6295 // If any high word has bits set, jump to the OOB trap.
6296 OolTrapLabel trap =
6297 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
6298 __ emit_cond_jump(kNotZero, trap.label(), kI32, mem_offsets_high_word,
6299 no_reg, trap.frozen());
6300 pinned.clear(mem_offsets_high_word);
6301 }
6302
6303 LiftoffRegister result =
6304 GenerateCCall(kI32,
6305 {{kIntPtrKind, LiftoffRegister{instance_data}, 0},
6306 {kI32, static_cast<int32_t>(imm.memory.index), 0},
6307 dst,
6308 src,
6309 {kI32, static_cast<int32_t>(imm.data_segment.index), 0},
6310 size},
6311 ExternalReference::wasm_memory_init());
6312 OolTrapLabel trap =
6313 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
6314 __ emit_cond_jump(kEqual, trap.label(), kI32, result.gp(), no_reg,
6315 trap.frozen());
6316 }
6317
6318 void DataDrop(FullDecoder* decoder, const IndexImmediate& imm) {
6319 LiftoffRegList pinned;
6320
6321 Register seg_size_array =
6322 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
6323 LOAD_TAGGED_PTR_INSTANCE_FIELD(seg_size_array, DataSegmentSizes, pinned);
6324
6325 LiftoffRegister seg_index =
6326 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6327 // Scale the seg_index for the array access.
6328 __ LoadConstant(
6329 seg_index,
6331 imm.index)));
6332
6333 // Set the length of the segment to '0' to drop it.
6334 LiftoffRegister null_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6335 __ LoadConstant(null_reg, WasmValue(0));
6336 __ Store(seg_size_array, seg_index.gp(), 0, null_reg, StoreType::kI32Store,
6337 pinned);
6338 }
6339
6340 void MemoryCopy(FullDecoder* decoder, const MemoryCopyImmediate& imm,
6341 const Value&, const Value&, const Value&) {
6343 Register mem_offsets_high_word = no_reg;
6344 LiftoffRegList pinned;
6345
6346 // The type of {size} is the min of {src} and {dst} (where {kI32 < kI64}).
6347 DCHECK(
6348 MatchingAddressTypeOnTopOfStack(imm.memory_dst.memory->is_memory64() &&
6349 imm.memory_src.memory->is_memory64()));
6350 VarState size = PopIndexToVarState(&mem_offsets_high_word, &pinned);
6351 DCHECK(MatchingMemTypeOnTopOfStack(imm.memory_src.memory));
6352 VarState src = PopIndexToVarState(&mem_offsets_high_word, &pinned);
6353 DCHECK(MatchingMemTypeOnTopOfStack(imm.memory_dst.memory));
6354 VarState dst = PopIndexToVarState(&mem_offsets_high_word, &pinned);
6355
6356 Register instance_data = __ cache_state() -> cached_instance_data;
6357 if (instance_data == no_reg) {
6358 instance_data = __ GetUnusedRegister(kGpReg, pinned).gp();
6359 __ LoadInstanceDataFromFrame(instance_data);
6360 }
6361 pinned.set(instance_data);
6362
6363 // TODO(crbug.com/41480344): The stack state in the OOL code should reflect
6364 // the state before popping any values (for a better debugging experience).
6365 DCHECK_IMPLIES(Is64(), mem_offsets_high_word == no_reg);
6366 if (!Is64() && mem_offsets_high_word != no_reg) {
6367 // If any high word has bits set, jump to the OOB trap.
6368 OolTrapLabel trap =
6369 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
6370 __ emit_cond_jump(kNotZero, trap.label(), kI32, mem_offsets_high_word,
6371 no_reg, trap.frozen());
6372 }
6373
6374 LiftoffRegister result =
6375 GenerateCCall(kI32,
6376 {{kIntPtrKind, LiftoffRegister{instance_data}, 0},
6377 {kI32, static_cast<int32_t>(imm.memory_dst.index), 0},
6378 {kI32, static_cast<int32_t>(imm.memory_src.index), 0},
6379 dst,
6380 src,
6381 size},
6382 ExternalReference::wasm_memory_copy());
6383 OolTrapLabel trap =
6384 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
6385 __ emit_cond_jump(kEqual, trap.label(), kI32, result.gp(), no_reg,
6386 trap.frozen());
6387 }
6388
6389 void MemoryFill(FullDecoder* decoder, const MemoryIndexImmediate& imm,
6390 const Value&, const Value&, const Value&) {
6392 Register mem_offsets_high_word = no_reg;
6393 LiftoffRegList pinned;
6394 DCHECK(MatchingMemTypeOnTopOfStack(imm.memory));
6395 VarState size = PopIndexToVarState(&mem_offsets_high_word, &pinned);
6396 VarState value = __ PopVarState();
6397 if (value.is_reg()) pinned.set(value.reg());
6398 DCHECK(MatchingMemTypeOnTopOfStack(imm.memory));
6399 VarState dst = PopIndexToVarState(&mem_offsets_high_word, &pinned);
6400
6401 Register instance_data = __ cache_state() -> cached_instance_data;
6402 if (instance_data == no_reg) {
6403 instance_data = __ GetUnusedRegister(kGpReg, pinned).gp();
6404 __ LoadInstanceDataFromFrame(instance_data);
6405 }
6406 pinned.set(instance_data);
6407
6408 // TODO(crbug.com/41480344): The stack state in the OOL code should reflect
6409 // the state before popping any values (for a better debugging experience).
6410 if (mem_offsets_high_word != no_reg) {
6411 // If any high word has bits set, jump to the OOB trap.
6412 OolTrapLabel trap =
6413 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
6414 __ emit_cond_jump(kNotZero, trap.label(), kI32, mem_offsets_high_word,
6415 no_reg, trap.frozen());
6416 }
6417
6418 LiftoffRegister result =
6419 GenerateCCall(kI32,
6420 {{kIntPtrKind, LiftoffRegister{instance_data}, 0},
6421 {kI32, static_cast<int32_t>(imm.index), 0},
6422 dst,
6423 value,
6424 size},
6425 ExternalReference::wasm_memory_fill());
6426 OolTrapLabel trap =
6427 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapMemOutOfBounds);
6428 __ emit_cond_jump(kEqual, trap.label(), kI32, result.gp(), no_reg,
6429 trap.frozen());
6430 }
6431
6432 void LoadSmi(LiftoffRegister reg, int value) {
6433 Address smi_value = Smi::FromInt(value).ptr();
6434 using smi_type = std::conditional_t<kSmiKind == kI32, int32_t, int64_t>;
6435 __ LoadConstant(reg, WasmValue{static_cast<smi_type>(smi_value)});
6436 }
6437
6438 VarState LoadSmiConstant(int32_t constant, LiftoffRegList* pinned) {
6439 if constexpr (kSmiKind == kI32) {
6440 int32_t smi_const = static_cast<int32_t>(Smi::FromInt(constant).ptr());
6441 return VarState{kI32, smi_const, 0};
6442 } else {
6443 LiftoffRegister reg = pinned->set(__ GetUnusedRegister(kGpReg, *pinned));
6444 LoadSmi(reg, constant);
6445 return VarState{kSmiKind, reg, 0};
6446 }
6447 }
6448
6449 void TableInit(FullDecoder* decoder, const TableInitImmediate& imm,
6450 const Value&, const Value&, const Value&) {
6452 LiftoffRegList pinned;
6453
6454 VarState table_index = LoadSmiConstant(imm.table.index, &pinned);
6455 VarState segment_index =
6456 LoadSmiConstant(imm.element_segment.index, &pinned);
6457 VarState extract_shared_data = LoadSmiConstant(0, &pinned);
6458
6459 VarState size = __ PopVarState();
6460 if (size.is_reg()) pinned.set(size.reg());
6461 VarState src = __ PopVarState();
6462 if (src.is_reg()) pinned.set(src.reg());
6463 Register index_high_word = no_reg;
6464 VarState dst = PopIndexToVarState(&index_high_word, &pinned);
6465
6466 // Trap if any bit in high word was set.
6467 CheckHighWordEmptyForTableType(decoder, index_high_word, &pinned);
6468
6469 CallBuiltin(
6470 Builtin::kWasmTableInit,
6471 MakeSig::Params(kIntPtrKind, kI32, kI32, kSmiKind, kSmiKind, kSmiKind),
6472 {dst, src, size, table_index, segment_index, extract_shared_data},
6473 decoder->position());
6474
6475 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
6476 }
6477
6478 void ElemDrop(FullDecoder* decoder, const IndexImmediate& imm) {
6479 LiftoffRegList pinned;
6481 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
6482 LOAD_TAGGED_PTR_INSTANCE_FIELD(element_segments, ElementSegments, pinned);
6483
6484 LiftoffRegister seg_index =
6485 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6486 __ LoadConstant(
6487 seg_index,
6488 WasmValue(
6490
6491 // Mark the segment as dropped by setting it to the empty fixed array.
6492 Register empty_fixed_array =
6493 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
6494 __ LoadFullPointer(
6495 empty_fixed_array, kRootRegister,
6496 IsolateData::root_slot_offset(RootIndex::kEmptyFixedArray));
6497
6498 __ StoreTaggedPointer(element_segments, seg_index.gp(), 0,
6499 empty_fixed_array, pinned);
6500 }
6501
6502 void TableCopy(FullDecoder* decoder, const TableCopyImmediate& imm,
6503 const Value&, const Value&, const Value&) {
6505 Register index_high_word = no_reg;
6506 LiftoffRegList pinned;
6507
6508 VarState table_src_index = LoadSmiConstant(imm.table_src.index, &pinned);
6509 VarState table_dst_index = LoadSmiConstant(imm.table_dst.index, &pinned);
6510 VarState extract_shared_data = LoadSmiConstant(0, &pinned);
6511
6512 VarState size = PopIndexToVarState(&index_high_word, &pinned);
6513 VarState src = PopIndexToVarState(&index_high_word, &pinned);
6514 VarState dst = PopIndexToVarState(&index_high_word, &pinned);
6515
6516 // Trap if any bit in the combined high words was set.
6517 CheckHighWordEmptyForTableType(decoder, index_high_word, &pinned);
6518
6519 CallBuiltin(
6520 Builtin::kWasmTableCopy,
6521 MakeSig::Params(kIntPtrKind, kIntPtrKind, kIntPtrKind, kSmiKind,
6522 kSmiKind, kSmiKind),
6523 {dst, src, size, table_dst_index, table_src_index, extract_shared_data},
6524 decoder->position());
6525
6526 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
6527 }
6528
6529 void TableGrow(FullDecoder* decoder, const TableIndexImmediate& imm,
6530 const Value&, const Value&, Value* result) {
6532 LiftoffRegList pinned;
6533
6534 LiftoffRegister table_index_reg =
6535 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6536 LoadSmi(table_index_reg, imm.index);
6537 VarState table_index(kSmiKind, table_index_reg, 0);
6538 // If `delta` is, OOB table.grow should return -1.
6539 VarState delta = PopIndexToVarStateSaturating(&pinned);
6540 VarState value = __ PopVarState();
6541 VarState extract_shared_data(kI32, 0, 0);
6542
6543 CallBuiltin(Builtin::kWasmTableGrow,
6544 MakeSig::Returns(kSmiKind).Params(kSmiKind, kIntPtrKind, kI32,
6545 kRefNull),
6546 {table_index, delta, extract_shared_data, value},
6547 decoder->position());
6548
6549 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
6550 __ SmiToInt32(kReturnRegister0);
6551 if (imm.table->is_table64()) {
6552 LiftoffRegister result64 = LiftoffRegister(kReturnRegister0);
6553 if (kNeedI64RegPair) {
6555 }
6556 __ emit_type_conversion(kExprI64SConvertI32, result64,
6557 LiftoffRegister(kReturnRegister0), nullptr);
6558 __ PushRegister(kI64, result64);
6559 } else {
6560 __ PushRegister(kI32, LiftoffRegister(kReturnRegister0));
6561 }
6562 }
6563
6564 void TableSize(FullDecoder* decoder, const TableIndexImmediate& imm, Value*) {
6565 // We have to look up instance->tables[table_index].length.
6566
6567 LiftoffRegList pinned;
6568 // Get the number of calls array address.
6569 Register tables = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
6570 LOAD_TAGGED_PTR_INSTANCE_FIELD(tables, Tables, pinned);
6571
6572 Register table = tables;
6573 __ LoadTaggedPointer(
6574 table, tables, no_reg,
6576
6577 int length_field_size = WasmTableObject::kCurrentLengthOffsetEnd -
6578 WasmTableObject::kCurrentLengthOffset + 1;
6579
6580 Register result = table;
6581 __ Load(LiftoffRegister(result), table, no_reg,
6582 wasm::ObjectAccess::ToTagged(WasmTableObject::kCurrentLengthOffset),
6583 length_field_size == 4 ? LoadType::kI32Load : LoadType::kI64Load);
6584
6586
6587 if (imm.table->is_table64()) {
6588 LiftoffRegister result64 = LiftoffRegister(result);
6589 if (kNeedI64RegPair) {
6590 result64 = LiftoffRegister::ForPair(
6591 result, __ GetUnusedRegister(kGpReg, pinned).gp());
6592 }
6593 __ emit_type_conversion(kExprI64SConvertI32, result64,
6594 LiftoffRegister(result), nullptr);
6595 __ PushRegister(kI64, result64);
6596 } else {
6597 __ PushRegister(kI32, LiftoffRegister(result));
6598 }
6599 }
6600
6601 void TableFill(FullDecoder* decoder, const TableIndexImmediate& imm,
6602 const Value&, const Value&, const Value&) {
6604 Register high_words = no_reg;
6605 LiftoffRegList pinned;
6606
6607 VarState table_index = LoadSmiConstant(imm.index, &pinned);
6608 VarState extract_shared_data{kI32, 0, 0};
6609
6610 VarState count = PopIndexToVarState(&high_words, &pinned);
6611 VarState value = __ PopVarState();
6612 if (value.is_reg()) pinned.set(value.reg());
6613 VarState start = PopIndexToVarState(&high_words, &pinned);
6614 // Trap if any bit in the combined high words was set.
6615 CheckHighWordEmptyForTableType(decoder, high_words, &pinned);
6616
6617 CallBuiltin(
6618 Builtin::kWasmTableFill,
6619 MakeSig::Params(kIntPtrKind, kIntPtrKind, kI32, kSmiKind, kRefNull),
6620 {start, count, extract_shared_data, table_index, value},
6621 decoder->position());
6622
6623 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
6624 }
6625
6626 LiftoffRegister GetRtt(FullDecoder* decoder, ModuleTypeIndex index,
6627 const TypeDefinition& type,
6628 const Value& descriptor_value) {
6629 if (!type.has_descriptor()) return RttCanon(index, {});
6630 return GetRttFromDescriptorOnStack(decoder, descriptor_value);
6631 }
6632
6633 LiftoffRegister GetRttFromDescriptorOnStack(FullDecoder* decoder,
6634 const Value& descriptor_value) {
6635 LiftoffRegList pinned;
6636 LiftoffRegister descriptor = pinned.set(__ PopToRegister({}));
6637 auto [explicit_check, implicit_check] =
6638 null_checks_for_struct_op(descriptor_value.type, 0);
6639 if (explicit_check) {
6640 MaybeEmitNullCheck(decoder, descriptor.gp(), pinned,
6641 descriptor_value.type);
6642 }
6643 LiftoffRegister rtt = __ GetUnusedRegister(kGpReg, pinned);
6644 LoadObjectField(decoder, rtt, descriptor.gp(), no_reg,
6645 ObjectAccess::ToTagged(WasmStruct::kHeaderSize), kRef,
6646 false, implicit_check, pinned);
6647 return rtt;
6648 }
6649
6650 void StructNew(FullDecoder* decoder, const StructIndexImmediate& imm,
6651 const Value& descriptor, bool initial_values_on_stack) {
6652 const TypeDefinition& type = decoder->module_->type(imm.index);
6653 LiftoffRegister rtt = GetRtt(decoder, imm.index, type, descriptor);
6654
6655 if (type.is_descriptor()) {
6656 CallBuiltin(Builtin::kWasmAllocateDescriptorStruct,
6657 MakeSig::Returns(kRef).Params(kRef, kI32),
6658 {VarState{kRef, rtt, 0},
6659 VarState{kI32, static_cast<int32_t>(imm.index.index), 0}},
6660 decoder->position());
6661 } else {
6662 CallBuiltin(Builtin::kWasmAllocateStructWithRtt,
6663 MakeSig::Returns(kRef).Params(kRef, kI32),
6664 {VarState{kRef, rtt, 0},
6665 VarState{kI32, WasmStruct::Size(imm.struct_type), 0}},
6666 decoder->position());
6667 }
6668
6669 LiftoffRegister obj(kReturnRegister0);
6670 LiftoffRegList pinned{obj};
6671
6672 for (uint32_t i = imm.struct_type->field_count(); i > 0;) {
6673 i--;
6674 int offset = StructFieldOffset(imm.struct_type, i);
6675 ValueType field_type = imm.struct_type->field(i);
6676 LiftoffRegister value = pinned.set(
6677 initial_values_on_stack
6678 ? __ PopToRegister(pinned)
6679 : __ GetUnusedRegister(reg_class_for(field_type.kind()), pinned));
6680 if (!initial_values_on_stack) {
6681 if (!CheckSupportedType(decoder, field_type.kind(), "default value")) {
6682 return;
6683 }
6684 SetDefaultValue(value, field_type);
6685 }
6686 // Skipping the write barrier is safe as long as:
6687 // (1) {obj} is freshly allocated, and
6688 // (2) {obj} is in new-space (not pretenured).
6689 StoreObjectField(decoder, obj.gp(), no_reg, offset, value, false, pinned,
6690 field_type.kind(), LiftoffAssembler::kSkipWriteBarrier);
6691 pinned.clear(value);
6692 }
6693 // If this assert fails then initialization of padding field might be
6694 // necessary.
6695 static_assert(Heap::kMinObjectSizeInTaggedWords == 2 &&
6696 WasmStruct::kHeaderSize == 2 * kTaggedSize,
6697 "empty struct might require initialization of padding field");
6698 __ PushRegister(kRef, obj);
6699 }
6700
6701 void StructNew(FullDecoder* decoder, const StructIndexImmediate& imm,
6702 const Value& descriptor, const Value args[], Value* result) {
6703 StructNew(decoder, imm, descriptor, true);
6704 }
6705
6706 void StructNewDefault(FullDecoder* decoder, const StructIndexImmediate& imm,
6707 const Value& descriptor, Value* result) {
6708 StructNew(decoder, imm, descriptor, false);
6709 }
6710
6711 void StructGet(FullDecoder* decoder, const Value& struct_obj,
6712 const FieldImmediate& field, bool is_signed, Value* result) {
6713 const StructType* struct_type = field.struct_imm.struct_type;
6714 ValueKind field_kind = struct_type->field(field.field_imm.index).kind();
6715 if (!CheckSupportedType(decoder, field_kind, "field load")) return;
6716 int offset = StructFieldOffset(struct_type, field.field_imm.index);
6717 LiftoffRegList pinned;
6718 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
6719
6720 auto [explicit_check, implicit_check] =
6721 null_checks_for_struct_op(struct_obj.type, field.field_imm.index);
6722
6723 if (explicit_check) {
6724 MaybeEmitNullCheck(decoder, obj.gp(), pinned, struct_obj.type);
6725 }
6726 LiftoffRegister value =
6727 __ GetUnusedRegister(reg_class_for(field_kind), pinned);
6728 LoadObjectField(decoder, value, obj.gp(), no_reg, offset, field_kind,
6729 is_signed, implicit_check, pinned);
6730 __ PushRegister(unpacked(field_kind), value);
6731 }
6732
6733 void StructSet(FullDecoder* decoder, const Value& struct_obj,
6734 const FieldImmediate& field, const Value& field_value) {
6735 const StructType* struct_type = field.struct_imm.struct_type;
6736 ValueKind field_kind = struct_type->field(field.field_imm.index).kind();
6737 int offset = StructFieldOffset(struct_type, field.field_imm.index);
6738 LiftoffRegList pinned;
6739 LiftoffRegister value = pinned.set(__ PopToRegister(pinned));
6740 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
6741
6742 auto [explicit_check, implicit_check] =
6743 null_checks_for_struct_op(struct_obj.type, field.field_imm.index);
6744
6745 if (explicit_check) {
6746 MaybeEmitNullCheck(decoder, obj.gp(), pinned, struct_obj.type);
6747 }
6748
6749 StoreObjectField(decoder, obj.gp(), no_reg, offset, value, implicit_check,
6750 pinned, field_kind);
6751 }
6752
6753 void ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate& imm,
6754 bool initial_value_on_stack) {
6756 // Max length check.
6757 {
6758 LiftoffRegister length =
6759 __ LoadToRegister(__ cache_state()->stack_state.end()[-1], {});
6760 OolTrapLabel trap =
6761 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapArrayTooLarge);
6762 __ emit_i32_cond_jumpi(kUnsignedGreaterThan, trap.label(), length.gp(),
6763 WasmArray::MaxLength(imm.array_type),
6764 trap.frozen());
6765 }
6766 ValueType elem_type = imm.array_type->element_type();
6767 ValueKind elem_kind = elem_type.kind();
6768 int elem_size = value_kind_size(elem_kind);
6769 // Allocate the array.
6770 {
6771 LiftoffRegister rtt = RttCanon(imm.index, {});
6772 CallBuiltin(Builtin::kWasmAllocateArray_Uninitialized,
6773 MakeSig::Returns(kRef).Params(kRef, kI32, kI32),
6774 {VarState{kRef, rtt, 0},
6775 __ cache_state()->stack_state.end()[-1], // length
6776 VarState{kI32, elem_size, 0}},
6777 decoder->position());
6778 }
6779
6780 LiftoffRegister obj(kReturnRegister0);
6781 LiftoffRegList pinned{obj};
6782 LiftoffRegister length = pinned.set(__ PopToModifiableRegister(pinned));
6783 LiftoffRegister value =
6784 pinned.set(__ GetUnusedRegister(reg_class_for(elem_kind), pinned));
6785 if (initial_value_on_stack) {
6786 __ PopToFixedRegister(value);
6787 } else {
6788 if (!CheckSupportedType(decoder, elem_kind, "default value")) return;
6789 SetDefaultValue(value, elem_type);
6790 }
6791
6792 LiftoffRegister index = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6793 __ LoadConstant(index, WasmValue(int32_t{0}));
6794
6795 // Initialize the array's elements.
6796 // Skipping the write barrier is safe as long as:
6797 // (1) {obj} is freshly allocated, and
6798 // (2) {obj} is in new-space (not pretenured).
6799 ArrayFillImpl(decoder, pinned, obj, index, value, length, elem_kind,
6801
6802 __ PushRegister(kRef, obj);
6803 }
6804
6805 void ArrayNew(FullDecoder* decoder, const ArrayIndexImmediate& imm,
6806 const Value& length_value, const Value& initial_value,
6807 Value* result) {
6808 ArrayNew(decoder, imm, true);
6809 }
6810
6811 void ArrayNewDefault(FullDecoder* decoder, const ArrayIndexImmediate& imm,
6812 const Value& length, Value* result) {
6813 ArrayNew(decoder, imm, false);
6814 }
6815
6816 void ArrayFill(FullDecoder* decoder, ArrayIndexImmediate& imm,
6817 const Value& array, const Value& /* index */,
6818 const Value& /* value */, const Value& /* length */) {
6820 {
6821 // Null check.
6822 LiftoffRegList pinned;
6823 LiftoffRegister array_reg = pinned.set(__ PeekToRegister(3, pinned));
6824 if (null_check_strategy_ == compiler::NullCheckStrategy::kExplicit) {
6825 MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type);
6826 }
6827
6828 // Bounds checks.
6829 LiftoffRegister array_length =
6830 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6831 bool implicit_null_check =
6832 array.type.is_nullable() &&
6834 LoadObjectField(decoder, array_length, array_reg.gp(), no_reg,
6835 ObjectAccess::ToTagged(WasmArray::kLengthOffset), kI32,
6836 false, implicit_null_check, pinned);
6837 LiftoffRegister index = pinned.set(__ PeekToRegister(2, pinned));
6838 LiftoffRegister length = pinned.set(__ PeekToRegister(0, pinned));
6839 LiftoffRegister index_plus_length =
6840 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6841 DCHECK(index_plus_length != array_length);
6842 __ emit_i32_add(index_plus_length.gp(), length.gp(), index.gp());
6843 OolTrapLabel trap =
6844 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapArrayOutOfBounds);
6845 __ emit_cond_jump(kUnsignedGreaterThan, trap.label(), kI32,
6846 index_plus_length.gp(), array_length.gp(),
6847 trap.frozen());
6848 // Guard against overflow.
6849 __ emit_cond_jump(kUnsignedGreaterThan, trap.label(), kI32, index.gp(),
6850 index_plus_length.gp(), trap.frozen());
6851 }
6852
6853 LiftoffRegList pinned;
6854 LiftoffRegister length = pinned.set(__ PopToModifiableRegister(pinned));
6855 LiftoffRegister value = pinned.set(__ PopToRegister(pinned));
6856 LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned));
6857 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
6858
6859 ArrayFillImpl(decoder, pinned, obj, index, value, length,
6860 imm.array_type->element_type().kind(),
6862 }
6863
6864 void ArrayGet(FullDecoder* decoder, const Value& array_obj,
6865 const ArrayIndexImmediate& imm, const Value& index_val,
6866 bool is_signed, Value* result) {
6867 LiftoffRegList pinned;
6868 LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned));
6869 LiftoffRegister array = pinned.set(__ PopToRegister(pinned));
6870 if (null_check_strategy_ == compiler::NullCheckStrategy::kExplicit) {
6871 MaybeEmitNullCheck(decoder, array.gp(), pinned, array_obj.type);
6872 }
6873 bool implicit_null_check =
6874 array_obj.type.is_nullable() &&
6876 BoundsCheckArray(decoder, implicit_null_check, array, index, pinned);
6877 ValueKind elem_kind = imm.array_type->element_type().kind();
6878 if (!CheckSupportedType(decoder, elem_kind, "array load")) return;
6879 int elem_size_shift = value_kind_size_log2(elem_kind);
6880 if (elem_size_shift != 0) {
6881 __ emit_i32_shli(index.gp(), index.gp(), elem_size_shift);
6882 }
6883 LiftoffRegister value =
6884 __ GetUnusedRegister(reg_class_for(elem_kind), pinned);
6885 LoadObjectField(decoder, value, array.gp(), index.gp(),
6886 wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize),
6887 elem_kind, is_signed, false, pinned);
6888 __ PushRegister(unpacked(elem_kind), value);
6889 }
6890
6891 void ArraySet(FullDecoder* decoder, const Value& array_obj,
6892 const ArrayIndexImmediate& imm, const Value& index_val,
6893 const Value& value_val) {
6894 LiftoffRegList pinned;
6895 LiftoffRegister value = pinned.set(__ PopToRegister(pinned));
6896 DCHECK_EQ(reg_class_for(imm.array_type->element_type().kind()),
6897 value.reg_class());
6898 LiftoffRegister index = pinned.set(__ PopToModifiableRegister(pinned));
6899 LiftoffRegister array = pinned.set(__ PopToRegister(pinned));
6900 if (null_check_strategy_ == compiler::NullCheckStrategy::kExplicit) {
6901 MaybeEmitNullCheck(decoder, array.gp(), pinned, array_obj.type);
6902 }
6903 bool implicit_null_check =
6904 array_obj.type.is_nullable() &&
6906 BoundsCheckArray(decoder, implicit_null_check, array, index, pinned);
6907 ValueKind elem_kind = imm.array_type->element_type().kind();
6908 int elem_size_shift = value_kind_size_log2(elem_kind);
6909 if (elem_size_shift != 0) {
6910 __ emit_i32_shli(index.gp(), index.gp(), elem_size_shift);
6911 }
6912 StoreObjectField(decoder, array.gp(), index.gp(),
6913 wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize),
6914 value, false, pinned, elem_kind);
6915 }
6916
6917 void ArrayLen(FullDecoder* decoder, const Value& array_obj, Value* result) {
6918 LiftoffRegList pinned;
6919 LiftoffRegister obj = pinned.set(__ PopToRegister(pinned));
6920 if (null_check_strategy_ == compiler::NullCheckStrategy::kExplicit) {
6921 MaybeEmitNullCheck(decoder, obj.gp(), pinned, array_obj.type);
6922 }
6923 LiftoffRegister len = __ GetUnusedRegister(kGpReg, pinned);
6924 int kLengthOffset = wasm::ObjectAccess::ToTagged(WasmArray::kLengthOffset);
6925 bool implicit_null_check =
6926 array_obj.type.is_nullable() &&
6928 LoadObjectField(decoder, len, obj.gp(), no_reg, kLengthOffset, kI32,
6929 false /* is_signed */, implicit_null_check, pinned);
6930 __ PushRegister(kI32, len);
6931 }
6932
6933 void ArrayCopy(FullDecoder* decoder, const Value& dst, const Value& dst_index,
6934 const Value& src, const Value& src_index,
6935 const ArrayIndexImmediate& src_imm, const Value& length) {
6936 // TODO(14034): Unify implementation with TF: Implement this with
6937 // GenerateCCallWithStackBuffer. Remove runtime function and builtin in
6938 // wasm.tq.
6939 CallBuiltin(Builtin::kWasmArrayCopy,
6940 MakeSig::Params(kI32, kI32, kI32, kRefNull, kRefNull),
6941 // Builtin parameter order:
6942 // [dst_index, src_index, length, dst, src].
6943 {__ cache_state()->stack_state.end()[-4],
6944 __ cache_state()->stack_state.end()[-2],
6945 __ cache_state()->stack_state.end()[-1],
6946 __ cache_state()->stack_state.end()[-5],
6947 __ cache_state()->stack_state.end()[-3]},
6948 decoder->position());
6949 __ DropValues(5);
6950 }
6951
6952 void ArrayNewFixed(FullDecoder* decoder, const ArrayIndexImmediate& array_imm,
6953 const IndexImmediate& length_imm,
6954 const Value* /* elements */, Value* /* result */) {
6955 LiftoffRegister rtt = RttCanon(array_imm.index, {});
6956 ValueKind elem_kind = array_imm.array_type->element_type().kind();
6957 int32_t elem_count = length_imm.index;
6958 // Allocate the array.
6959 CallBuiltin(Builtin::kWasmAllocateArray_Uninitialized,
6960 MakeSig::Returns(kRef).Params(kRef, kI32, kI32),
6961 {VarState{kRef, rtt, 0}, VarState{kI32, elem_count, 0},
6962 VarState{kI32, value_kind_size(elem_kind), 0}},
6963 decoder->position());
6964
6965 // Initialize the array with stack arguments.
6966 LiftoffRegister array(kReturnRegister0);
6967 if (!CheckSupportedType(decoder, elem_kind, "array.new_fixed")) return;
6968 for (int i = elem_count - 1; i >= 0; i--) {
6969 LiftoffRegList pinned{array};
6970 LiftoffRegister element = pinned.set(__ PopToRegister(pinned));
6971 int offset =
6972 WasmArray::kHeaderSize + (i << value_kind_size_log2(elem_kind));
6973 // Skipping the write barrier is safe as long as:
6974 // (1) {array} is freshly allocated, and
6975 // (2) {array} is in new-space (not pretenured).
6976 StoreObjectField(decoder, array.gp(), no_reg,
6977 wasm::ObjectAccess::ToTagged(offset), element, false,
6978 pinned, elem_kind, LiftoffAssembler::kSkipWriteBarrier);
6979 }
6980
6981 // Push the array onto the stack.
6982 __ PushRegister(kRef, array);
6983 }
6984
6985 void ArrayNewSegment(FullDecoder* decoder,
6986 const ArrayIndexImmediate& array_imm,
6987 const IndexImmediate& segment_imm,
6988 const Value& /* offset */, const Value& /* length */,
6989 Value* /* result */) {
6991 LiftoffRegList pinned;
6992
6993 LiftoffRegister rtt = pinned.set(RttCanon(array_imm.index, pinned));
6994
6995 LiftoffRegister is_element_reg =
6996 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
6997 LoadSmi(is_element_reg,
6998 array_imm.array_type->element_type().is_reference());
6999
7000 LiftoffRegister extract_shared_data_reg =
7001 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7002 LoadSmi(extract_shared_data_reg, 0);
7003
7004 CallBuiltin(
7005 Builtin::kWasmArrayNewSegment,
7006 MakeSig::Returns(kRef).Params(kI32, kI32, kI32, kSmiKind, kSmiKind,
7007 kRef),
7008 {
7009 VarState{kI32, static_cast<int>(segment_imm.index), 0}, // segment
7010 __ cache_state()->stack_state.end()[-2], // offset
7011 __ cache_state()->stack_state.end()[-1], // length
7012 VarState{kSmiKind, is_element_reg, 0}, // is_element
7013 VarState{kSmiKind, extract_shared_data_reg, 0}, // shared
7014 VarState{kRef, rtt, 0} // rtt
7015 },
7016 decoder->position());
7017
7018 // Pop parameters from the value stack.
7019 __ DropValues(2);
7020 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7021
7022 LiftoffRegister result(kReturnRegister0);
7023 __ PushRegister(kRef, result);
7024 }
7025
7026 void ArrayInitSegment(FullDecoder* decoder,
7027 const ArrayIndexImmediate& array_imm,
7028 const IndexImmediate& segment_imm,
7029 const Value& /* array */,
7030 const Value& /* array_index */,
7031 const Value& /* segment_offset*/,
7032 const Value& /* length */) {
7034 LiftoffRegList pinned;
7035
7036 LiftoffRegister segment_index_reg =
7037 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7038 LoadSmi(segment_index_reg, static_cast<int32_t>(segment_imm.index));
7039
7040 LiftoffRegister is_element_reg =
7041 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7042 LoadSmi(is_element_reg,
7043 array_imm.array_type->element_type().is_reference());
7044
7045 LiftoffRegister extract_shared_data_reg =
7046 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7047 LoadSmi(extract_shared_data_reg, 0);
7048
7049 // Builtin parameter order: array_index, segment_offset, length,
7050 // segment_index, array.
7051 CallBuiltin(Builtin::kWasmArrayInitSegment,
7052 MakeSig::Params(kI32, kI32, kI32, kSmiKind, kSmiKind, kSmiKind,
7053 kRefNull),
7054 {__ cache_state()->stack_state.end()[-3],
7055 __ cache_state()->stack_state.end()[-2],
7056 __ cache_state()->stack_state.end()[-1],
7057 VarState{kSmiKind, segment_index_reg, 0},
7058 VarState{kSmiKind, is_element_reg, 0},
7059 VarState{kSmiKind, extract_shared_data_reg, 0},
7060 __ cache_state()->stack_state.end()[-4]},
7061 decoder->position());
7062 __ DropValues(4);
7063 }
7064
7065 void RefI31(FullDecoder* decoder, const Value& input, Value* result) {
7066 LiftoffRegister src = __ PopToRegister();
7067 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {});
7068 if constexpr (SmiValuesAre31Bits()) {
7069 static_assert(kSmiTag == 0);
7070 __ emit_i32_shli(dst.gp(), src.gp(), kSmiTagSize);
7071 } else {
7073 // Set the topmost bit to sign-extend the second bit. This way,
7074 // interpretation in JS (if this value escapes there) will be the same as
7075 // i31.get_s.
7076 __ emit_i64_shli(dst, src, kSmiTagSize + kSmiShiftSize + 1);
7077 __ emit_i64_sari(dst, dst, 1);
7078 }
7079 __ PushRegister(kRef, dst);
7080 }
7081
7082 void I31GetS(FullDecoder* decoder, const Value& input, Value* result) {
7083 LiftoffRegList pinned;
7084 LiftoffRegister src = pinned.set(__ PopToRegister());
7085 MaybeEmitNullCheck(decoder, src.gp(), pinned, input.type);
7086 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {});
7087 if constexpr (SmiValuesAre31Bits()) {
7088 __ emit_i32_sari(dst.gp(), src.gp(), kSmiTagSize);
7089 } else {
7091 // The topmost bit is already sign-extended.
7092 // Liftoff expects that the upper half of any i32 value in a register
7093 // is zeroed out, not sign-extended from the lower half.
7094 __ emit_i64_shri(dst, src, kSmiTagSize + kSmiShiftSize);
7095 }
7096 __ PushRegister(kI32, dst);
7097 }
7098
7099 void I31GetU(FullDecoder* decoder, const Value& input, Value* result) {
7100 LiftoffRegList pinned;
7101 LiftoffRegister src = pinned.set(__ PopToRegister());
7102 MaybeEmitNullCheck(decoder, src.gp(), pinned, input.type);
7103 LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {});
7104 if constexpr (SmiValuesAre31Bits()) {
7105 __ emit_i32_shri(dst.gp(), src.gp(), kSmiTagSize);
7106 } else {
7108 // Remove topmost bit.
7109 __ emit_i64_shli(dst, src, 1);
7110 __ emit_i64_shri(dst, dst, kSmiTagSize + kSmiShiftSize + 1);
7111 }
7112 __ PushRegister(kI32, dst);
7113 }
7114
7115 void RefGetDesc(FullDecoder* decoder, const Value& ref_val, Value* desc_val) {
7116 LiftoffRegList pinned;
7117 LiftoffRegister ref = pinned.set(__ PopToRegister());
7118
7119 // Implicit null checks don't cover the map load.
7120 MaybeEmitNullCheck(decoder, ref.gp(), pinned, ref_val.type);
7121
7122 LiftoffRegister value = __ GetUnusedRegister(kGpReg, pinned);
7123 __ LoadMap(value.gp(), ref.gp());
7124 LoadObjectField(
7125 decoder, value, value.gp(), no_reg,
7126 wasm::ObjectAccess::ToTagged(Map::kInstanceDescriptorsOffset), kRef,
7127 false, false, pinned);
7128 __ PushRegister(kRef, value);
7129 }
7130
7131 LiftoffRegister RttCanon(ModuleTypeIndex type_index, LiftoffRegList pinned) {
7132 LiftoffRegister rtt = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7133 LOAD_TAGGED_PTR_INSTANCE_FIELD(rtt.gp(), ManagedObjectMaps, pinned);
7134 __ LoadTaggedPointer(
7135 rtt.gp(), rtt.gp(), no_reg,
7137 return rtt;
7138 }
7139
7140 enum NullSucceeds : bool { // --
7141 kNullSucceeds = true,
7142 kNullFails = false
7143 };
7144
7145 // Falls through on match (=successful type check).
7146 // Returns the register containing the object.
7147 void SubtypeCheck(const WasmModule* module, Register obj_reg,
7148 ValueType obj_type, Register rtt_reg, HeapType target_type,
7149 Register scratch_null, Register scratch2, Label* no_match,
7150 NullSucceeds null_succeeds,
7151 const FreezeCacheState& frozen) {
7152 Label match;
7153 bool is_cast_from_any = obj_type.is_reference_to(HeapType::kAny);
7154
7155 // Skip the null check if casting from any and not {null_succeeds}.
7156 // In that case the instance type check will identify null as not being a
7157 // wasm object and fail.
7158 if (obj_type.is_nullable() && (!is_cast_from_any || null_succeeds)) {
7159 __ emit_cond_jump(kEqual, null_succeeds ? &match : no_match,
7160 obj_type.kind(), obj_reg, scratch_null, frozen);
7161 }
7162 Register tmp1 = scratch_null; // Done with null checks.
7163
7164 // Add Smi check if the source type may store a Smi (i31ref or JS Smi).
7165 if (IsSubtypeOf(kWasmRefI31, obj_type, module)) {
7167 frozen);
7168 }
7169
7170 __ LoadMap(tmp1, obj_reg);
7171 // {tmp1} now holds the object's map.
7172
7173 if (module->type(target_type.ref_index()).is_final ||
7174 target_type.is_exact()) {
7175 // In this case, simply check for map equality.
7176 __ emit_cond_jump(kNotEqual, no_match, ValueKind::kRef, tmp1, rtt_reg,
7177 frozen);
7178 } else {
7179 // Check for rtt equality, and if not, check if the rtt is a struct/array
7180 // rtt.
7181 __ emit_cond_jump(kEqual, &match, ValueKind::kRef, tmp1, rtt_reg, frozen);
7182
7183 if (is_cast_from_any) {
7184 // Check for map being a map for a wasm object (struct, array, func).
7185 __ Load(LiftoffRegister(scratch2), tmp1, no_reg,
7186 wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset),
7187 LoadType::kI32Load16U);
7188 __ emit_i32_subi(scratch2, scratch2, FIRST_WASM_OBJECT_TYPE);
7189 __ emit_i32_cond_jumpi(kUnsignedGreaterThan, no_match, scratch2,
7190 LAST_WASM_OBJECT_TYPE - FIRST_WASM_OBJECT_TYPE,
7191 frozen);
7192 }
7193
7194 // Constant-time subtyping check: load exactly one candidate RTT from the
7195 // supertypes list.
7196 // Step 1: load the WasmTypeInfo into {tmp1}.
7197 constexpr int kTypeInfoOffset = wasm::ObjectAccess::ToTagged(
7198 Map::kConstructorOrBackPointerOrNativeContextOffset);
7199 __ LoadTaggedPointer(tmp1, tmp1, no_reg, kTypeInfoOffset);
7200 // Step 2: check the list's length if needed.
7201 uint32_t rtt_depth = GetSubtypingDepth(module, target_type.ref_index());
7202 if (rtt_depth >= kMinimumSupertypeArraySize) {
7203 LiftoffRegister list_length(scratch2);
7204 int offset =
7205 ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesLengthOffset);
7206 __ LoadSmiAsInt32(list_length, tmp1, offset);
7207 __ emit_i32_cond_jumpi(kUnsignedLessThanEqual, no_match,
7208 list_length.gp(), rtt_depth, frozen);
7209 }
7210 // Step 3: load the candidate list slot into {tmp1}, and compare it.
7211 __ LoadTaggedPointer(
7212 tmp1, tmp1, no_reg,
7213 ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset +
7214 rtt_depth * kTaggedSize));
7215 __ emit_cond_jump(kNotEqual, no_match, ValueKind::kRef, tmp1, rtt_reg,
7216 frozen);
7217 }
7218
7219 // Fall through to {match}.
7220 __ bind(&match);
7221 }
7222
7223 void RefTest(FullDecoder* decoder, HeapType target_type, const Value& obj,
7224 Value* /* result_val */, bool null_succeeds) {
7225 Label return_false, done;
7226 LiftoffRegList pinned;
7227 LiftoffRegister rtt_reg =
7228 pinned.set(RttCanon(target_type.ref_index(), pinned));
7229 LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned));
7230 Register scratch_null =
7231 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7232 LiftoffRegister result = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7233 if (obj.type.is_nullable()) {
7234 LoadNullValueForCompare(scratch_null, pinned, obj.type);
7235 }
7236
7237 {
7238 FREEZE_STATE(frozen);
7239 SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(),
7240 target_type, scratch_null, result.gp(), &return_false,
7241 null_succeeds ? kNullSucceeds : kNullFails, frozen);
7242
7243 __ LoadConstant(result, WasmValue(1));
7244 // TODO(jkummerow): Emit near jumps on platforms that have them.
7245 __ emit_jump(&done);
7246
7247 __ bind(&return_false);
7248 __ LoadConstant(result, WasmValue(0));
7249 __ bind(&done);
7250 }
7251 __ PushRegister(kI32, result);
7252 }
7253
7254 void RefTestAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
7255 Value* result_val, bool null_succeeds) {
7256 switch (type.representation()) {
7257 case HeapType::kEq:
7258 return AbstractTypeCheck<&LiftoffCompiler::EqCheck>(decoder, obj,
7260 case HeapType::kI31:
7261 return AbstractTypeCheck<&LiftoffCompiler::I31Check>(decoder, obj,
7263 case HeapType::kStruct:
7264 return AbstractTypeCheck<&LiftoffCompiler::StructCheck>(decoder, obj,
7266 case HeapType::kArray:
7267 return AbstractTypeCheck<&LiftoffCompiler::ArrayCheck>(decoder, obj,
7269 case HeapType::kString:
7270 return AbstractTypeCheck<&LiftoffCompiler::StringCheck>(decoder, obj,
7272 case HeapType::kNone:
7274 case HeapType::kNoFunc:
7275 case HeapType::kNoExn:
7277 return EmitIsNull(kExprRefIsNull, obj.type);
7278 case HeapType::kAny:
7279 // Any may never need a cast as it is either implicitly convertible or
7280 // never convertible for any given type.
7281 default:
7282 UNREACHABLE();
7283 }
7284 }
7285
7286 void RefCast(FullDecoder* decoder, const Value& obj, Value* result) {
7287 if (v8_flags.experimental_wasm_assume_ref_cast_succeeds) return;
7288 LiftoffRegister rtt = RttCanon(result->type.ref_index(), {});
7289 return RefCastImpl(decoder, result->type, obj, rtt);
7290 }
7291
7292 void RefCastDesc(FullDecoder* decoder, const Value& obj, const Value& desc,
7293 Value* result) {
7294 if (v8_flags.experimental_wasm_assume_ref_cast_succeeds) {
7295 __ DropValues(1); // Drop the descriptor, pretend it was consumed.
7296 return;
7297 }
7298 LiftoffRegister rtt = GetRttFromDescriptorOnStack(decoder, desc);
7299 // Pretending that the target type is exact skips the supertype check.
7300 return RefCastImpl(decoder, result->type.AsExact(), obj, rtt);
7301 }
7302
7303 void RefCastImpl(FullDecoder* decoder, ValueType target_type,
7304 const Value& obj, LiftoffRegister rtt) {
7305 LiftoffRegList pinned{rtt};
7306 LiftoffRegister obj_reg = pinned.set(__ PopToRegister(pinned));
7307 Register scratch_null =
7308 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7309 Register scratch2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7310 if (obj.type.is_nullable()) {
7311 LoadNullValueForCompare(scratch_null, pinned, obj.type);
7312 }
7313
7314 {
7315 NullSucceeds on_null =
7316 target_type.is_nullable() ? kNullSucceeds : kNullFails;
7317 OolTrapLabel trap =
7318 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapIllegalCast);
7319 SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt.gp(),
7320 target_type.heap_type(), scratch_null, scratch2,
7321 trap.label(), on_null, trap.frozen());
7322 }
7323 __ PushRegister(obj.type.kind(), obj_reg);
7324 }
7325
7326 void RefCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
7327 Value* result_val, bool null_succeeds) {
7328 switch (type.representation()) {
7329 case HeapType::kEq:
7332 case HeapType::kI31:
7335 case HeapType::kStruct:
7338 case HeapType::kArray:
7341 case HeapType::kString:
7344 case HeapType::kNone:
7346 case HeapType::kNoFunc:
7347 case HeapType::kNoExn:
7349 return AssertNullTypecheck(decoder, obj, result_val);
7350 case HeapType::kAny:
7351 // Any may never need a cast as it is either implicitly convertible or
7352 // never convertible for any given type.
7353 default:
7354 UNREACHABLE();
7355 }
7356 }
7357
7358 void BrOnCast(FullDecoder* decoder, HeapType target_type, const Value& obj,
7359 Value* /* result_on_branch */, uint32_t depth,
7360 bool null_succeeds) {
7361 LiftoffRegister rtt = RttCanon(target_type.ref_index(), {});
7362 return BrOnCastImpl(decoder, target_type, obj, rtt, depth, null_succeeds);
7363 }
7364
7365 void BrOnCastDesc(FullDecoder* decoder, HeapType target_type,
7366 const Value& obj, const Value& descriptor,
7367 Value* /* result_on_branch */, uint32_t depth,
7368 bool null_succeeds) {
7369 LiftoffRegister rtt = GetRttFromDescriptorOnStack(decoder, descriptor);
7370 // Pretending that the target type is exact skips the supertype check.
7371 return BrOnCastImpl(decoder, target_type.AsExact(), obj, rtt, depth,
7373 }
7374 void BrOnCastImpl(FullDecoder* decoder, HeapType target_type,
7375 const Value& obj, LiftoffRegister rtt, uint32_t depth,
7376 bool null_succeeds) {
7377 LiftoffRegList pinned{rtt};
7378 // Avoid having sequences of branches do duplicate work.
7379 if (depth != decoder->control_depth() - 1) {
7380 __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity,
7381 pinned);
7382 }
7383
7384 Label cont_false;
7385 LiftoffRegister obj_reg = pinned.set(__ PeekToRegister(0, pinned));
7386 Register scratch_null =
7387 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7388 Register scratch2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7389 if (obj.type.is_nullable()) {
7390 LoadNullValue(scratch_null, kWasmAnyRef);
7391 }
7392 FREEZE_STATE(frozen);
7393
7394 NullSucceeds null_handling = null_succeeds ? kNullSucceeds : kNullFails;
7395 SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt.gp(),
7396 target_type, scratch_null, scratch2, &cont_false,
7397 null_handling, frozen);
7398
7399 BrOrRet(decoder, depth);
7400
7401 __ bind(&cont_false);
7402 }
7403
7404 void BrOnCastFail(FullDecoder* decoder, HeapType target_type,
7405 const Value& obj, Value* /* result_on_fallthrough */,
7406 uint32_t depth, bool null_succeeds) {
7407 LiftoffRegister rtt = RttCanon(target_type.ref_index(), {});
7408 return BrOnCastFailImpl(decoder, target_type, obj, rtt, depth,
7410 }
7411
7412 void BrOnCastDescFail(FullDecoder* decoder, HeapType target_type,
7413 const Value& obj, const Value& descriptor,
7414 Value* /* result_on_fallthrough */, uint32_t depth,
7415 bool null_succeeds) {
7416 LiftoffRegister rtt = GetRttFromDescriptorOnStack(decoder, descriptor);
7417 // Pretending that the target type is exact skips the supertype check.
7418 return BrOnCastFailImpl(decoder, target_type.AsExact(), obj, rtt, depth,
7420 }
7421
7422 void BrOnCastFailImpl(FullDecoder* decoder, HeapType target_type,
7423 const Value& obj, LiftoffRegister rtt, uint32_t depth,
7424 bool null_succeeds) {
7425 LiftoffRegList pinned{rtt};
7426 // Avoid having sequences of branches do duplicate work.
7427 if (depth != decoder->control_depth() - 1) {
7428 __ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity,
7429 pinned);
7430 }
7431
7432 Label cont_branch, fallthrough;
7433
7434 LiftoffRegister obj_reg = pinned.set(__ PeekToRegister(0, pinned));
7435 Register scratch_null =
7436 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7437 Register scratch2 = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7438 if (obj.type.is_nullable()) {
7439 LoadNullValue(scratch_null, kWasmAnyRef);
7440 }
7441 FREEZE_STATE(frozen);
7442
7443 NullSucceeds null_handling = null_succeeds ? kNullSucceeds : kNullFails;
7444 SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt.gp(),
7445 target_type, scratch_null, scratch2, &cont_branch,
7446 null_handling, frozen);
7447 __ emit_jump(&fallthrough);
7448
7449 __ bind(&cont_branch);
7450 BrOrRet(decoder, depth);
7451
7452 __ bind(&fallthrough);
7453 }
7454
7455 void BrOnCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
7456 Value* result_on_branch, uint32_t depth,
7457 bool null_succeeds) {
7458 switch (type.representation()) {
7459 case HeapType::kEq:
7460 return BrOnAbstractType<&LiftoffCompiler::EqCheck>(obj, decoder, depth,
7462 case HeapType::kI31:
7463 return BrOnAbstractType<&LiftoffCompiler::I31Check>(obj, decoder, depth,
7465 case HeapType::kStruct:
7466 return BrOnAbstractType<&LiftoffCompiler::StructCheck>(
7467 obj, decoder, depth, null_succeeds);
7468 case HeapType::kArray:
7469 return BrOnAbstractType<&LiftoffCompiler::ArrayCheck>(
7470 obj, decoder, depth, null_succeeds);
7471 case HeapType::kString:
7472 return BrOnAbstractType<&LiftoffCompiler::StringCheck>(
7473 obj, decoder, depth, null_succeeds);
7474 case HeapType::kNone:
7476 case HeapType::kNoFunc:
7477 case HeapType::kNoExn:
7479 return BrOnNull(decoder, obj, depth, /*pass_null_along_branch*/ true,
7480 nullptr);
7481 case HeapType::kAny:
7482 // Any may never need a cast as it is either implicitly convertible or
7483 // never convertible for any given type.
7484 default:
7485 UNREACHABLE();
7486 }
7487 }
7488
7489 void BrOnCastFailAbstract(FullDecoder* decoder, const Value& obj,
7490 HeapType type, Value* result_on_fallthrough,
7491 uint32_t depth, bool null_succeeds) {
7492 switch (type.representation()) {
7493 case HeapType::kEq:
7494 return BrOnNonAbstractType<&LiftoffCompiler::EqCheck>(
7495 obj, decoder, depth, null_succeeds);
7496 case HeapType::kI31:
7497 return BrOnNonAbstractType<&LiftoffCompiler::I31Check>(
7498 obj, decoder, depth, null_succeeds);
7499 case HeapType::kStruct:
7500 return BrOnNonAbstractType<&LiftoffCompiler::StructCheck>(
7501 obj, decoder, depth, null_succeeds);
7502 case HeapType::kArray:
7503 return BrOnNonAbstractType<&LiftoffCompiler::ArrayCheck>(
7504 obj, decoder, depth, null_succeeds);
7505 case HeapType::kString:
7506 return BrOnNonAbstractType<&LiftoffCompiler::StringCheck>(
7507 obj, decoder, depth, null_succeeds);
7508 case HeapType::kNone:
7510 case HeapType::kNoFunc:
7511 case HeapType::kNoExn:
7513 return BrOnNonNull(decoder, obj, nullptr, depth,
7514 /*drop_null_on_fallthrough*/ false);
7515 case HeapType::kAny:
7516 // Any may never need a cast as it is either implicitly convertible or
7517 // never convertible for any given type.
7518 default:
7519 UNREACHABLE();
7520 }
7521 }
7522
7523 struct TypeCheck {
7524 Register obj_reg = no_reg;
7525 ValueType obj_type;
7526 Register tmp = no_reg;
7527 Label* no_match;
7529 std::optional<OolTrapLabel> trap;
7531
7532 TypeCheck(ValueType obj_type, Label* no_match, bool null_succeeds,
7533 Builtin no_match_trap = Builtin::kNoBuiltinId)
7534 : obj_type(obj_type),
7538 // Either a non-trapping no_match label needs to be provided or a builtin
7539 // to trap with in case the type check fails.
7541 }
7542
7543 Register null_reg() { return tmp; } // After {Initialize}.
7544 Register instance_type() { return tmp; } // After {LoadInstanceType}.
7545 };
7546
7547 enum PopOrPeek { kPop, kPeek };
7548
7549 void Initialize(TypeCheck& check, FullDecoder* decoder, PopOrPeek pop_or_peek,
7550 ValueType type) {
7551 LiftoffRegList pinned;
7552 if (pop_or_peek == kPop) {
7553 check.obj_reg = pinned.set(__ PopToRegister(pinned)).gp();
7554 } else {
7555 check.obj_reg = pinned.set(__ PeekToRegister(0, pinned)).gp();
7556 }
7557 check.tmp = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
7558 if (check.obj_type.is_nullable()) {
7559 LoadNullValue(check.null_reg(), type);
7560 }
7561 if (check.no_match == nullptr) {
7562 check.trap.emplace(AddOutOfLineTrap(decoder, check.no_match_trap));
7563 check.no_match = check.trap->label();
7564 }
7565 }
7566 void LoadInstanceType(TypeCheck& check, const FreezeCacheState& frozen,
7567 Label* on_smi) {
7568 // The check for null_succeeds == true has to be handled by the caller!
7569 // TODO(mliedtke): Reiterate the null_succeeds case once all generic cast
7570 // instructions are implemented.
7571 if (!check.null_succeeds && check.obj_type.is_nullable()) {
7572 __ emit_cond_jump(kEqual, check.no_match, kRefNull, check.obj_reg,
7573 check.null_reg(), frozen);
7574 }
7575 __ emit_smi_check(check.obj_reg, on_smi, LiftoffAssembler::kJumpOnSmi,
7576 frozen);
7577 __ LoadMap(check.instance_type(), check.obj_reg);
7578 __ Load(LiftoffRegister(check.instance_type()), check.instance_type(),
7579 no_reg, wasm::ObjectAccess::ToTagged(Map::kInstanceTypeOffset),
7580 LoadType::kI32Load16U);
7581 }
7582
7583 // Abstract type checkers. They all fall through on match.
7584 void StructCheck(TypeCheck& check, const FreezeCacheState& frozen) {
7585 LoadInstanceType(check, frozen, check.no_match);
7586 LiftoffRegister instance_type(check.instance_type());
7587 __ emit_i32_cond_jumpi(kNotEqual, check.no_match, check.instance_type(),
7588 WASM_STRUCT_TYPE, frozen);
7589 }
7590
7591 void ArrayCheck(TypeCheck& check, const FreezeCacheState& frozen) {
7592 LoadInstanceType(check, frozen, check.no_match);
7593 LiftoffRegister instance_type(check.instance_type());
7594 __ emit_i32_cond_jumpi(kNotEqual, check.no_match, check.instance_type(),
7595 WASM_ARRAY_TYPE, frozen);
7596 }
7597
7598 void I31Check(TypeCheck& check, const FreezeCacheState& frozen) {
7599 __ emit_smi_check(check.obj_reg, check.no_match,
7601 }
7602
7603 void EqCheck(TypeCheck& check, const FreezeCacheState& frozen) {
7604 Label match;
7605 LoadInstanceType(check, frozen, &match);
7606 // We're going to test a range of WasmObject instance types with a single
7607 // unsigned comparison.
7608 Register tmp = check.instance_type();
7609 __ emit_i32_subi(tmp, tmp, FIRST_WASM_OBJECT_TYPE);
7610 __ emit_i32_cond_jumpi(kUnsignedGreaterThan, check.no_match, tmp,
7611 LAST_WASM_OBJECT_TYPE - FIRST_WASM_OBJECT_TYPE,
7612 frozen);
7613 __ bind(&match);
7614 }
7615
7616 void StringCheck(TypeCheck& check, const FreezeCacheState& frozen) {
7617 LoadInstanceType(check, frozen, check.no_match);
7618 LiftoffRegister instance_type(check.instance_type());
7619 __ emit_i32_cond_jumpi(kUnsignedGreaterThanEqual, check.no_match,
7620 check.instance_type(), FIRST_NONSTRING_TYPE, frozen);
7621 }
7622
7623 using TypeChecker = void (LiftoffCompiler::*)(TypeCheck& check,
7624 const FreezeCacheState& frozen);
7625
7626 template <TypeChecker type_checker>
7627 void AbstractTypeCheck(FullDecoder* decoder, const Value& object,
7628 bool null_succeeds) {
7629 Label match, no_match, done;
7630 TypeCheck check(object.type, &no_match, null_succeeds);
7631 Initialize(check, decoder, kPop, object.type);
7632 LiftoffRegister result(check.tmp);
7633 {
7634 FREEZE_STATE(frozen);
7635
7636 if (null_succeeds && check.obj_type.is_nullable()) {
7637 __ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg,
7638 check.null_reg(), frozen);
7639 }
7640
7641 (this->*type_checker)(check, frozen);
7642
7643 __ bind(&match);
7644 __ LoadConstant(result, WasmValue(1));
7645 // TODO(jkummerow): Emit near jumps on platforms that have them.
7646 __ emit_jump(&done);
7647
7648 __ bind(&no_match);
7649 __ LoadConstant(result, WasmValue(0));
7650 __ bind(&done);
7651 }
7652 __ PushRegister(kI32, result);
7653 }
7654
7655 template <TypeChecker type_checker>
7656 void AbstractTypeCast(const Value& object, FullDecoder* decoder,
7657 bool null_succeeds) {
7658 Label match;
7659 TypeCheck check(object.type, nullptr, null_succeeds,
7660 Builtin::kThrowWasmTrapIllegalCast);
7661 Initialize(check, decoder, kPeek, object.type);
7662 FREEZE_STATE(frozen);
7663
7664 if (null_succeeds && check.obj_type.is_nullable()) {
7665 __ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg,
7666 check.null_reg(), frozen);
7667 }
7668 (this->*type_checker)(check, frozen);
7669 __ bind(&match);
7670 }
7671
7672 template <TypeChecker type_checker>
7673 void BrOnAbstractType(const Value& object, FullDecoder* decoder,
7674 uint32_t br_depth, bool null_succeeds) {
7675 // Avoid having sequences of branches do duplicate work.
7676 if (br_depth != decoder->control_depth() - 1) {
7677 __ PrepareForBranch(decoder->control_at(br_depth)->br_merge()->arity, {});
7678 }
7679
7680 Label no_match, match;
7681 TypeCheck check(object.type, &no_match, null_succeeds);
7682 Initialize(check, decoder, kPeek, object.type);
7683 FREEZE_STATE(frozen);
7684
7685 if (null_succeeds && check.obj_type.is_nullable()) {
7686 __ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg,
7687 check.null_reg(), frozen);
7688 }
7689
7690 (this->*type_checker)(check, frozen);
7691 __ bind(&match);
7692 BrOrRet(decoder, br_depth);
7693
7694 __ bind(&no_match);
7695 }
7696
7697 template <TypeChecker type_checker>
7698 void BrOnNonAbstractType(const Value& object, FullDecoder* decoder,
7699 uint32_t br_depth, bool null_succeeds) {
7700 // Avoid having sequences of branches do duplicate work.
7701 if (br_depth != decoder->control_depth() - 1) {
7702 __ PrepareForBranch(decoder->control_at(br_depth)->br_merge()->arity, {});
7703 }
7704
7705 Label no_match, end;
7706 TypeCheck check(object.type, &no_match, null_succeeds);
7707 Initialize(check, decoder, kPeek, object.type);
7708 FREEZE_STATE(frozen);
7709
7710 if (null_succeeds && check.obj_type.is_nullable()) {
7711 __ emit_cond_jump(kEqual, &end, kRefNull, check.obj_reg, check.null_reg(),
7712 frozen);
7713 }
7714
7715 (this->*type_checker)(check, frozen);
7716 __ emit_jump(&end);
7717
7718 __ bind(&no_match);
7719 BrOrRet(decoder, br_depth);
7720
7721 __ bind(&end);
7722 }
7723
7724 void StringNewWtf8(FullDecoder* decoder, const MemoryIndexImmediate& imm,
7725 const unibrow::Utf8Variant variant, const Value& offset,
7726 const Value& size, Value* result) {
7728 LiftoffRegList pinned;
7729
7730 VarState memory_var{kI32, static_cast<int>(imm.index), 0};
7731
7732 LiftoffRegister variant_reg =
7733 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7734 LoadSmi(variant_reg, static_cast<int32_t>(variant));
7735 VarState variant_var(kSmiKind, variant_reg, 0);
7736
7737 VarState& size_var = __ cache_state()->stack_state.end()[-1];
7738
7739 DCHECK(MatchingMemType(imm.memory, 1));
7740 VarState address = IndexToVarStateSaturating(1, &pinned);
7741
7742 CallBuiltin(
7743 Builtin::kWasmStringNewWtf8,
7744 MakeSig::Returns(kRefNull).Params(kIntPtrKind, kI32, kI32, kSmiKind),
7745 {address, size_var, memory_var, variant_var}, decoder->position());
7746 __ DropValues(2);
7747 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7748
7749 LiftoffRegister result_reg(kReturnRegister0);
7750 __ PushRegister(kRef, result_reg);
7751 }
7752
7753 void StringNewWtf8Array(FullDecoder* decoder,
7754 const unibrow::Utf8Variant variant,
7755 const Value& array, const Value& start,
7756 const Value& end, Value* result) {
7758 LiftoffRegList pinned;
7759
7760 LiftoffRegister array_reg = pinned.set(
7761 __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
7762 MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type);
7763 VarState array_var(kRef, array_reg, 0);
7764
7765 LiftoffRegister variant_reg =
7766 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7767 LoadSmi(variant_reg, static_cast<int32_t>(variant));
7768 VarState variant_var(kSmiKind, variant_reg, 0);
7769
7770 CallBuiltin(Builtin::kWasmStringNewWtf8Array,
7771 MakeSig::Returns(kRefNull).Params(kI32, kI32, kRef, kSmiKind),
7772 {
7773 __ cache_state()->stack_state.end()[-2], // start
7774 __ cache_state()->stack_state.end()[-1], // end
7775 array_var,
7776 variant_var,
7777 },
7778 decoder->position());
7779 __ cache_state()->stack_state.pop_back(3);
7780 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7781
7782 LiftoffRegister result_reg(kReturnRegister0);
7783 __ PushRegister(kRef, result_reg);
7784 }
7785
7786 void StringNewWtf16(FullDecoder* decoder, const MemoryIndexImmediate& imm,
7787 const Value& offset, const Value& size, Value* result) {
7789 VarState memory_var{kI32, static_cast<int32_t>(imm.index), 0};
7790
7791 VarState& size_var = __ cache_state()->stack_state.end()[-1];
7792
7793 LiftoffRegList pinned;
7794 DCHECK(MatchingMemType(imm.memory, 1));
7795 VarState address = IndexToVarStateSaturating(1, &pinned);
7796
7797 CallBuiltin(Builtin::kWasmStringNewWtf16,
7798 MakeSig::Returns(kRef).Params(kI32, kIntPtrKind, kI32),
7799 {memory_var, address, size_var}, decoder->position());
7800 __ DropValues(2);
7801 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7802
7803 LiftoffRegister result_reg(kReturnRegister0);
7804 __ PushRegister(kRef, result_reg);
7805 }
7806
7807 void StringNewWtf16Array(FullDecoder* decoder, const Value& array,
7808 const Value& start, const Value& end,
7809 Value* result) {
7811 LiftoffRegList pinned;
7812
7813 LiftoffRegister array_reg = pinned.set(
7814 __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
7815 MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type);
7816 VarState array_var(kRef, array_reg, 0);
7817
7818 CallBuiltin(Builtin::kWasmStringNewWtf16Array,
7819 MakeSig::Returns(kRef).Params(kRef, kI32, kI32),
7820 {
7821 array_var,
7822 __ cache_state()->stack_state.end()[-2], // start
7823 __ cache_state()->stack_state.end()[-1], // end
7824 },
7825 decoder->position());
7826 __ cache_state()->stack_state.pop_back(3);
7827 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7828
7829 LiftoffRegister result_reg(kReturnRegister0);
7830 __ PushRegister(kRef, result_reg);
7831 }
7832
7833 void StringConst(FullDecoder* decoder, const StringConstImmediate& imm,
7834 Value* result) {
7836 VarState index_var{kI32, static_cast<int32_t>(imm.index), 0};
7837
7838 CallBuiltin(Builtin::kWasmStringConst, MakeSig::Returns(kRef).Params(kI32),
7839 {index_var}, decoder->position());
7840 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7841
7842 LiftoffRegister result_reg(kReturnRegister0);
7843 __ PushRegister(kRef, result_reg);
7844 }
7845
7846 void StringMeasureWtf8(FullDecoder* decoder,
7847 const unibrow::Utf8Variant variant, const Value& str,
7848 Value* result) {
7850 LiftoffRegList pinned;
7851 LiftoffRegister string_reg = pinned.set(__ PopToRegister(pinned));
7852 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
7853 VarState string_var(kRef, string_reg, 0);
7854
7856 switch (variant) {
7857 case unibrow::Utf8Variant::kUtf8:
7858 builtin = Builtin::kWasmStringMeasureUtf8;
7859 break;
7861 case unibrow::Utf8Variant::kWtf8:
7862 builtin = Builtin::kWasmStringMeasureWtf8;
7863 break;
7864 case unibrow::Utf8Variant::kUtf8NoTrap:
7865 UNREACHABLE();
7866 }
7867 CallBuiltin(builtin, MakeSig::Returns(kI32).Params(kRef),
7868 {
7869 string_var,
7870 },
7871 decoder->position());
7872 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7873
7874 LiftoffRegister result_reg(kReturnRegister0);
7875 __ PushRegister(kI32, result_reg);
7876 }
7877
7878 void StringMeasureWtf16(FullDecoder* decoder, const Value& str,
7879 Value* result) {
7880 LiftoffRegList pinned;
7881 LiftoffRegister string_reg = pinned.set(__ PopToRegister(pinned));
7882 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
7883 LiftoffRegister value = __ GetUnusedRegister(kGpReg, pinned);
7884 LoadObjectField(decoder, value, string_reg.gp(), no_reg,
7887 ValueKind::kI32, false /* is_signed */,
7888 false /* trapping */, pinned);
7889 __ PushRegister(kI32, value);
7890 }
7891
7892 void StringEncodeWtf8(FullDecoder* decoder, const MemoryIndexImmediate& imm,
7893 const unibrow::Utf8Variant variant, const Value& str,
7894 const Value& offset, Value* result) {
7896 LiftoffRegList pinned;
7897
7898 DCHECK(MatchingMemType(imm.memory, 0));
7899 VarState offset_var = IndexToVarStateSaturating(0, &pinned);
7900
7901 LiftoffRegister string_reg = pinned.set(
7902 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
7903 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
7904 VarState string_var(kRef, string_reg, 0);
7905
7906 VarState memory_var{kI32, static_cast<int32_t>(imm.index), 0};
7907 VarState variant_var{kI32, static_cast<int32_t>(variant), 0};
7908
7909 CallBuiltin(Builtin::kWasmStringEncodeWtf8,
7910 MakeSig::Returns(kI32).Params(kIntPtrKind, kI32, kI32, kRef),
7911 {offset_var, memory_var, variant_var, string_var},
7912 decoder->position());
7913 __ DropValues(2);
7914 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7915
7916 LiftoffRegister result_reg(kReturnRegister0);
7917 __ PushRegister(kI32, result_reg);
7918 }
7919
7920 void StringEncodeWtf8Array(FullDecoder* decoder,
7921 const unibrow::Utf8Variant variant,
7922 const Value& str, const Value& array,
7923 const Value& start, Value* result) {
7925 LiftoffRegList pinned;
7926
7927 LiftoffRegister array_reg = pinned.set(
7928 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
7929 MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type);
7930 VarState array_var(kRef, array_reg, 0);
7931
7932 LiftoffRegister string_reg = pinned.set(
7933 __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
7934 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
7935 VarState string_var(kRef, string_reg, 0);
7936
7937 VarState& start_var = __ cache_state()->stack_state.end()[-1];
7938
7939 LiftoffRegister variant_reg =
7940 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
7941 LoadSmi(variant_reg, static_cast<int32_t>(variant));
7942 VarState variant_var(kSmiKind, variant_reg, 0);
7943
7944 CallBuiltin(Builtin::kWasmStringEncodeWtf8Array,
7945 MakeSig::Returns(kI32).Params(kRef, kRef, kI32, kSmiKind),
7946 {
7947 string_var,
7948 array_var,
7949 start_var,
7950 variant_var,
7951 },
7952 decoder->position());
7953 __ DropValues(3);
7954 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7955
7956 LiftoffRegister result_reg(kReturnRegister0);
7957 __ PushRegister(kI32, result_reg);
7958 }
7959
7960 void StringEncodeWtf16(FullDecoder* decoder, const MemoryIndexImmediate& imm,
7961 const Value& str, const Value& offset, Value* result) {
7963 LiftoffRegList pinned;
7964
7965 DCHECK(MatchingMemType(imm.memory, 0));
7966 VarState offset_var = IndexToVarStateSaturating(0, &pinned);
7967
7968 LiftoffRegister string_reg = pinned.set(
7969 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
7970 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
7971 VarState string_var(kRef, string_reg, 0);
7972
7973 VarState memory_var{kI32, static_cast<int32_t>(imm.index), 0};
7974
7975 CallBuiltin(Builtin::kWasmStringEncodeWtf16,
7976 MakeSig::Returns(kI32).Params(kRef, kIntPtrKind, kI32),
7977 {string_var, offset_var, memory_var}, decoder->position());
7978 __ DropValues(2);
7979 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
7980
7981 LiftoffRegister result_reg(kReturnRegister0);
7982 __ PushRegister(kI32, result_reg);
7983 }
7984
7985 void StringEncodeWtf16Array(FullDecoder* decoder, const Value& str,
7986 const Value& array, const Value& start,
7987 Value* result) {
7989 LiftoffRegList pinned;
7990
7991 LiftoffRegister array_reg = pinned.set(
7992 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
7993 MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type);
7994 VarState array_var(kRef, array_reg, 0);
7995
7996 LiftoffRegister string_reg = pinned.set(
7997 __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
7998 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
7999 VarState string_var(kRef, string_reg, 0);
8000
8001 VarState& start_var = __ cache_state()->stack_state.end()[-1];
8002
8003 CallBuiltin(Builtin::kWasmStringEncodeWtf16Array,
8004 MakeSig::Returns(kI32).Params(kRef, kRef, kI32),
8005 {
8006 string_var,
8007 array_var,
8008 start_var,
8009 },
8010 decoder->position());
8011 __ DropValues(3);
8012 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8013
8014 LiftoffRegister result_reg(kReturnRegister0);
8015 __ PushRegister(kI32, result_reg);
8016 }
8017
8018 void StringConcat(FullDecoder* decoder, const Value& head, const Value& tail,
8019 Value* result) {
8020 FUZZER_HEAVY_INSTRUCTION; // Fast, but may create very long strings.
8021 LiftoffRegList pinned;
8022
8023 LiftoffRegister tail_reg = pinned.set(__ PopToRegister(pinned));
8024 MaybeEmitNullCheck(decoder, tail_reg.gp(), pinned, tail.type);
8025 VarState tail_var(kRef, tail_reg, 0);
8026
8027 LiftoffRegister head_reg = pinned.set(__ PopToRegister(pinned));
8028 MaybeEmitNullCheck(decoder, head_reg.gp(), pinned, head.type);
8029 VarState head_var(kRef, head_reg, 0);
8030
8031 CallBuiltin(Builtin::kWasmStringConcat,
8032 MakeSig::Returns(kRef).Params(kRef, kRef),
8033 {
8034 head_var,
8035 tail_var,
8036 },
8037 decoder->position());
8038 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8039
8040 LiftoffRegister result_reg(kReturnRegister0);
8041 __ PushRegister(kRef, result_reg);
8042 }
8043
8044 void StringEq(FullDecoder* decoder, const Value& a, const Value& b,
8045 Value* result) {
8046 FUZZER_HEAVY_INSTRUCTION; // Slow path is linear in string length.
8047 LiftoffRegister result_reg(kReturnRegister0);
8048 LiftoffRegList pinned{result_reg};
8049 LiftoffRegister b_reg = pinned.set(__ PopToModifiableRegister(pinned));
8050 LiftoffRegister a_reg = pinned.set(__ PopToModifiableRegister(pinned));
8051
8052 __ SpillAllRegisters();
8053
8054 Label done;
8055
8056 {
8057 LiftoffRegister null = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
8058 bool check_for_null = a.type.is_nullable() || b.type.is_nullable();
8059 if (check_for_null) {
8060 LoadNullValueForCompare(null.gp(), pinned, kWasmStringRef);
8061 }
8062
8063 FREEZE_STATE(frozen);
8064
8065 // If values pointer-equal, result is 1.
8066 __ LoadConstant(result_reg, WasmValue(int32_t{1}));
8067 __ emit_cond_jump(kEqual, &done, kRefNull, a_reg.gp(), b_reg.gp(),
8068 frozen);
8069
8070 // Otherwise if either operand is null, result is 0.
8071 if (check_for_null) {
8072 __ LoadConstant(result_reg, WasmValue(int32_t{0}));
8073 if (a.type.is_nullable()) {
8074 __ emit_cond_jump(kEqual, &done, kRefNull, a_reg.gp(), null.gp(),
8075 frozen);
8076 }
8077 if (b.type.is_nullable()) {
8078 __ emit_cond_jump(kEqual, &done, kRefNull, b_reg.gp(), null.gp(),
8079 frozen);
8080 }
8081 }
8082
8083 // Ending the frozen state here is fine, because we already spilled the
8084 // rest of the cache, and the subsequent runtime call will reset the cache
8085 // state anyway.
8086 }
8087
8088 // Operands are pointer-distinct and neither is null; call out to the
8089 // runtime.
8090 VarState a_var(kRef, a_reg, 0);
8091 VarState b_var(kRef, b_reg, 0);
8092 CallBuiltin(Builtin::kWasmStringEqual,
8093 MakeSig::Returns(kI32).Params(kRef, kRef),
8094 {
8095 a_var,
8096 b_var,
8097 },
8098 decoder->position());
8099 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8100
8101 __ bind(&done);
8102
8103 __ PushRegister(kI32, result_reg);
8104 }
8105
8106 void StringIsUSVSequence(FullDecoder* decoder, const Value& str,
8107 Value* result) {
8109 LiftoffRegList pinned;
8110
8111 LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned));
8112 MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type);
8113 VarState str_var(kRef, str_reg, 0);
8114
8115 CallBuiltin(Builtin::kWasmStringIsUSVSequence,
8116 MakeSig::Returns(kI32).Params(kRef),
8117 {
8118 str_var,
8119 },
8120 decoder->position());
8121 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8122
8123 LiftoffRegister result_reg(kReturnRegister0);
8124 __ PushRegister(kI32, result_reg);
8125 }
8126
8127 void StringAsWtf8(FullDecoder* decoder, const Value& str, Value* result) {
8129 LiftoffRegList pinned;
8130
8131 LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned));
8132 MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type);
8133 VarState str_var(kRef, str_reg, 0);
8134
8135 CallBuiltin(Builtin::kWasmStringAsWtf8, MakeSig::Returns(kRef).Params(kRef),
8136 {
8137 str_var,
8138 },
8139 decoder->position());
8140 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8141
8142 LiftoffRegister result_reg(kReturnRegister0);
8143 __ PushRegister(kRef, result_reg);
8144 }
8145
8146 void StringViewWtf8Advance(FullDecoder* decoder, const Value& view,
8147 const Value& pos, const Value& bytes,
8148 Value* result) {
8150 LiftoffRegList pinned;
8151
8152 VarState& bytes_var = __ cache_state()->stack_state.end()[-1];
8153 VarState& pos_var = __ cache_state()->stack_state.end()[-2];
8154
8155 LiftoffRegister view_reg = pinned.set(
8156 __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
8157 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8158 VarState view_var(kRef, view_reg, 0);
8159
8160 CallBuiltin(Builtin::kWasmStringViewWtf8Advance,
8161 MakeSig::Returns(kI32).Params(kRef, kI32, kI32),
8162 {
8163 view_var,
8164 pos_var,
8165 bytes_var,
8166 },
8167 decoder->position());
8168 __ DropValues(3);
8169 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8170
8171 LiftoffRegister result_reg(kReturnRegister0);
8172 __ PushRegister(kI32, result_reg);
8173 }
8174
8175 void StringViewWtf8Encode(FullDecoder* decoder,
8176 const MemoryIndexImmediate& imm,
8177 const unibrow::Utf8Variant variant,
8178 const Value& view, const Value& addr,
8179 const Value& pos, const Value& bytes,
8180 Value* next_pos, Value* bytes_written) {
8182 LiftoffRegList pinned;
8183
8184 VarState& bytes_var = __ cache_state()->stack_state.end()[-1];
8185 VarState& pos_var = __ cache_state()->stack_state.end()[-2];
8186
8187 DCHECK(MatchingMemType(imm.memory, 2));
8188 VarState addr_var = IndexToVarStateSaturating(2, &pinned);
8189
8190 LiftoffRegister view_reg = pinned.set(
8191 __ LoadToRegister(__ cache_state()->stack_state.end()[-4], pinned));
8192 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8193 VarState view_var(kRef, view_reg, 0);
8194
8195 // TODO(jkummerow): Support Smi offsets when constructing {VarState}s
8196 // directly; avoid requesting a register.
8197 LiftoffRegister memory_reg =
8198 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
8199 LoadSmi(memory_reg, imm.index);
8200 VarState memory_var(kSmiKind, memory_reg, 0);
8201
8202 LiftoffRegister variant_reg =
8203 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
8204 LoadSmi(variant_reg, static_cast<int32_t>(variant));
8205 VarState variant_var(kSmiKind, variant_reg, 0);
8206
8207 CallBuiltin(
8208 Builtin::kWasmStringViewWtf8Encode,
8209 MakeSig::Returns(kI32, kI32)
8210 .Params(kIntPtrKind, kI32, kI32, kRef, kSmiKind, kSmiKind),
8211 {addr_var, pos_var, bytes_var, view_var, memory_var, variant_var},
8212 decoder->position());
8213 __ DropValues(4);
8214 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8215
8216 LiftoffRegister next_pos_reg(kReturnRegister0);
8217 __ PushRegister(kI32, next_pos_reg);
8218 LiftoffRegister bytes_written_reg(kReturnRegister1);
8219 __ PushRegister(kI32, bytes_written_reg);
8220 }
8221
8222 void StringViewWtf8Slice(FullDecoder* decoder, const Value& view,
8223 const Value& start, const Value& end,
8224 Value* result) {
8226 LiftoffRegList pinned;
8227
8228 VarState& end_var = __ cache_state()->stack_state.end()[-1];
8229 VarState& start_var = __ cache_state()->stack_state.end()[-2];
8230
8231 LiftoffRegister view_reg = pinned.set(
8232 __ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
8233 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8234 VarState view_var(kRef, view_reg, 0);
8235
8236 CallBuiltin(Builtin::kWasmStringViewWtf8Slice,
8237 MakeSig::Returns(kRef).Params(kRef, kI32, kI32),
8238 {
8239 view_var,
8240 start_var,
8241 end_var,
8242 },
8243 decoder->position());
8244 __ DropValues(3);
8245 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8246
8247 LiftoffRegister result_reg(kReturnRegister0);
8248 __ PushRegister(kRef, result_reg);
8249 }
8250
8251 void StringAsWtf16(FullDecoder* decoder, const Value& str, Value* result) {
8252 LiftoffRegList pinned;
8253
8254 LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned));
8255 MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type);
8256 VarState str_var(kRef, str_reg, 0);
8257
8258 CallBuiltin(Builtin::kWasmStringAsWtf16,
8259 MakeSig::Returns(kRef).Params(kRef),
8260 {
8261 str_var,
8262 },
8263 decoder->position());
8264 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8265
8266 LiftoffRegister result_reg(kReturnRegister0);
8267 __ PushRegister(kRef, result_reg);
8268 }
8269
8270 void StringViewWtf16GetCodeUnit(FullDecoder* decoder, const Value& view,
8271 const Value& pos, Value* result) {
8272 LiftoffRegList pinned;
8273 LiftoffRegister pos_reg = pinned.set(__ PopToRegister(pinned));
8274 LiftoffRegister view_reg = pinned.set(__ PopToRegister(pinned));
8275 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8276 VarState view_var(kRef, view_reg, 0);
8277 VarState pos_var(kI32, pos_reg, 0);
8278
8279 CallBuiltin(Builtin::kWasmStringViewWtf16GetCodeUnit,
8280 MakeSig::Returns(kI32).Params(kRef, kI32), {view_var, pos_var},
8281 decoder->position());
8282 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8283
8284 LiftoffRegister result_reg(kReturnRegister0);
8285 __ PushRegister(kI32, result_reg);
8286 }
8287
8288 void StringViewWtf16Encode(FullDecoder* decoder,
8289 const MemoryIndexImmediate& imm, const Value& view,
8290 const Value& offset, const Value& pos,
8291 const Value& codeunits, Value* result) {
8293 LiftoffRegList pinned;
8294
8295 VarState& codeunits_var = __ cache_state()->stack_state.end()[-1];
8296 VarState& pos_var = __ cache_state()->stack_state.end()[-2];
8297
8298 DCHECK(MatchingMemType(imm.memory, 2));
8299 VarState offset_var = IndexToVarStateSaturating(2, &pinned);
8300
8301 LiftoffRegister view_reg = pinned.set(
8302 __ LoadToRegister(__ cache_state()->stack_state.end()[-4], pinned));
8303 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8304 VarState view_var(kRef, view_reg, 0);
8305
8306 LiftoffRegister memory_reg =
8307 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
8308 LoadSmi(memory_reg, imm.index);
8309 VarState memory_var(kSmiKind, memory_reg, 0);
8310
8311 CallBuiltin(
8312 Builtin::kWasmStringViewWtf16Encode,
8313 MakeSig::Returns(kI32).Params(kIntPtrKind, kI32, kI32, kRef, kSmiKind),
8314 {offset_var, pos_var, codeunits_var, view_var, memory_var},
8315 decoder->position());
8316 __ DropValues(4);
8317 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8318
8319 LiftoffRegister result_reg(kReturnRegister0);
8320 __ PushRegister(kI32, result_reg);
8321 }
8322
8323 void StringViewWtf16Slice(FullDecoder* decoder, const Value& view,
8324 const Value& start, const Value& end,
8325 Value* result) {
8327 LiftoffRegList pinned;
8328 LiftoffRegister end_reg = pinned.set(__ PopToRegister(pinned));
8329 LiftoffRegister start_reg = pinned.set(__ PopToRegister(pinned));
8330 LiftoffRegister view_reg = pinned.set(__ PopToRegister(pinned));
8331 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8332 VarState view_var(kRef, view_reg, 0);
8333 VarState start_var(kI32, start_reg, 0);
8334 VarState end_var(kI32, end_reg, 0);
8335
8336 CallBuiltin(Builtin::kWasmStringViewWtf16Slice,
8337 MakeSig::Returns(kRef).Params(kRef, kI32, kI32),
8338 {
8339 view_var,
8340 start_var,
8341 end_var,
8342 },
8343 decoder->position());
8344 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8345
8346 LiftoffRegister result_reg(kReturnRegister0);
8347 __ PushRegister(kRef, result_reg);
8348 }
8349
8350 void StringAsIter(FullDecoder* decoder, const Value& str, Value* result) {
8351 LiftoffRegList pinned;
8352
8353 LiftoffRegister str_reg = pinned.set(__ PopToRegister(pinned));
8354 MaybeEmitNullCheck(decoder, str_reg.gp(), pinned, str.type);
8355 VarState str_var(kRef, str_reg, 0);
8356
8357 CallBuiltin(Builtin::kWasmStringAsIter, MakeSig::Returns(kRef).Params(kRef),
8358 {
8359 str_var,
8360 },
8361 decoder->position());
8362 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8363
8364 LiftoffRegister result_reg(kReturnRegister0);
8365 __ PushRegister(kRef, result_reg);
8366 }
8367
8368 void StringViewIterNext(FullDecoder* decoder, const Value& view,
8369 Value* result) {
8370 LiftoffRegList pinned;
8371
8372 LiftoffRegister view_reg = pinned.set(__ PopToRegister(pinned));
8373 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8374 VarState view_var(kRef, view_reg, 0);
8375
8376 CallBuiltin(Builtin::kWasmStringViewIterNext,
8377 MakeSig::Returns(kI32).Params(kRef),
8378 {
8379 view_var,
8380 },
8381 decoder->position());
8382 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8383
8384 LiftoffRegister result_reg(kReturnRegister0);
8385 __ PushRegister(kI32, result_reg);
8386 }
8387
8388 void StringViewIterAdvance(FullDecoder* decoder, const Value& view,
8389 const Value& codepoints, Value* result) {
8390 LiftoffRegList pinned;
8391
8392 VarState& codepoints_var = __ cache_state()->stack_state.end()[-1];
8393
8394 LiftoffRegister view_reg = pinned.set(
8395 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
8396 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8397 VarState view_var(kRef, view_reg, 0);
8398
8399 CallBuiltin(Builtin::kWasmStringViewIterAdvance,
8400 MakeSig::Returns(kI32).Params(kRef, kI32),
8401 {
8402 view_var,
8403 codepoints_var,
8404 },
8405 decoder->position());
8406 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8407
8408 LiftoffRegister result_reg(kReturnRegister0);
8409 __ DropValues(2);
8410 __ PushRegister(kI32, result_reg);
8411 }
8412
8413 void StringViewIterRewind(FullDecoder* decoder, const Value& view,
8414 const Value& codepoints, Value* result) {
8415 LiftoffRegList pinned;
8416
8417 VarState& codepoints_var = __ cache_state()->stack_state.end()[-1];
8418
8419 LiftoffRegister view_reg = pinned.set(
8420 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
8421 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8422 VarState view_var(kRef, view_reg, 0);
8423
8424 CallBuiltin(Builtin::kWasmStringViewIterRewind,
8425 MakeSig::Returns(kI32).Params(kRef, kI32),
8426 {
8427 view_var,
8428 codepoints_var,
8429 },
8430 decoder->position());
8431 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8432
8433 LiftoffRegister result_reg(kReturnRegister0);
8434 __ DropValues(2);
8435 __ PushRegister(kI32, result_reg);
8436 }
8437
8438 void StringViewIterSlice(FullDecoder* decoder, const Value& view,
8439 const Value& codepoints, Value* result) {
8441 LiftoffRegList pinned;
8442
8443 VarState& codepoints_var = __ cache_state()->stack_state.end()[-1];
8444
8445 LiftoffRegister view_reg = pinned.set(
8446 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
8447 MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
8448 VarState view_var(kRef, view_reg, 0);
8449
8450 CallBuiltin(Builtin::kWasmStringViewIterSlice,
8451 MakeSig::Returns(kRef).Params(kRef, kI32),
8452 {
8453 view_var,
8454 codepoints_var,
8455 },
8456 decoder->position());
8457 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8458
8459 LiftoffRegister result_reg(kReturnRegister0);
8460 __ DropValues(2);
8461 __ PushRegister(kRef, result_reg);
8462 }
8463
8464 void StringCompare(FullDecoder* decoder, const Value& lhs, const Value& rhs,
8465 Value* result) {
8467 LiftoffRegList pinned;
8468 LiftoffRegister rhs_reg = pinned.set(
8469 __ LoadToRegister(__ cache_state()->stack_state.end()[-1], pinned));
8470 MaybeEmitNullCheck(decoder, rhs_reg.gp(), pinned, rhs.type);
8471 VarState rhs_var(kRef, rhs_reg, 0);
8472
8473 LiftoffRegister lhs_reg = pinned.set(
8474 __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
8475 MaybeEmitNullCheck(decoder, lhs_reg.gp(), pinned, lhs.type);
8476 VarState lhs_var(kRef, lhs_reg, 0);
8477
8478 CallBuiltin(Builtin::kStringCompare,
8479 MakeSig::Returns(kSmiKind).Params(kRef, kRef),
8480 {lhs_var, rhs_var}, decoder->position());
8481 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8482
8483 LiftoffRegister result_reg(kReturnRegister0);
8484 __ DropValues(2);
8485 __ SmiToInt32(kReturnRegister0);
8486 __ PushRegister(kI32, result_reg);
8487 }
8488
8489 void StringFromCodePoint(FullDecoder* decoder, const Value& code_point,
8490 Value* result) {
8491 VarState& codepoint_var = __ cache_state()->stack_state.end()[-1];
8492
8493 CallBuiltin(Builtin::kWasmStringFromCodePoint,
8494 MakeSig::Returns(kRef).Params(kI32), {codepoint_var},
8495 decoder->position());
8496 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
8497
8498 LiftoffRegister result_reg(kReturnRegister0);
8499 __ DropValues(1);
8500 __ PushRegister(kRef, result_reg);
8501 }
8502
8503 void StringHash(FullDecoder* decoder, const Value& string, Value* result) {
8505 LiftoffRegList pinned;
8506 LiftoffRegister string_reg = pinned.set(
8507 __ LoadToRegister(__ cache_state()->stack_state.end()[-1], pinned));
8508 MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, string.type);
8509 VarState string_var(kRef, string_reg, 0);
8510
8511 CallBuiltin(Builtin::kWasmStringHash, MakeSig::Returns(kI32).Params(kRef),
8512 {string_var}, decoder->position());
8513
8514 LiftoffRegister result_reg(kReturnRegister0);
8515 __ DropValues(1);
8516 __ PushRegister(kI32, result_reg);
8517 }
8518
8519 void Forward(FullDecoder* decoder, const Value& from, Value* to) {
8520 // Nothing to do here.
8521 }
8522
8523 private:
8524 void CallDirect(FullDecoder* decoder, const CallFunctionImmediate& imm,
8525 const Value args[], Value returns[],
8526 CallJumpMode call_jump_mode) {
8527 MostlySmallValueKindSig sig(zone_, imm.sig);
8528 for (ValueKind ret : sig.returns()) {
8529 if (!CheckSupportedType(decoder, ret, "return")) return;
8530 }
8531
8532 bool needs_indirect_call = imm.index < env_->module->num_imported_functions;
8533 auto call_descriptor = compiler::GetWasmCallDescriptor(
8534 zone_, imm.sig,
8535 needs_indirect_call ? compiler::kWasmIndirectFunction
8537 call_descriptor = GetLoweredCallDescriptor(zone_, call_descriptor);
8538
8539 // One slot would be enough for call_direct, but would make index
8540 // computations much more complicated.
8541 size_t vector_slot = encountered_call_instructions_.size() * 2;
8542 if (v8_flags.wasm_inlining) {
8543 encountered_call_instructions_.push_back(imm.index);
8544 }
8545
8546 if (needs_indirect_call) {
8547 // A direct call to an imported function.
8549 LiftoffRegList pinned;
8550 Register implicit_arg =
8551 pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
8552 Register target = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
8553
8554 {
8555 SCOPED_CODE_COMMENT("Load ref and target for imported function");
8556 Register dispatch_table = target;
8557 LOAD_PROTECTED_PTR_INSTANCE_FIELD(dispatch_table,
8558 DispatchTableForImports, pinned);
8559 __ LoadProtectedPointer(
8560 implicit_arg, dispatch_table,
8563
8564 __ LoadCodePointer(
8565 target, dispatch_table,
8568 }
8569
8570 __ PrepareCall(&sig, call_descriptor, &target, implicit_arg);
8571 if (call_jump_mode == CallJumpMode::kTailCall) {
8572 __ PrepareTailCall(
8573 static_cast<int>(call_descriptor->ParameterSlotCount()),
8574 static_cast<int>(
8575 call_descriptor->GetStackParameterDelta(descriptor_)));
8576 __ TailCallIndirect(call_descriptor, target);
8577 } else {
8579 __ pc_offset(), SourcePosition(decoder->position()), true);
8580 __ CallIndirect(&sig, call_descriptor, target);
8581 FinishCall(decoder, &sig, call_descriptor);
8582 }
8583 } else {
8584 // Update call counts for inlining.
8585 if (v8_flags.wasm_inlining) {
8586 LiftoffRegister vector = __ GetUnusedRegister(kGpReg, {});
8588 kIntPtrKind);
8589 __ IncrementSmi(vector,
8591 static_cast<int>(vector_slot)));
8592 // Warning: {vector} may be clobbered by {IncrementSmi}!
8593 }
8594 // A direct call within this module just gets the current instance.
8595 __ PrepareCall(&sig, call_descriptor);
8596 // Just encode the function index. This will be patched at instantiation.
8597 Address addr = static_cast<Address>(imm.index);
8598 if (call_jump_mode == CallJumpMode::kTailCall) {
8599 DCHECK(descriptor_->CanTailCall(call_descriptor));
8600 __ PrepareTailCall(
8601 static_cast<int>(call_descriptor->ParameterSlotCount()),
8602 static_cast<int>(
8603 call_descriptor->GetStackParameterDelta(descriptor_)));
8604 __ TailCallNativeWasmCode(addr);
8605 } else {
8607 __ pc_offset(), SourcePosition(decoder->position()), true);
8608 __ CallNativeWasmCode(addr);
8609 FinishCall(decoder, &sig, call_descriptor);
8610 }
8611 }
8612 }
8613
8614 void CallIndirectImpl(FullDecoder* decoder, const CallIndirectImmediate& imm,
8615 CallJumpMode call_jump_mode) {
8616 MostlySmallValueKindSig sig(zone_, imm.sig);
8617 for (ValueKind ret : sig.returns()) {
8618 if (!CheckSupportedType(decoder, ret, "return")) return;
8619 }
8620 const WasmTable* table = imm.table_imm.table;
8621
8622 if (deopt_info_bytecode_offset_ == decoder->pc_offset() &&
8623 deopt_location_kind_ == LocationKindForDeopt::kEagerDeopt) {
8624 CHECK(v8_flags.wasm_deopt);
8625 EmitDeoptPoint(decoder);
8626 }
8627
8628 LiftoffRegList pinned;
8629 VarState index_slot = IndexToVarStateSaturating(0, &pinned);
8630
8631 const bool is_static_index = index_slot.is_const();
8632 Register index_reg =
8633 is_static_index
8634 ? no_reg
8635 : pinned.set(__ LoadToRegister(index_slot, pinned).gp());
8636
8637 static_assert(kV8MaxWasmTableSize <= kMaxUInt32);
8638 const uint32_t max_table_size = static_cast<uint32_t>(
8639 table->has_maximum_size
8640 ? std::min<uint64_t>(table->maximum_size, wasm::max_table_size())
8641 : wasm::max_table_size());
8642 const bool statically_oob =
8643 is_static_index &&
8644 static_cast<uint32_t>(index_slot.i32_const()) >= max_table_size;
8645
8646 TempRegisterScope temps;
8647 pinned |= temps.AddTempRegisters(3, kGpReg, &asm_, pinned);
8648
8649 ScopedTempRegister dispatch_table{temps, kGpReg};
8650 if (imm.table_imm.index == 0) {
8651 // Load the dispatch table directly.
8652 LOAD_PROTECTED_PTR_INSTANCE_FIELD(dispatch_table.gp_reg(), DispatchTable0,
8653 pinned);
8654 } else {
8655 // Load the dispatch table from the ProtectedFixedArray of all dispatch
8656 // tables.
8657 Register dispatch_tables = dispatch_table.gp_reg();
8658 LOAD_PROTECTED_PTR_INSTANCE_FIELD(dispatch_tables, DispatchTables,
8659 pinned);
8660 __ LoadProtectedPointer(dispatch_table.gp_reg(), dispatch_tables,
8661 ObjectAccess::ElementOffsetInProtectedFixedArray(
8662 imm.table_imm.index));
8663 }
8664
8665 {
8666 SCOPED_CODE_COMMENT("Check index is in-bounds");
8667 // Bounds check against the table size: Compare against the dispatch table
8668 // size, or a constant if the size is statically known.
8669 const bool needs_dynamic_size =
8670 !table->has_maximum_size ||
8671 table->maximum_size != table->initial_size;
8672
8673 ScopedTempRegister table_size{temps, kGpReg};
8674 OolTrapLabel out_of_bounds =
8675 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapTableOutOfBounds);
8676
8677 if (statically_oob) {
8678 __ emit_jump(out_of_bounds.label());
8679 // This case is unlikely to happen in production. Thus we just continue
8680 // generating code afterwards, to make sure that the stack is in a
8681 // consistent state for following instructions.
8682 } else if (needs_dynamic_size) {
8683 __ Load(table_size.reg(), dispatch_table.gp_reg(), no_reg,
8685 LoadType::kI32Load);
8686
8687 if (is_static_index) {
8688 __ emit_i32_cond_jumpi(kUnsignedLessThanEqual, out_of_bounds.label(),
8689 table_size.gp_reg(), index_slot.i32_const(),
8690 out_of_bounds.frozen());
8691 } else {
8692 ValueKind comparison_type = kI32;
8693 if (Is64() && table->is_table64()) {
8694 // {index_reg} is a uintptr, so do a ptrsize comparison.
8695 __ emit_u32_to_uintptr(table_size.gp_reg(), table_size.gp_reg());
8696 comparison_type = kIntPtrKind;
8697 }
8698 __ emit_cond_jump(kUnsignedLessThanEqual, out_of_bounds.label(),
8699 comparison_type, table_size.gp_reg(), index_reg,
8700 out_of_bounds.frozen());
8701 }
8702 } else {
8703 DCHECK_EQ(max_table_size, table->initial_size);
8704 if (is_static_index) {
8705 DCHECK_LT(index_slot.i32_const(), max_table_size);
8706 } else if (Is64() && table->is_table64()) {
8707 // On 32-bit, this is the same as below, so include the `Is64()` test
8708 // to statically tell the compiler to skip this branch.
8709 // Note: {max_table_size} will be sign-extended, which is fine because
8710 // the MSB is known to be 0 (asserted by the static_assert below).
8711 static_assert(kV8MaxWasmTableSize <= kMaxInt);
8712 __ emit_ptrsize_cond_jumpi(kUnsignedGreaterThanEqual,
8713 out_of_bounds.label(), index_reg,
8714 max_table_size, out_of_bounds.frozen());
8715 } else {
8716 __ emit_i32_cond_jumpi(kUnsignedGreaterThanEqual,
8717 out_of_bounds.label(), index_reg,
8718 max_table_size, out_of_bounds.frozen());
8719 }
8720 }
8721 }
8722
8723 // If the function index is dynamic, compute a pointer to the dispatch table
8724 // entry. Otherwise remember the static offset from the dispatch table to
8725 // add it to later loads from that table.
8726 ScopedTempRegister dispatch_table_base{std::move(dispatch_table)};
8727 int dispatch_table_offset = 0;
8728 if (is_static_index) {
8729 // Avoid potential integer overflow here by excluding too large
8730 // (statically OOB) indexes. This code is not reached for statically OOB
8731 // indexes anyway.
8732 dispatch_table_offset =
8733 statically_oob
8734 ? 0
8736 WasmDispatchTable::OffsetOf(index_slot.i32_const()));
8737 } else {
8738 // TODO(clemensb): Produce better code for this (via more specialized
8739 // platform-specific methods?).
8740
8741 Register entry_offset = index_reg;
8742 // After this computation we don't need the index register any more. If
8743 // there is no other user we can overwrite it.
8744 bool index_reg_still_used =
8745 __ cache_state() -> get_use_count(LiftoffRegister{index_reg}) > 1;
8746 if (index_reg_still_used) entry_offset = temps.Acquire(kGpReg).gp();
8747
8748 __ emit_u32_to_uintptr(entry_offset, index_reg);
8749 index_reg = no_reg;
8750 __ emit_ptrsize_muli(entry_offset, entry_offset,
8752 __ emit_ptrsize_add(dispatch_table_base.gp_reg(),
8753 dispatch_table_base.gp_reg(), entry_offset);
8754 if (index_reg_still_used) temps.Return(std::move(entry_offset));
8755 dispatch_table_offset =
8757 }
8758
8759 bool needs_type_check = !EquivalentTypes(
8760 table->type.AsNonNull(), ValueType::Ref(imm.sig_imm.heap_type()),
8761 decoder->module_, decoder->module_);
8762 bool needs_null_check = table->type.is_nullable();
8763
8764 // We do both the type check and the null check by checking the signature,
8765 // so this shares most code. For the null check we then only check if the
8766 // stored signature is != -1.
8767 if (needs_type_check || needs_null_check) {
8768 SCOPED_CODE_COMMENT(needs_type_check ? "Check signature"
8769 : "Check for null entry");
8770 ScopedTempRegister real_sig_id{temps, kGpReg};
8771
8772 // Load the signature from the dispatch table.
8773 __ Load(real_sig_id.reg(), dispatch_table_base.gp_reg(), no_reg,
8774 dispatch_table_offset + WasmDispatchTable::kSigBias,
8775 LoadType::kI32Load);
8776
8777 // Compare against expected signature.
8778 // Since Liftoff code is never serialized (hence not reused across
8779 // isolates / processes) the canonical signature ID is a static integer.
8780 CanonicalTypeIndex canonical_sig_id =
8781 decoder->module_->canonical_sig_id(imm.sig_imm.index);
8782 OolTrapLabel sig_mismatch =
8783 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapFuncSigMismatch);
8784 __ DropValues(1);
8785
8786 if (!needs_type_check) {
8787 DCHECK(needs_null_check);
8788 // Only check for -1 (nulled table entry).
8789 __ emit_i32_cond_jumpi(kEqual, sig_mismatch.label(),
8790 real_sig_id.gp_reg(), -1, sig_mismatch.frozen());
8791 } else if (!decoder->module_->type(imm.sig_imm.index).is_final) {
8792 Label success_label;
8793 __ emit_i32_cond_jumpi(kEqual, &success_label, real_sig_id.gp_reg(),
8794 canonical_sig_id.index, sig_mismatch.frozen());
8795 if (needs_null_check) {
8796 __ emit_i32_cond_jumpi(kEqual, sig_mismatch.label(),
8797 real_sig_id.gp_reg(), -1,
8798 sig_mismatch.frozen());
8799 }
8800 ScopedTempRegister real_rtt{temps, kGpReg};
8801 __ LoadFullPointer(
8802 real_rtt.gp_reg(), kRootRegister,
8803 IsolateData::root_slot_offset(RootIndex::kWasmCanonicalRtts));
8804 __ LoadTaggedPointer(
8805 real_rtt.gp_reg(), real_rtt.gp_reg(), real_sig_id.gp_reg(),
8807 nullptr, true);
8808 // real_sig_id is not used any more.
8809 real_sig_id.Reset();
8810 // Remove the weak reference tag.
8811 if constexpr (kSystemPointerSize == 4) {
8812 __ emit_i32_andi(real_rtt.gp_reg(), real_rtt.gp_reg(),
8813 static_cast<int32_t>(~kWeakHeapObjectMask));
8814 } else {
8815 __ emit_i64_andi(real_rtt.reg(), real_rtt.reg(),
8816 static_cast<int32_t>(~kWeakHeapObjectMask));
8817 }
8818 // Constant-time subtyping check: load exactly one candidate RTT from
8819 // the supertypes list.
8820 // Step 1: load the WasmTypeInfo.
8821 constexpr int kTypeInfoOffset = wasm::ObjectAccess::ToTagged(
8822 Map::kConstructorOrBackPointerOrNativeContextOffset);
8823 ScopedTempRegister type_info{std::move(real_rtt)};
8824 __ LoadTaggedPointer(type_info.gp_reg(), type_info.gp_reg(), no_reg,
8825 kTypeInfoOffset);
8826 // Step 2: check the list's length if needed.
8827 uint32_t rtt_depth =
8828 GetSubtypingDepth(decoder->module_, imm.sig_imm.index);
8829 if (rtt_depth >= kMinimumSupertypeArraySize) {
8830 ScopedTempRegister list_length{temps, kGpReg};
8831 int offset =
8832 ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesLengthOffset);
8833 __ LoadSmiAsInt32(list_length.reg(), type_info.gp_reg(), offset);
8834 __ emit_i32_cond_jumpi(kUnsignedLessThanEqual, sig_mismatch.label(),
8835 list_length.gp_reg(), rtt_depth,
8836 sig_mismatch.frozen());
8837 }
8838 // Step 3: load the candidate list slot, and compare it.
8839 ScopedTempRegister maybe_match{std::move(type_info)};
8840 __ LoadTaggedPointer(
8841 maybe_match.gp_reg(), maybe_match.gp_reg(), no_reg,
8842 ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset +
8843 rtt_depth * kTaggedSize));
8844 ScopedTempRegister formal_rtt{temps, kGpReg};
8845 // Instead of {pinned}, we use {kGpCacheRegList} as the list of pinned
8846 // registers, to prevent any attempt to cache the instance, which would
8847 // be incompatible with the {FREEZE_STATE} that is in effect here.
8848 LOAD_TAGGED_PTR_INSTANCE_FIELD(formal_rtt.gp_reg(), ManagedObjectMaps,
8850 __ LoadTaggedPointer(
8851 formal_rtt.gp_reg(), formal_rtt.gp_reg(), no_reg,
8853 imm.sig_imm.index.index));
8854 __ emit_cond_jump(kNotEqual, sig_mismatch.label(), kRef,
8855 formal_rtt.gp_reg(), maybe_match.gp_reg(),
8856 sig_mismatch.frozen());
8857
8858 __ bind(&success_label);
8859 } else {
8860 __ emit_i32_cond_jumpi(kNotEqual, sig_mismatch.label(),
8861 real_sig_id.gp_reg(), canonical_sig_id.index,
8862 sig_mismatch.frozen());
8863 }
8864 } else {
8865 __ DropValues(1);
8866 }
8867
8868 {
8869 SCOPED_CODE_COMMENT("Execute indirect call");
8870
8871 // The first parameter will be either a WasmTrustedInstanceData or a
8872 // WasmImportData.
8873 Register implicit_arg = temps.Acquire(kGpReg).gp();
8874 Register target = temps.Acquire(kGpReg).gp();
8875
8876 {
8877 SCOPED_CODE_COMMENT("Load implicit arg and target from dispatch table");
8878 __ LoadProtectedPointer(
8879 implicit_arg, dispatch_table_base.gp_reg(),
8880 dispatch_table_offset + WasmDispatchTable::kImplicitArgBias);
8881 __ LoadCodePointer(
8882 target, dispatch_table_base.gp_reg(),
8883 dispatch_table_offset + WasmDispatchTable::kTargetBias);
8884 }
8885
8886 if (v8_flags.wasm_inlining_call_indirect) {
8887 SCOPED_CODE_COMMENT("Feedback collection for speculative inlining");
8888
8889 ScopedTempRegister vector{std::move(dispatch_table_base)};
8891 kRef);
8892 VarState vector_var{kRef, vector.reg(), 0};
8893
8894 // A constant `uint32_t` is sufficient for the vector slot index.
8895 // The number of call instructions (and hence feedback vector slots) is
8896 // capped by the number of instructions, which is capped by the maximum
8897 // function body size.
8898 static_assert(kV8MaxWasmFunctionSize <
8899 std::numeric_limits<uint32_t>::max() / 2);
8900 uint32_t vector_slot =
8901 static_cast<uint32_t>(encountered_call_instructions_.size()) * 2;
8904 VarState index_var(kI32, vector_slot, 0);
8905
8906 // Thread the target and ref through the builtin call (i.e., pass them
8907 // as parameters and return them unchanged) as `CallBuiltin` otherwise
8908 // clobbers them. (The spilling code in `SpillAllRegisters` is only
8909 // aware of registers used on Liftoff's abstract value stack, not the
8910 // ones manually allocated above.)
8911 // TODO(335082212): We could avoid this and reduce the code size for
8912 // each call_indirect by moving the target and ref lookup into the
8913 // builtin as well.
8914 // However, then we would either (a) need to replicate the optimizations
8915 // above for static indices etc., which increases code duplication and
8916 // maintenance cost, or (b) regress performance even more than the
8917 // builtin call itself already does.
8918 // All in all, let's keep it simple at first, i.e., share the maximum
8919 // amount of code when inlining is enabled vs. not.
8920 VarState target_var(kI32, LiftoffRegister(target), 0);
8921 VarState implicit_arg_var(kRef, LiftoffRegister(implicit_arg), 0);
8922
8923 // CallIndirectIC(vector: FixedArray, vectorIndex: int32,
8924 // target: WasmCodePointer (== uint32),
8925 // implicitArg: WasmTrustedInstanceData|WasmImportData)
8926 // -> <target, implicit_arg>
8927 CallBuiltin(Builtin::kCallIndirectIC,
8928 MakeSig::Returns(kIntPtrKind, kIntPtrKind)
8929 .Params(kRef, kI32, kI32, kRef),
8930 {vector_var, index_var, target_var, implicit_arg_var},
8931 decoder->position());
8932 target = kReturnRegister0;
8933 implicit_arg = kReturnRegister1;
8934 }
8935
8936 auto call_descriptor = compiler::GetWasmCallDescriptor(
8938 call_descriptor = GetLoweredCallDescriptor(zone_, call_descriptor);
8939
8940 __ PrepareCall(&sig, call_descriptor, &target, implicit_arg);
8941 if (call_jump_mode == CallJumpMode::kTailCall) {
8942 __ PrepareTailCall(
8943 static_cast<int>(call_descriptor->ParameterSlotCount()),
8944 static_cast<int>(
8945 call_descriptor->GetStackParameterDelta(descriptor_)));
8946 __ TailCallIndirect(call_descriptor, target);
8947 } else {
8949 __ pc_offset(), SourcePosition(decoder->position()), true);
8950 __ CallIndirect(&sig, call_descriptor, target);
8951 FinishCall(decoder, &sig, call_descriptor);
8952 }
8953 }
8954 }
8955
8956 void StoreFrameDescriptionForDeopt(
8957 FullDecoder* decoder, uint32_t adapt_shadow_stack_pc_offset = 0) {
8958 DCHECK(v8_flags.wasm_deopt);
8960
8961 frame_description_ = std::make_unique<LiftoffFrameDescriptionForDeopt>(
8962 LiftoffFrameDescriptionForDeopt{
8963 decoder->pc_offset(), static_cast<uint32_t>(__ pc_offset()),
8964#ifdef V8_ENABLE_CET_SHADOW_STACK
8965 adapt_shadow_stack_pc_offset,
8966#endif // V8_ENABLE_CET_SHADOW_STACK
8967 std::vector<LiftoffVarState>(__ cache_state()->stack_state.begin(),
8968 __ cache_state()->stack_state.end()),
8969 __ cache_state()->cached_instance_data});
8970 }
8971
8972 void EmitDeoptPoint(FullDecoder* decoder) {
8973#if defined(DEBUG) and !defined(V8_TARGET_ARCH_ARM)
8974 // Liftoff may only use "allocatable registers" as defined by the
8975 // RegisterConfiguration. (The deoptimizer will not handle non-allocatable
8976 // registers).
8977 // Note that this DCHECK is skipped for arm 32 bit as its deoptimizer
8978 // decides to handle all available double / simd registers.
8979 const RegisterConfiguration* config = RegisterConfiguration::Default();
8981 config->num_allocatable_simd128_registers());
8983 const int* end = config->allocatable_simd128_codes() +
8984 config->num_allocatable_simd128_registers();
8985 DCHECK(std::find(config->allocatable_simd128_codes(), end, reg.code()) !=
8986 end);
8987 }
8988#endif
8989
8990 LiftoffAssembler::CacheState initial_state(zone_);
8991 initial_state.Split(*__ cache_state());
8992 // TODO(mliedtke): The deopt point should be in out-of-line-code.
8993 Label deopt_point;
8994 Label callref;
8995 __ emit_jump(&callref);
8996 __ bind(&deopt_point);
8997 uint32_t adapt_shadow_stack_pc_offset = __ pc_offset();
8998#ifdef V8_ENABLE_CET_SHADOW_STACK
8999 if (v8_flags.cet_compatible) {
9000 __ CallBuiltin(Builtin::kAdaptShadowStackForDeopt);
9001 }
9002#endif // V8_ENABLE_CET_SHADOW_STACK
9003 StoreFrameDescriptionForDeopt(decoder, adapt_shadow_stack_pc_offset);
9004 CallBuiltin(Builtin::kWasmLiftoffDeoptFinish, MakeSig(), {},
9006 __ MergeStackWith(initial_state, 0, LiftoffAssembler::kForwardJump);
9007 __ cache_state() -> Steal(initial_state);
9008 __ bind(&callref);
9009 }
9010
9011 void CallRefImpl(FullDecoder* decoder, ValueType func_ref_type,
9012 const FunctionSig* type_sig, CallJumpMode call_jump_mode) {
9013 MostlySmallValueKindSig sig(zone_, type_sig);
9014 for (ValueKind ret : sig.returns()) {
9015 if (!CheckSupportedType(decoder, ret, "return")) return;
9016 }
9017 compiler::CallDescriptor* call_descriptor = compiler::GetWasmCallDescriptor(
9019 call_descriptor = GetLoweredCallDescriptor(zone_, call_descriptor);
9020
9021 Register target_reg = no_reg;
9022 Register implicit_arg_reg = no_reg;
9023
9024 if (v8_flags.wasm_inlining) {
9025 if (deopt_info_bytecode_offset_ == decoder->pc_offset() &&
9027 CHECK(v8_flags.wasm_deopt);
9028 EmitDeoptPoint(decoder);
9029 }
9030 LiftoffRegList pinned;
9031 LiftoffRegister func_ref = pinned.set(__ PopToRegister(pinned));
9032 LiftoffRegister vector = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
9033 MaybeEmitNullCheck(decoder, func_ref.gp(), pinned, func_ref_type);
9034 VarState func_ref_var(kRef, func_ref, 0);
9035
9037 VarState vector_var{kRef, vector, 0};
9038 // A constant `uint32_t` is sufficient for the vector slot index.
9039 // The number of call instructions (and hence feedback vector slots) is
9040 // capped by the number of instructions, which is capped by the maximum
9041 // function body size.
9042 static_assert(kV8MaxWasmFunctionSize <
9043 std::numeric_limits<uint32_t>::max() / 2);
9044 uint32_t vector_slot =
9045 static_cast<uint32_t>(encountered_call_instructions_.size()) * 2;
9047 VarState index_var(kI32, vector_slot, 0);
9048
9049 // CallRefIC(vector: FixedArray, vectorIndex: int32,
9050 // funcref: WasmFuncRef) -> <target, implicit_arg>
9051 CallBuiltin(
9052 Builtin::kCallRefIC,
9053 MakeSig::Returns(kIntPtrKind, kIntPtrKind).Params(kRef, kI32, kRef),
9054 {vector_var, index_var, func_ref_var}, decoder->position());
9055 target_reg = LiftoffRegister(kReturnRegister0).gp();
9056 implicit_arg_reg = kReturnRegister1;
9057 } else { // v8_flags.wasm_inlining
9058 // Non-feedback-collecting version.
9059 // Executing a write barrier needs temp registers; doing this on a
9060 // conditional branch confuses the LiftoffAssembler's register management.
9061 // Spill everything up front to work around that.
9062 __ SpillAllRegisters();
9063
9064 LiftoffRegList pinned;
9065 Register func_ref = pinned.set(__ PopToModifiableRegister(pinned)).gp();
9066 MaybeEmitNullCheck(decoder, func_ref, pinned, func_ref_type);
9067 implicit_arg_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
9068 target_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)).gp();
9069
9070 // Load the WasmInternalFunction from the WasmFuncRef.
9071 Register internal_function = func_ref;
9072 __ LoadTrustedPointer(
9073 internal_function, func_ref,
9074 ObjectAccess::ToTagged(WasmFuncRef::kTrustedInternalOffset),
9075 kWasmInternalFunctionIndirectPointerTag);
9076
9077 // Load the implicit argument (WasmTrustedInstanceData or WasmImportData)
9078 // and target.
9079 __ LoadProtectedPointer(
9080 implicit_arg_reg, internal_function,
9082 WasmInternalFunction::kProtectedImplicitArgOffset));
9083
9084 __ LoadFullPointer(target_reg, internal_function,
9086 WasmInternalFunction::kRawCallTargetOffset));
9087
9088 // Now the call target is in {target_reg} and the first parameter
9089 // (WasmTrustedInstanceData or WasmImportData) is in
9090 // {implicit_arg_reg}.
9091 } // v8_flags.wasm_inlining
9092
9093 __ PrepareCall(&sig, call_descriptor, &target_reg, implicit_arg_reg);
9094 if (call_jump_mode == CallJumpMode::kTailCall) {
9095 __ PrepareTailCall(
9096 static_cast<int>(call_descriptor->ParameterSlotCount()),
9097 static_cast<int>(
9098 call_descriptor->GetStackParameterDelta(descriptor_)));
9099 __ TailCallIndirect(call_descriptor, target_reg);
9100 } else {
9102 __ pc_offset(), SourcePosition(decoder->position()), true);
9103 __ CallIndirect(&sig, call_descriptor, target_reg);
9104 FinishCall(decoder, &sig, call_descriptor);
9105 }
9106 }
9107
9108 void LoadNullValue(Register null, ValueType type) {
9109 __ LoadFullPointer(
9111 type.use_wasm_null()
9112 ? IsolateData::root_slot_offset(RootIndex::kWasmNull)
9113 : IsolateData::root_slot_offset(RootIndex::kNullValue));
9114 }
9115
9116 // Stores the null value representation in the passed register.
9117 // If pointer compression is active, only the compressed tagged pointer
9118 // will be stored. Any operations with this register therefore must
9119 // not compare this against 64 bits using quadword instructions.
9120 void LoadNullValueForCompare(Register null, LiftoffRegList pinned,
9121 ValueType type) {
9122#if V8_STATIC_ROOTS_BOOL
9123 uint32_t value = type.use_wasm_null() ? StaticReadOnlyRoot::kWasmNull
9124 : StaticReadOnlyRoot::kNullValue;
9125 __ LoadConstant(LiftoffRegister(null),
9126 WasmValue(static_cast<uint32_t>(value)));
9127#else
9128 LoadNullValue(null, type);
9129#endif
9130 }
9131
9132 void LoadExceptionSymbol(Register dst, LiftoffRegList pinned,
9133 RootIndex root_index) {
9134 __ LoadFullPointer(dst, kRootRegister,
9135 IsolateData::root_slot_offset(root_index));
9136 }
9137
9138 void MaybeEmitNullCheck(FullDecoder* decoder, Register object,
9139 LiftoffRegList pinned, ValueType type) {
9140 if (v8_flags.experimental_wasm_skip_null_checks || !type.is_nullable()) {
9141 return;
9142 }
9143 LiftoffRegister null = __ GetUnusedRegister(kGpReg, pinned);
9144 LoadNullValueForCompare(null.gp(), pinned, type);
9145 OolTrapLabel trap =
9146 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapNullDereference);
9147 __ emit_cond_jump(kEqual, trap.label(), kRefNull, object, null.gp(),
9148 trap.frozen());
9149 }
9150
9151 void BoundsCheckArray(FullDecoder* decoder, bool implicit_null_check,
9152 LiftoffRegister array, LiftoffRegister index,
9153 LiftoffRegList pinned) {
9154 if (V8_UNLIKELY(v8_flags.experimental_wasm_skip_bounds_checks)) return;
9155 LiftoffRegister length = __ GetUnusedRegister(kGpReg, pinned);
9156 constexpr int kLengthOffset =
9157 wasm::ObjectAccess::ToTagged(WasmArray::kLengthOffset);
9158 uint32_t protected_instruction_pc = 0;
9159 __ Load(length, array.gp(), no_reg, kLengthOffset, LoadType::kI32Load,
9160 implicit_null_check ? &protected_instruction_pc : nullptr);
9161 if (implicit_null_check) {
9162 RegisterProtectedInstruction(decoder, protected_instruction_pc);
9163 }
9164 OolTrapLabel trap =
9165 AddOutOfLineTrap(decoder, Builtin::kThrowWasmTrapArrayOutOfBounds);
9166 __ emit_cond_jump(kUnsignedGreaterThanEqual, trap.label(), kI32, index.gp(),
9167 length.gp(), trap.frozen());
9168 }
9169
9170 int StructFieldOffset(const StructType* struct_type, int field_index) {
9171 return wasm::ObjectAccess::ToTagged(WasmStruct::kHeaderSize +
9172 struct_type->field_offset(field_index));
9173 }
9174
9175 std::pair<bool, bool> null_checks_for_struct_op(ValueType struct_type,
9176 int field_index) {
9177 bool explicit_null_check =
9178 struct_type.is_nullable() &&
9181 bool implicit_null_check =
9182 struct_type.is_nullable() && !explicit_null_check;
9183 return {explicit_null_check, implicit_null_check};
9184 }
9185
9186 void LoadObjectField(FullDecoder* decoder, LiftoffRegister dst, Register src,
9187 Register offset_reg, int offset, ValueKind kind,
9188 bool is_signed, bool trapping, LiftoffRegList pinned) {
9189 uint32_t protected_load_pc = 0;
9190 if (is_reference(kind)) {
9191 __ LoadTaggedPointer(dst.gp(), src, offset_reg, offset,
9192 trapping ? &protected_load_pc : nullptr);
9193 } else {
9194 // Primitive kind.
9195 LoadType load_type = LoadType::ForValueKind(kind, is_signed);
9196 __ Load(dst, src, offset_reg, offset, load_type,
9197 trapping ? &protected_load_pc : nullptr);
9198 }
9199 if (trapping) RegisterProtectedInstruction(decoder, protected_load_pc);
9200 }
9201
9202 void StoreObjectField(FullDecoder* decoder, Register obj, Register offset_reg,
9203 int offset, LiftoffRegister value, bool trapping,
9204 LiftoffRegList pinned, ValueKind kind,
9205 LiftoffAssembler::SkipWriteBarrier skip_write_barrier =
9207 uint32_t protected_load_pc = 0;
9208 if (is_reference(kind)) {
9209 __ StoreTaggedPointer(obj, offset_reg, offset, value.gp(), pinned,
9210 trapping ? &protected_load_pc : nullptr,
9211 skip_write_barrier);
9212 } else {
9213 // Primitive kind.
9214 StoreType store_type = StoreType::ForValueKind(kind);
9215 __ Store(obj, offset_reg, offset, value, store_type, pinned,
9216 trapping ? &protected_load_pc : nullptr);
9217 }
9218 if (trapping) RegisterProtectedInstruction(decoder, protected_load_pc);
9219 }
9220
9221 void SetDefaultValue(LiftoffRegister reg, ValueType type) {
9222 DCHECK(type.is_defaultable());
9223 switch (type.kind()) {
9224 case kI8:
9225 case kI16:
9226 case kI32:
9227 return __ LoadConstant(reg, WasmValue(int32_t{0}));
9228 case kI64:
9229 return __ LoadConstant(reg, WasmValue(int64_t{0}));
9230 case kF16:
9231 case kF32:
9232 return __ LoadConstant(reg, WasmValue(float{0.0}));
9233 case kF64:
9234 return __ LoadConstant(reg, WasmValue(double{0.0}));
9235 case kS128:
9237 return __ emit_s128_xor(reg, reg, reg);
9238 case kRefNull:
9239 return LoadNullValue(reg.gp(), type);
9240 case kVoid:
9241 case kTop:
9242 case kBottom:
9243 case kRef:
9244 UNREACHABLE();
9245 }
9246 }
9247
9248 void MaybeOSR() {
9250 __ MaybeOSR();
9251 }
9252 }
9253
9254 void FinishCall(FullDecoder* decoder, ValueKindSig* sig,
9255 compiler::CallDescriptor* call_descriptor) {
9256 DefineSafepoint();
9257 RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
9258
9259 if (deopt_info_bytecode_offset_ == decoder->pc_offset() &&
9261 CHECK(v8_flags.wasm_deopt);
9262 uint32_t adapt_shadow_stack_pc_offset = 0;
9263#ifdef V8_ENABLE_CET_SHADOW_STACK
9264 if (v8_flags.cet_compatible) {
9265 SCOPED_CODE_COMMENT("deopt entry for kAdaptShadowStackForDeopt");
9266 // AdaptShadowStackForDeopt is be called to build shadow stack after
9267 // deoptimization. Deoptimizer will directly jump to
9268 // `call AdaptShadowStackForDeopt`. But, in any other case, it should be
9269 // ignored.
9270 Label deopt_point;
9271 __ emit_jump(&deopt_point);
9272 adapt_shadow_stack_pc_offset = __ pc_offset();
9273 __ CallBuiltin(Builtin::kAdaptShadowStackForDeopt);
9274 __ bind(&deopt_point);
9275 }
9276#endif // V8_ENABLE_CET_SHADOW_STACK
9277 StoreFrameDescriptionForDeopt(decoder, adapt_shadow_stack_pc_offset);
9278 }
9279
9280 int pc_offset = __ pc_offset();
9281 MaybeOSR();
9282 EmitLandingPad(decoder, pc_offset);
9283 __ FinishCall(sig, call_descriptor);
9284 }
9285
9286 void CheckNan(LiftoffRegister src, LiftoffRegList pinned, ValueKind kind) {
9287 DCHECK(kind == ValueKind::kF32 || kind == ValueKind::kF64);
9288 auto nondeterminism_addr = __ GetUnusedRegister(kGpReg, pinned);
9289 __ LoadConstant(nondeterminism_addr,
9291 __ emit_store_nonzero_if_nan(nondeterminism_addr.gp(), src.fp(), kind);
9292 }
9293
9294 void CheckS128Nan(LiftoffRegister dst, LiftoffRegList pinned,
9295 ValueKind lane_kind) {
9297 LiftoffRegister tmp_gp = pinned.set(__ GetUnusedRegister(kGpReg, pinned));
9298 LiftoffRegister tmp_s128 = pinned.set(__ GetUnusedRegister(rc, pinned));
9299 LiftoffRegister nondeterminism_addr =
9300 pinned.set(__ GetUnusedRegister(kGpReg, pinned));
9301 __ LoadConstant(nondeterminism_addr,
9303 __ emit_s128_store_nonzero_if_nan(nondeterminism_addr.gp(), dst,
9304 tmp_gp.gp(), tmp_s128, lane_kind);
9305 }
9306
9307 void ArrayFillImpl(FullDecoder* decoder, LiftoffRegList pinned,
9308 LiftoffRegister obj, LiftoffRegister index,
9309 LiftoffRegister value, LiftoffRegister length,
9310 ValueKind elem_kind,
9311 LiftoffAssembler::SkipWriteBarrier skip_write_barrier) {
9312 // initial_offset = WasmArray::kHeaderSize + index * elem_size.
9313 LiftoffRegister offset = index;
9314 if (value_kind_size_log2(elem_kind) != 0) {
9315 __ emit_i32_shli(offset.gp(), index.gp(),
9316 value_kind_size_log2(elem_kind));
9317 }
9318 __ emit_i32_addi(offset.gp(), offset.gp(),
9319 wasm::ObjectAccess::ToTagged(WasmArray::kHeaderSize));
9320
9321 // end_offset = initial_offset + length * elem_size.
9322 LiftoffRegister end_offset = length;
9323 if (value_kind_size_log2(elem_kind) != 0) {
9324 __ emit_i32_shli(end_offset.gp(), length.gp(),
9325 value_kind_size_log2(elem_kind));
9326 }
9327 __ emit_i32_add(end_offset.gp(), end_offset.gp(), offset.gp());
9328
9329 FREEZE_STATE(frozen_for_conditional_jumps);
9330 Label loop, done;
9331 __ bind(&loop);
9332 __ emit_cond_jump(kUnsignedGreaterThanEqual, &done, kI32, offset.gp(),
9333 end_offset.gp(), frozen_for_conditional_jumps);
9334 StoreObjectField(decoder, obj.gp(), offset.gp(), 0, value, false, pinned,
9335 elem_kind, skip_write_barrier);
9336 __ emit_i32_addi(offset.gp(), offset.gp(), value_kind_size(elem_kind));
9337 __ emit_jump(&loop);
9338
9339 __ bind(&done);
9340 }
9341
9342 void RegisterProtectedInstruction(FullDecoder* decoder,
9343 uint32_t protected_instruction_pc) {
9344 protected_instructions_.emplace_back(
9345 trap_handler::ProtectedInstructionData{protected_instruction_pc});
9347 protected_instruction_pc, SourcePosition(decoder->position()), true);
9348 if (for_debugging_) {
9349 DefineSafepoint(protected_instruction_pc);
9350 }
9351 }
9352
9353 bool has_outstanding_op() const {
9355 }
9356
9357 bool test_and_reset_outstanding_op(WasmOpcode opcode) {
9358 DCHECK_NE(kNoOutstandingOp, opcode);
9359 if (outstanding_op_ != opcode) return false;
9361 return true;
9362 }
9363
9364 void TraceCacheState(FullDecoder* decoder) const {
9365 if (!v8_flags.trace_liftoff) return;
9366 StdoutStream os;
9367 for (int control_depth = decoder->control_depth() - 1; control_depth >= -1;
9368 --control_depth) {
9369 auto* cache_state =
9370 control_depth == -1 ? __ cache_state()
9371 : &decoder->control_at(control_depth)
9372 ->label_state;
9373 os << PrintCollection(cache_state->stack_state);
9374 if (control_depth != -1) PrintF("; ");
9375 }
9376 os << "\n";
9377 }
9378
9379 void DefineSafepoint(int pc_offset = 0) {
9380 if (pc_offset == 0) pc_offset = __ pc_offset_for_safepoint();
9381 if (pc_offset == last_safepoint_offset_) return;
9383 auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_, pc_offset);
9384 __ cache_state()->DefineSafepoint(safepoint);
9385 }
9386
9387 void DefineSafepointWithCalleeSavedRegisters() {
9388 int pc_offset = __ pc_offset_for_safepoint();
9389 if (pc_offset == last_safepoint_offset_) return;
9391 auto safepoint = safepoint_table_builder_.DefineSafepoint(&asm_, pc_offset);
9392 __ cache_state()->DefineSafepointWithCalleeSavedRegisters(safepoint);
9393 }
9394
9395 // Return a register holding the instance, populating the "cached instance"
9396 // register if possible. If no free register is available, the cache is not
9397 // set and we use {fallback} instead. This can be freely overwritten by the
9398 // caller then.
9399 V8_INLINE Register LoadInstanceIntoRegister(LiftoffRegList pinned,
9400 Register fallback) {
9401 Register instance = __ cache_state() -> cached_instance_data;
9402 if (V8_UNLIKELY(instance == no_reg)) {
9403 instance = LoadInstanceIntoRegister_Slow(pinned, fallback);
9404 }
9405 return instance;
9406 }
9407
9409 LoadInstanceIntoRegister_Slow(LiftoffRegList pinned, Register fallback) {
9410 DCHECK_EQ(no_reg, __ cache_state()->cached_instance_data);
9411 SCOPED_CODE_COMMENT("load instance");
9412 Register instance = __ cache_state()->TrySetCachedInstanceRegister(
9413 pinned | LiftoffRegList{fallback});
9414 if (instance == no_reg) instance = fallback;
9415 __ LoadInstanceDataFromFrame(instance);
9416 return instance;
9417 }
9418
9419 static constexpr WasmOpcode kNoOutstandingOp = kExprUnreachable;
9420 static constexpr base::EnumSet<ValueKind> kUnconditionallySupported{
9421 // MVP:
9422 kI32, kI64, kF32, kF64,
9423 // Extern ref:
9424 kRef, kRefNull, kI8, kI16};
9425
9426 LiftoffAssembler asm_;
9427
9428 // Used for merging code generation of subsequent operations (via look-ahead).
9429 // Set by the first opcode, reset by the second.
9431
9432 // {supported_types_} is updated in {MaybeBailoutForUnsupportedType}.
9434 compiler::CallDescriptor* const descriptor_;
9435 CompilationEnv* const env_;
9436 DebugSideTableBuilder* const debug_sidetable_builder_;
9437 base::OwnedVector<ValueType> stack_value_types_for_debugging_;
9440 const int func_index_;
9441 ZoneVector<OutOfLineCode> out_of_line_code_;
9442 SourcePositionTableBuilder source_position_table_builder_;
9443 ZoneVector<trap_handler::ProtectedInstructionData> protected_instructions_;
9444 // Zone used to store information during compilation. The result will be
9445 // stored independently, such that this zone can die together with the
9446 // LiftoffCompiler after compilation.
9448 SafepointTableBuilder safepoint_table_builder_;
9449 // The pc offset of the instructions to reserve the stack frame. Needed to
9450 // patch the actually needed stack size in the end.
9452 // For emitting breakpoint, we store a pointer to the position of the next
9453 // breakpoint, and a pointer after the list of breakpoints as end marker.
9454 // A single breakpoint at offset 0 indicates that we should prepare the
9455 // function for stepping by flooding it with breakpoints.
9456 const int* next_breakpoint_ptr_ = nullptr;
9457 const int* next_breakpoint_end_ = nullptr;
9458
9459 // Introduce a dead breakpoint to ensure that the calculation of the return
9460 // address in OSR is correct.
9462
9463 // Remember whether the did function-entry break checks (for "hook on function
9464 // call" and "break on entry" a.k.a. instrumentation breakpoint). This happens
9465 // at the first breakable opcode in the function (if compiling for debugging).
9467
9468 struct HandlerInfo {
9469 MovableLabel handler;
9471 };
9472
9473 ZoneVector<HandlerInfo> handlers_;
9475
9476 // Current number of exception refs on the stack.
9478
9479 // The pc_offset of the last defined safepoint. -1 if no safepoint has been
9480 // defined yet.
9482
9483 // Updated during compilation on every "call", "call_indirect", and "call_ref"
9484 // instruction.
9485 // Holds the call target, or for "call_indirect" and "call_ref" the sentinels
9486 // {FunctionTypeFeedback::kCallIndirect} / {FunctionTypeFeedback::kCallRef}.
9487 // After compilation, this is transferred into {WasmModule::type_feedback}.
9488 std::vector<uint32_t> encountered_call_instructions_;
9489
9490 // Pointer to information passed from the fuzzer. The pointer will be
9491 // embedded in generated code, which will update the value at runtime.
9492 int32_t* const max_steps_;
9493 // Whether to track nondeterminism at runtime; used by differential fuzzers.
9495
9496 // Information for deopt'ed code.
9499
9500 std::unique_ptr<LiftoffFrameDescriptionForDeopt> frame_description_;
9501
9506
9507 DISALLOW_IMPLICIT_CONSTRUCTORS(LiftoffCompiler);
9508};
9509
9510// static
9511constexpr WasmOpcode LiftoffCompiler::kNoOutstandingOp;
9512// static
9513constexpr base::EnumSet<ValueKind> LiftoffCompiler::kUnconditionallySupported;
9514
9515std::unique_ptr<AssemblerBuffer> NewLiftoffAssemblerBuffer(int func_body_size) {
9516 size_t code_size_estimate =
9518 // Allocate the initial buffer a bit bigger to avoid reallocation during code
9519 // generation. Overflows when casting to int are fine, as we will allocate at
9520 // least {AssemblerBase::kMinimalBufferSize} anyway, so in the worst case we
9521 // have to grow more often.
9522 int initial_buffer_size = static_cast<int>(128 + code_size_estimate * 4 / 3);
9523
9524 return NewAssemblerBuffer(initial_buffer_size);
9525}
9526
9527} // namespace
9528
9530 CompilationEnv* env, const FunctionBody& func_body,
9531 const LiftoffOptions& compiler_options) {
9532 DCHECK(compiler_options.is_initialized());
9533 // Liftoff does not validate the code, so that should have run before.
9534 DCHECK(env->module->function_was_validated(compiler_options.func_index));
9535 base::TimeTicks start_time;
9536 if (V8_UNLIKELY(v8_flags.trace_wasm_compilation_times)) {
9537 start_time = base::TimeTicks::Now();
9538 }
9539 int func_body_size = static_cast<int>(func_body.end - func_body.start);
9540 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
9541 "wasm.CompileBaseline", "funcIndex", compiler_options.func_index,
9542 "bodySize", func_body_size);
9543
9544 Zone zone(GetWasmEngine()->allocator(), "LiftoffCompilationZone");
9545 auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, func_body.sig);
9546
9547 std::unique_ptr<DebugSideTableBuilder> debug_sidetable_builder;
9548 if (compiler_options.debug_sidetable) {
9549 debug_sidetable_builder = std::make_unique<DebugSideTableBuilder>();
9550 }
9551 DCHECK_IMPLIES(compiler_options.max_steps,
9552 compiler_options.for_debugging == kForDebugging);
9553 WasmDetectedFeatures unused_detected_features;
9554
9556 &zone, env->module, env->enabled_features,
9557 compiler_options.detected_features ? compiler_options.detected_features
9558 : &unused_detected_features,
9559 func_body, call_descriptor, env, &zone,
9560 NewLiftoffAssemblerBuffer(func_body_size), debug_sidetable_builder.get(),
9561 compiler_options);
9562 decoder.Decode();
9563 LiftoffCompiler* compiler = &decoder.interface();
9564 if (decoder.failed()) compiler->OnFirstError(&decoder);
9565
9566 if (auto* counters = compiler_options.counters) {
9567 // Check that the histogram for the bailout reasons has the correct size.
9568 DCHECK_EQ(0, counters->liftoff_bailout_reasons()->min());
9570 counters->liftoff_bailout_reasons()->max());
9572 counters->liftoff_bailout_reasons()->num_buckets());
9573 // Register the bailout reason (can also be {kSuccess}).
9574 counters->liftoff_bailout_reasons()->AddSample(
9575 static_cast<int>(compiler->bailout_reason()));
9576 }
9577
9578 if (compiler->did_bailout()) return WasmCompilationResult{};
9579
9581 compiler->GetCode(&result.code_desc);
9582 result.instr_buffer = compiler->ReleaseBuffer();
9583 result.source_positions = compiler->GetSourcePositionTable();
9584 result.protected_instructions_data = compiler->GetProtectedInstructionsData();
9585 result.frame_slot_count = compiler->GetTotalFrameSlotCountForGC();
9586 result.ool_spill_count = compiler->OolSpillCount();
9587 auto* lowered_call_desc = GetLoweredCallDescriptor(&zone, call_descriptor);
9588 result.tagged_parameter_slots = lowered_call_desc->GetTaggedParameterSlots();
9589 result.func_index = compiler_options.func_index;
9590 result.result_tier = ExecutionTier::kLiftoff;
9591 result.for_debugging = compiler_options.for_debugging;
9592 result.frame_has_feedback_slot = v8_flags.wasm_inlining;
9593 result.liftoff_frame_descriptions = compiler->ReleaseFrameDescriptions();
9594 if (auto* debug_sidetable = compiler_options.debug_sidetable) {
9595 *debug_sidetable = debug_sidetable_builder->GenerateDebugSideTable();
9596 }
9597
9598 if (V8_UNLIKELY(v8_flags.trace_wasm_compilation_times)) {
9599 base::TimeDelta time = base::TimeTicks::Now() - start_time;
9600 int codesize = result.code_desc.body_size();
9601 StdoutStream{} << "Compiled function "
9602 << reinterpret_cast<const void*>(env->module) << "#"
9603 << compiler_options.func_index << " using Liftoff, took "
9604 << time.InMilliseconds() << " ms and "
9605 << zone.allocation_size() << " bytes; bodysize "
9606 << func_body_size << " codesize " << codesize << std::endl;
9607 }
9608
9609 DCHECK(result.succeeded());
9610
9611 return result;
9612}
9613
9614std::unique_ptr<DebugSideTable> GenerateLiftoffDebugSideTable(
9615 const WasmCode* code) {
9616 auto* native_module = code->native_module();
9617 auto* function = &native_module->module()->functions[code->index()];
9618 ModuleWireBytes wire_bytes{native_module->wire_bytes()};
9619 base::Vector<const uint8_t> function_bytes =
9620 wire_bytes.GetFunctionBytes(function);
9621 CompilationEnv env = CompilationEnv::ForModule(native_module);
9622 bool is_shared = native_module->module()->type(function->sig_index).is_shared;
9623 FunctionBody func_body{function->sig, 0, function_bytes.begin(),
9624 function_bytes.end(), is_shared};
9625
9626 Zone zone(GetWasmEngine()->allocator(), "LiftoffDebugSideTableZone");
9627 auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, function->sig);
9628 DebugSideTableBuilder debug_sidetable_builder;
9629 WasmDetectedFeatures detected;
9630 constexpr int kSteppingBreakpoints[] = {0};
9631 DCHECK(code->for_debugging() == kForDebugging ||
9632 code->for_debugging() == kForStepping);
9633 base::Vector<const int> breakpoints =
9634 code->for_debugging() == kForStepping
9635 ? base::ArrayVector(kSteppingBreakpoints)
9638 &zone, native_module->module(), env.enabled_features, &detected,
9639 func_body, call_descriptor, &env, &zone,
9640 NewAssemblerBuffer(AssemblerBase::kDefaultBufferSize),
9641 &debug_sidetable_builder,
9643 .set_func_index(code->index())
9644 .set_for_debugging(code->for_debugging())
9645 .set_breakpoints(breakpoints));
9646 decoder.Decode();
9647 DCHECK(decoder.ok());
9648 DCHECK(!decoder.interface().did_bailout());
9649 return debug_sidetable_builder.GenerateDebugSideTable();
9650}
9651
9652} // namespace v8::internal::wasm
friend Zone
Definition asm-types.cc:195
#define T
Register * reg_
int16_t parameter_count
Definition builtins.cc:67
Builtins::Kind kind
Definition builtins.cc:40
#define SLOW_DCHECK(condition)
Definition checks.h:21
SourcePosition pos
static OwnedVector< T > NewForOverwrite(size_t size)
Definition vector.h:294
constexpr T * begin() const
Definition vector.h:96
static Vector< T > cast(Vector< S > input)
Definition vector.h:157
constexpr T * end() const
Definition vector.h:103
static constexpr int kNoHandlerTable
static CallInterfaceDescriptor CallInterfaceDescriptorFor(Builtin builtin)
Definition builtins.cc:189
static V8_EXPORT_PRIVATE const char * name(Builtin builtin)
Definition builtins.cc:226
static V8_INLINE constexpr int SlotOffset(int index)
Definition contexts.h:516
static bool IsSupported(CpuFeature f)
static const char * NameOfIsolateIndependentAddress(Address address, MemorySpan< Address > shared_external_references)
static constexpr int OffsetOfElementAt(int index)
static void EmitReturnEntry(Assembler *masm, int offset, int handler)
static int EmitReturnTableStart(Assembler *masm)
static constexpr int root_slot_offset(RootIndex root_index)
static IsolateGroup * current()
MemorySpan< Address > external_ref_table()
static LinkageLocation ForAnyRegister(MachineType type=MachineType::None())
constexpr unsigned Count() const
static const RegisterConfiguration * Default()
static constexpr Register from_code(int code)
static constexpr Tagged< Smi > FromInt(int value)
Definition smi.h:38
V8_INLINE constexpr StorageType ptr() const
static constexpr int MaxLength(uint32_t element_size_bytes)
static constexpr size_t kTargetBias
static constexpr size_t kSigBias
static constexpr size_t kImplicitArgBias
static constexpr size_t kEntrySize
static constexpr size_t kLengthOffset
static constexpr size_t kEntriesOffset
static constexpr int OffsetOf(int index)
static uint32_t GetEncodedSize(const wasm::WasmTagSig *tag)
static int Size(const wasm::StructType *type)
size_t allocation_size() const
Definition zone.h:189
static CallDescriptor * GetStubCallDescriptor(Zone *zone, const CallInterfaceDescriptor &descriptor, int stack_parameter_count, CallDescriptor::Flags flags, Operator::Properties properties=Operator::kNoProperties, StubCallMode stub_mode=StubCallMode::kCallCodeObject)
Definition linkage.cc:587
constexpr IndependentHeapType AsNonNull() const
void emit_i64x2_uconvert_i32x4_low(LiftoffRegister dst, LiftoffRegister src)
bool emit_f16x8_nearest_int(LiftoffRegister dst, LiftoffRegister src)
bool emit_f32x4_floor(LiftoffRegister dst, LiftoffRegister src)
void emit_f64_div(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_f32x4_pmax(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_sconvert_i32x4_low(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_shl(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_lt(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_gt_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_add_sat_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32x4_le(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_div(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
static constexpr ValueKind kSmiKind
void emit_i64_shl(LiftoffRegister dst, LiftoffRegister src, Register amount)
void emit_i64_ori(LiftoffRegister dst, LiftoffRegister lhs, int32_t imm)
void emit_i64_shli(LiftoffRegister dst, LiftoffRegister src, int32_t amount)
void emit_i8x16_add_sat_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_relaxed_trunc_f64x2_u_zero(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_min_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_relaxed_q15mulr_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f32_min(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i32x4_ge_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_qfms(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister src3)
void emit_f32x4_div(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_extmul_high_i8x16_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f64x2_pmax(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_trunc(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_extmul_high_i32x4_u(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_i8x16_min_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32_shri(Register dst, Register src, int32_t amount)
void emit_f64x2_max(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64_mul(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i16x8_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_shli(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_i32_eqz(Register dst, Register src)
void emit_i32x4_extadd_pairwise_i16x8_s(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_uconvert_f32x4(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_uconvert_i8x16_low(LiftoffRegister dst, LiftoffRegister src)
void emit_f32x4_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_splat(LiftoffRegister dst, LiftoffRegister src)
void emit_f32_max(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i8x16_uconvert_i16x8(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_shri_s(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_f32x4_demote_f64x2_zero(LiftoffRegister dst, LiftoffRegister src)
void emit_i64_sari(LiftoffRegister dst, LiftoffRegister src, int32_t amount)
void emit_f64x2_splat(LiftoffRegister dst, LiftoffRegister src)
void emit_f32_mul(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_s128_and(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_abs(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_sub_sat_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32x4_max(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_ge_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_dot_i16x8_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64_xori(LiftoffRegister dst, LiftoffRegister lhs, int32_t imm)
void emit_i16x8_extmul_low_i8x16_u(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f64x2_le(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_shr_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_shr_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_uconvert_i8x16_high(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32x4_pmin(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_alltrue(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_dot_i8x16_i7x16_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_i16x8_sconvert_i8x16_high(LiftoffRegister dst, LiftoffRegister src)
void emit_f64x2_qfma(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister src3)
void emit_i32_shli(Register dst, Register src, int32_t amount)
void emit_i32x4_extmul_low_i16x8_u(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_i32x4_uconvert_i16x8_low(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_splat(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_sar(Register dst, Register src, Register amount)
void emit_i16x8_shri_u(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
bool emit_i16x8_sconvert_f16x8(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_sconvert_i32x4(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_add_sat_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_shl(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_add_sat_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_shl(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_s128_not(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_gt_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_f64x2_convert_low_i32x4_u(LiftoffRegister dst, LiftoffRegister src)
void emit_f32_copysign(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_f32x4_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_pmax(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_shli(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_f64_min(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i64x2_shr_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_sconvert_i16x8(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64_set_cond(Condition condition, Register dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_f32x4_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_abs(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_shr(Register dst, Register src, Register amount)
void emit_i8x16_max_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_extmul_low_i32x4_u(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_i16x8_max_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_abs(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_andi(Register dst, Register lhs, int32_t imm)
bool emit_f16x8_abs(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_ge_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_ptrsize_set_cond(Condition condition, Register dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_convert_low_i32x4_s(LiftoffRegister dst, LiftoffRegister src)
bool emit_f32x4_promote_low_f16x8(LiftoffRegister dst, LiftoffRegister src)
bool emit_f16x8_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_shri_u(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
bool emit_f16x8_max(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_shr_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32x4_uconvert_i32x4(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_extmul_high_i32x4_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f32x4_relaxed_min(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_shri_s(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_f64_sub(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i8x16_gt_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_promote_low_f32x4(LiftoffRegister dst, LiftoffRegister src)
void emit_i64_addi(LiftoffRegister dst, LiftoffRegister lhs, int64_t imm)
void emit_i16x8_sub_sat_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_bitmask(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_alltrue(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_and(Register dst, Register lhs, Register rhs)
void emit_f64_copysign(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
bool emit_f64x2_trunc(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_rounding_average_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_abs(LiftoffRegister dst, LiftoffRegister src)
bool emit_f16x8_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_uconvert_i32x4_high(LiftoffRegister dst, LiftoffRegister src)
bool emit_f64x2_ceil(LiftoffRegister dst, LiftoffRegister src)
bool emit_f16x8_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_extmul_high_i8x16_u(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f32x4_qfms(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister src3)
void emit_i32x4_shli(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_i32x4_min_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32_xor(Register dst, Register lhs, Register rhs)
void emit_s128_select(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister mask)
bool emit_f16x8_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_sconvert_i32x4_high(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_trunc_sat_f64x2_s_zero(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_or(Register dst, Register lhs, Register rhs)
bool emit_f16x8_floor(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_extadd_pairwise_i8x16_s(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_bitmask(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_extmul_low_i32x4_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
bool emit_f16x8_demote_f32x4_zero(LiftoffRegister dst, LiftoffRegister src)
void emit_f32x4_qfma(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister src3)
void emit_f32x4_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_gt_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_min(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_shri_s(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
bool emit_f64x2_nearest_int(LiftoffRegister dst, LiftoffRegister src)
void emit_f32x4_abs(LiftoffRegister dst, LiftoffRegister src)
void emit_s128_and_not(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_s128_relaxed_laneselect(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister mask, int lane_width)
void emit_i32x4_shri_u(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_i32x4_sconvert_f32x4(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_shri_u(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_i32_sari(Register dst, Register src, int32_t amount)
void emit_i32x4_uconvert_i16x8_high(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_sub_sat_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_max_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f32x4_trunc(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_max_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64_xor(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32x4_relaxed_max(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_ceil(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_min_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64_max(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i32x4_extmul_low_i16x8_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f64_add(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i64_sar(LiftoffRegister dst, LiftoffRegister src, Register amount)
void emit_i16x8_shr_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32_ori(Register dst, Register lhs, int32_t imm)
void emit_f64x2_qfms(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister src3)
bool emit_f32x4_ceil(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_trunc_sat_f64x2_u_zero(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_mul(Register dst, Register lhs, Register rhs)
void emit_f32_set_cond(Condition condition, Register dst, DoubleRegister lhs, DoubleRegister rhs)
bool emit_f16x8_uconvert_i16x8(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_min_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_demote_f64x2_zero(LiftoffRegister dst, LiftoffRegister src)
bool emit_f16x8_div(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_gt_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f32x4_nearest_int(LiftoffRegister dst, LiftoffRegister src)
void emit_i64_shri(LiftoffRegister dst, LiftoffRegister src, int32_t amount)
void emit_i32x4_sconvert_i16x8_high(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_shri_s(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_i64_set_cond(Condition condition, Register dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f64x2_floor(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_sub_sat_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_sconvert_i16x8_low(LiftoffRegister dst, LiftoffRegister src)
void emit_f64x2_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_le(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_rounding_average_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_gt_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_min_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_ge_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_ge_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_uconvert_i32x4(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_lt(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_popcnt(LiftoffRegister dst, LiftoffRegister src)
void emit_i64_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_pmin(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32x4_sconvert_i32x4(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_ne(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64_or(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64x2_alltrue(LiftoffRegister dst, LiftoffRegister src)
void emit_f32_sub(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i16x8_ge_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32_xori(Register dst, Register lhs, int32_t imm)
void emit_i32_set_cond(Condition, Register dst, Register lhs, Register rhs)
void emit_f32x4_min(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_sconvert_i8x16_low(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_sub(Register dst, Register lhs, Register rhs)
void emit_i16x8_q15mulr_sat_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_i64x2_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_shli(LiftoffRegister dst, LiftoffRegister lhs, int32_t rhs)
void emit_i64_shr(LiftoffRegister dst, LiftoffRegister src, Register amount)
void emit_i16x8_bitmask(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_sub(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_max_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32_div(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
void emit_i64_andi(LiftoffRegister dst, LiftoffRegister lhs, int32_t imm)
bool emit_f16x8_min(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_relaxed_trunc_f32x4_s(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_splat(LiftoffRegister dst, LiftoffRegister src)
void emit_f32x4_sqrt(LiftoffRegister dst, LiftoffRegister src)
void emit_i16x8_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_shr_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_extmul_low_i8x16_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f64x2_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_s128_or(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_alltrue(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_shr_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_abs(LiftoffRegister dst, LiftoffRegister src)
void emit_f32x4_splat(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_shr_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64_eqz(Register dst, LiftoffRegister src)
void emit_f32x4_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_gt_s(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_shl(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_splat(LiftoffRegister dst, LiftoffRegister src)
bool emit_f16x8_sconvert_i16x8(LiftoffRegister dst, LiftoffRegister src)
void emit_f64x2_sqrt(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_shuffle(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs, const uint8_t shuffle[16], bool is_swizzle)
void emit_i32x4_extadd_pairwise_i16x8_u(LiftoffRegister dst, LiftoffRegister src)
static constexpr ValueKind kIntPtrKind
void emit_i32_addi(Register dst, Register lhs, int32_t imm)
void emit_i16x8_extadd_pairwise_i8x16_u(LiftoffRegister dst, LiftoffRegister src)
void emit_i32x4_extmul_high_i16x8_s(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_i64x2_add(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i64_mul(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_max_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_i16x8_uconvert_f16x8(LiftoffRegister dst, LiftoffRegister src)
void emit_i8x16_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_i64x2_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_neg(LiftoffRegister dst, LiftoffRegister src)
void emit_s128_xor(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i16x8_ge_u(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i32x4_relaxed_trunc_f32x4_u(LiftoffRegister dst, LiftoffRegister src)
void emit_v128_anytrue(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_shl(Register dst, Register src, Register amount)
void emit_i32x4_extmul_high_i16x8_u(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2)
void emit_f32x4_lt(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_s128_const(LiftoffRegister dst, const uint8_t imms[16])
void emit_i64_and(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_relaxed_min(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_relaxed_max(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
bool emit_f16x8_qfma(LiftoffRegister dst, LiftoffRegister src1, LiftoffRegister src2, LiftoffRegister src3)
void emit_i8x16_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f64x2_pmin(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_f32_add(DoubleRegister dst, DoubleRegister lhs, DoubleRegister rhs)
bool emit_f16x8_sqrt(LiftoffRegister dst, LiftoffRegister src)
void emit_i32_add(Register dst, Register lhs, Register rhs)
void emit_i32x4_relaxed_trunc_f64x2_s_zero(LiftoffRegister dst, LiftoffRegister src)
void emit_f32x4_eq(LiftoffRegister dst, LiftoffRegister lhs, LiftoffRegister rhs)
void emit_i8x16_bitmask(LiftoffRegister dst, LiftoffRegister src)
static LiftoffRegister from_external_code(RegClass rc, ValueKind kind, int code)
static LiftoffRegister ForPair(Register low, Register high)
static LoadType ForValueKind(ValueKind kind, bool is_signed=false)
static constexpr int ElementOffsetInTaggedFixedArray(int index)
static constexpr int ElementOffsetInTaggedFixedUInt32Array(int index)
static constexpr int ElementOffsetInTaggedFixedAddressArray(int index)
static constexpr int ToTagged(int offset)
static void CanonicalizeShuffle(bool inputs_equal, uint8_t *shuffle, bool *needs_swap, bool *is_swizzle)
static StoreType ForValueKind(ValueKind kind)
static constexpr ValueType Ref(ModuleTypeIndex index, bool shared, RefTypeKind kind)
Definition value-type.h:887
static size_t EstimateLiftoffCodeSize(int body_size)
static Address GetNondeterminismAddr()
static constexpr bool IsBreakable(WasmOpcode)
static constexpr const char * OpcodeName(WasmOpcode)
static constexpr bool IsPrefixOpcode(WasmOpcode)
static WasmValue ForUintPtr(uintptr_t value)
Definition wasm-value.h:130
Zone * zone_
#define COMPRESS_POINTERS_BOOL
Definition globals.h:99
#define FOREACH_WASM_TRAPREASON(V)
Definition globals.h:2650
const InternalIndex descriptor_
const PropertyKind kind_
int start
uint32_t count
int end
ZoneVector< OpIndex > candidates
base::Vector< const DirectHandle< Object > > args
Definition execution.cc:74
Label label
int32_t offset
TNode< Object > target
ZoneVector< RpoNumber > & result
#define TRACE(...)
Builtin builtin
static constexpr int kTierUpCostForFunctionEntry
uint32_t input_idx_
ValueKind reg_kind_
LiftoffRegister reg
std::vector< Value > changed_values_
#define CASE_TYPE_CONVERSION(opcode, dst_kind, src_kind, ext_ref, can_trap)
LinkageLocation location_
std::unique_ptr< LiftoffFrameDescriptionForDeopt > frame_description_
#define ATOMIC_LOAD_OP(name, type)
bool null_succeeds
ValueKind inline_storage_[kInlineStorage]
#define LIST_FEATURE(name,...)
SpilledRegistersForInspection * spilled_registers
LiftoffRegList spills
MovableLabel continuation
Label label_
WasmOpcode outstanding_op_
int32_t *const max_steps_
std::vector< EntryBuilder > entries_
LiftoffAssembler::CacheState label_state
#define CASE_I32_UNOP(opcode, fn)
ZoneVector< trap_handler::ProtectedInstructionData > protected_instructions_
LiftoffBailoutReason bailout_reason_
Label * no_match
Register cached_instance_data
#define ATOMIC_BINOP_OP(op, name, type)
MovableLabel handler
#define ATOMIC_STORE_LIST(V)
const ForDebugging for_debugging_
TryInfo * try_info
ArgType first_arg
int last_safepoint_offset_
int stack_height_
#define CASE_SIMD_REPLACE_LANE_OP(opcode, kind, fn)
int num_exceptions_
LiftoffAssembler::CacheState catch_state
LiftoffRegList param_regs_
static constexpr int kTierUpCostForCheck
base::OwnedVector< ValueType > stack_value_types_for_debugging_
std::list< EntryBuilder > ool_entries_
#define ATOMIC_BINOP_INSTRUCTION_LIST(V)
bool catch_reached
int pc_offset_
ZoneVector< HandlerInfo > handlers_
static constexpr uint32_t kFirstInputIdx
const int * next_breakpoint_ptr_
#define CASE_FLOAT_UNOP(opcode, kind, fn)
FreezeCacheState freeze_
#define SCOPED_CODE_COMMENT(str)
ZoneVector< OutOfLineCode > out_of_line_code_
int num_exceptions
OutOfLineSafepointInfo * safepoint_info
#define LOAD_TAGGED_PTR_INSTANCE_FIELD(dst, name, pinned)
bool needs_gp_pair_
DebugSideTableBuilder::EntryBuilder * debug_sidetable_entry_builder
const uint32_t deopt_info_bytecode_offset_
RegClass rc_
ValueType obj_type
Register obj_reg
Builtin no_match_trap
uint32_t pc_offset_stack_frame_construction_
std::vector< Value > last_ool_values_
static constexpr bool kUsesPoppedArgs
const int * next_breakpoint_end_
int num_locals_
base::EnumSet< ValueKind > supported_types_
#define RUNTIME_STUB_FOR_TRAP(trap_reason)
#define ATOMIC_COMPARE_EXCHANGE_OP(name, type)
DebugSideTableBuilder *const debug_sidetable_builder_
static constexpr base::EnumSet< ValueKind > kUnconditionallySupported
int handler_table_offset_
const bool detect_nondeterminism_
#define CASE_SIMD_EXTRACT_LANE_OP(opcode, kind, fn)
std::vector< uint32_t > encountered_call_instructions_
SafepointTableBuilder safepoint_table_builder_
const compiler::NullCheckStrategy null_check_strategy_
ElseState * else_state
ZoneVector< Entry > entries
bool in_handler
static constexpr WasmOpcode kNoOutstandingOp
#define CODE_COMMENT(str)
std::vector< Value > last_values_
const int func_index_
#define FUZZER_HEAVY_INSTRUCTION
int pc_offset
EmitFn fn
const LocationKindForDeopt deopt_location_kind_
LiftoffRegList regs_to_save
SourcePositionTableBuilder source_position_table_builder_
TempRegisterScope * temp_scope_
LiftoffAssembler asm_
static constexpr size_t kInlineStorage
#define CASE_FLOAT_UNOP_WITH_CFALLBACK(opcode, kind, fn)
#define FREEZE_STATE(witness_name)
#define ATOMIC_LOAD_LIST(V)
int dead_breakpoint_
bool did_function_entry_break_checks_
const uint32_t num_params_
#define LOAD_PROTECTED_PTR_INSTANCE_FIELD(dst, name, pinned)
uint32_t param_idx_
#define ATOMIC_STORE_OP(name, type)
#define ATOMIC_COMPARE_EXCHANGE_LIST(V)
CompilationEnv *const env_
Label catch_label
std::optional< OolTrapLabel > trap
#define CASE_I64_UNOP(opcode, fn)
LiftoffRegList free_temps_
int position
Definition liveedit.cc:290
uint32_t const mask
STL namespace.
Utf8Variant
Definition unicode.h:145
int int32_t
Definition unicode.cc:40
int SNPrintF(Vector< char > str, const char *format,...)
Definition strings.cc:20
constexpr bool IsInBounds(T index, T length, T max)
Definition bounds.h:49
auto Reversed(T &t)
Definition iterator.h:105
constexpr Vector< T > VectorOf(T *start, size_t size)
Definition vector.h:360
OwnedVector< T > OwnedCopyOf(const T *data, size_t size)
Definition vector.h:383
LockGuard< Mutex > MutexGuard
Definition mutex.h:219
StaticCanonicalForLoopMatcher::BinOp BinOp
CallDescriptor * GetWasmCallDescriptor(Zone *zone, const Signature< T > *fsig, WasmCallKind call_kind, bool need_frame_state)
Node::Uses::const_iterator begin(const Node::Uses &uses)
Definition node.h:708
void split(const std::string &str, char delimiter, std::vector< std::string > *vparams)
void Store(LiftoffAssembler *assm, LiftoffRegister src, MemOperand dst, ValueKind kind)
void AtomicBinop(LiftoffAssembler *lasm, Register dst_addr, Register offset_reg, uintptr_t offset_imm, LiftoffRegister value, LiftoffRegister result, StoreType type, Binop op)
CPURegister LoadToRegister(LiftoffAssembler *assm, UseScratchRegisterScope *temps, const LiftoffAssembler::VarState &src)
void EmitSimdShiftOp(LiftoffAssembler *assm, LiftoffRegister dst, LiftoffRegister operand, LiftoffRegister count)
static bool StringCheck(const WasmRef obj)
constexpr MachineType machine_type(ValueKind kind)
static constexpr RegClass reg_class_for(ValueKind kind)
FunctionSig WasmTagSig
constexpr uint32_t kWasmPageSizeLog2
static bool EqCheck(const WasmRef obj)
int GetSubtypingDepth(const WasmModule *module, ModuleTypeIndex type_index)
constexpr Condition Flip(Condition cond)
static constexpr bool needs_gp_reg_pair(ValueKind kind)
uint32_t max_table_size()
constexpr uint32_t kMinimumSupertypeArraySize
constexpr IndependentHeapType kWasmStringRef
constexpr Condition Negate(Condition cond)
bool AbstractTypeCast(Isolate *isolate, const WasmRef obj, const ValueType obj_type, bool null_succeeds)
constexpr DoubleRegList kLiftoffAssemblerFpCacheRegs
constexpr size_t kV8MaxWasmFunctionSize
Definition wasm-limits.h:51
constexpr IndependentHeapType kWasmAnyRef
V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2, const WasmModule *module1, const WasmModule *module2)
LiftoffAssembler::VarState VarState
constexpr IndependentHeapType kWasmExternRef
static bool I31Check(const WasmRef obj)
constexpr auto kRegister
constexpr RegList kLiftoffAssemblerGpCacheRegs
static bool StructCheck(const WasmRef obj)
static constexpr bool kNeedS128RegPair
WasmCompilationResult ExecuteLiftoffCompilation(CompilationEnv *env, const FunctionBody &func_body, const LiftoffOptions &compiler_options)
constexpr IndependentHeapType kWasmFuncRef
std::unique_ptr< DebugSideTable > GenerateLiftoffDebugSideTable(const WasmCode *code)
constexpr int value_kind_size_log2(ValueKind kind)
constexpr IndependentHeapType kWasmRefI31
constexpr ValueKind unpacked(ValueKind kind)
constexpr size_t kMaxMemory64Size
bool(*)(const WasmRef obj) TypeChecker
static constexpr LiftoffRegList GetCacheRegList(RegClass rc)
WasmEngine * GetWasmEngine()
int declared_function_index(const WasmModule *module, int func_index)
constexpr int kMaxStructFieldIndexForImplicitNullCheck
V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype, const WasmModule *sub_module, const WasmModule *super_module)
typedef void(VECTORCALL PWasmOp)(const uint8_t *code
constexpr int value_kind_size(ValueKind kind)
constexpr auto kStack
static constexpr LiftoffRegList kGpCacheRegList
static bool ArrayCheck(const WasmRef obj)
constexpr size_t kV8MaxWasmTableSize
Definition wasm-limits.h:58
constexpr bool kPartialOOBWritesAreNoops
Signature< ValueType > FunctionSig
constexpr auto kIntConst
constexpr bool is_reference(ValueKind kind)
static int StructFieldOffset(const StructType *struct_type, int field_index)
static constexpr bool kNeedI64RegPair
LiftoffAssembler::ValueKindSig ValueKindSig
constexpr Register no_reg
constexpr Register kRootRegister
constexpr int kUInt8Size
Definition globals.h:394
constexpr int kTaggedSize
Definition globals.h:542
constexpr int kInt64Size
Definition globals.h:402
constexpr int kSimd128Size
Definition globals.h:706
constexpr int kNoSourcePosition
Definition globals.h:850
DwVfpRegister DoubleRegister
void PrintF(const char *format,...)
Definition utils.cc:39
const int kSmiTagSize
Definition v8-internal.h:87
wasm::WasmModule WasmModule
const Address kWeakHeapObjectMask
Definition globals.h:967
kWasmInternalFunctionIndirectPointerTag instance_data
too high values may cause the compiler to set high thresholds for inlining to as much as possible avoid inlined allocation of objects that cannot escape trace load stores from virtual maglev objects use TurboFan fast string builder analyze liveness of environment slots and zap dead values trace TurboFan load elimination emit data about basic block usage in builtins to this enable builtin reordering when run mksnapshot flag for emit warnings when applying builtin profile data verify register allocation in TurboFan randomly schedule instructions to stress dependency tracking enable store store elimination in TurboFan rewrite far to near simulate GC compiler thread race related to allow float parameters to be passed in simulator mode JS Wasm Run additional turbo_optimize_inlined_js_wasm_wrappers enable experimental feedback collection in generic lowering enable Turboshaft s WasmLoadElimination enable Turboshaft s low level load elimination for JS enable Turboshaft s escape analysis for string concatenation use enable Turbolev features that we want to ship in the not too far future trace individual Turboshaft reduction steps trace intermediate Turboshaft reduction steps invocation count threshold for early optimization Enables optimizations which favor memory size over execution speed Enables sampling allocation profiler with X as a sample interval min size of a semi the new space consists of two semi spaces max size of the Collect garbage after Collect garbage after keeps maps alive for< n > old space garbage collections print one detailed trace line in name
Definition flags.cc:2086
kWasmInternalFunctionIndirectPointerTag kProtectedInstanceDataOffset sig
constexpr int kSystemPointerSize
Definition globals.h:410
kMemory0SizeOffset Address kNewAllocationLimitAddressOffset Address kOldAllocationLimitAddressOffset uint8_t kGlobalsStartOffset kJumpTableStartOffset std::atomic< uint32_t > kTieringBudgetArrayOffset kDataSegmentStartsOffset kElementSegmentsOffset kInstanceObjectOffset kMemoryObjectsOffset kTaggedGlobalsBufferOffset tables
constexpr Register kReturnRegister1
constexpr int kTaggedSizeLog2
Definition globals.h:543
constexpr Register kReturnRegister0
constexpr bool SmiValuesAre31Bits()
std::unique_ptr< AssemblerBuffer > NewAssemblerBuffer(int size)
Definition assembler.cc:167
constexpr int kInt32Size
Definition globals.h:401
constexpr Register kWasmImplicitArgRegister
const int kSmiShiftSize
kMemory0SizeOffset Address kNewAllocationLimitAddressOffset Address kOldAllocationLimitAddressOffset uint8_t kGlobalsStartOffset kJumpTableStartOffset std::atomic< uint32_t > kTieringBudgetArrayOffset kDataSegmentStartsOffset element_segments
V8_EXPORT_PRIVATE FlagValues v8_flags
constexpr bool SmiValuesAre32Bits()
return value
Definition map-inl.h:893
constexpr bool Is64()
const int kSmiTag
Definition v8-internal.h:86
constexpr int kMaxInt
Definition globals.h:374
JSArrayBuffer::IsDetachableBit is_shared
constexpr uint32_t kMaxUInt32
Definition globals.h:387
auto PrintCollection(const T &collection) -> PrintIteratorRange< typename std::common_type< decltype(std::begin(collection)), decltype(std::end(collection))>::type >
Definition ostreams.h:186
bool is_signed(Condition cond)
i::Address Load(i::Address address)
Definition unwinder.cc:19
Definition c-api.cc:87
RegExpCompiler * compiler_
#define V8_NOEXCEPT
#define FATAL(...)
Definition logging.h:47
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define CHECK_EQ(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName)
Definition macros.h:143
constexpr bool IsAligned(T value, U alignment)
Definition macros.h:403
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName)
Definition macros.h:130
static constexpr uint32_t kCallRef
static constexpr uint32_t kCallIndirect
std::unique_ptr< DebugSideTable > * debug_sidetable
WasmDetectedFeatures * detected_features
std::unique_ptr< AssemblerBuffer > instr_buffer
#define OFFSET_OF_DATA_START(Type)
#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val)
#define TRACE_DISABLED_BY_DEFAULT(name)
#define V8_STATIC_ROOTS_BOOL
Definition v8config.h:1001
#define V8_INLINE
Definition v8config.h:500
#define V8_LIKELY(condition)
Definition v8config.h:661
#define V8_UNLIKELY(condition)
Definition v8config.h:660
#define V8_NOINLINE
Definition v8config.h:586
#define V8_PRESERVE_MOST
Definition v8config.h:598
#define LOAD_INSTANCE_FIELD(instance, name, representation)
#define FOREACH_WASM_EXPERIMENTAL_FEATURE_FLAG(V)