v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
liftoff-assembler.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#include <sstream>
9
15#include "src/utils/ostreams.h"
22
23namespace v8::internal::wasm {
24
27
30
31namespace {
32
33class RegisterReuseMap {
34 public:
35 void Add(LiftoffRegister src, LiftoffRegister dst) {
36 if ([[maybe_unused]] auto previous = Lookup(src)) {
37 DCHECK_EQ(previous, dst);
38 return;
39 }
40 map_.emplace_back(src);
41 map_.emplace_back(dst);
42 }
43
44 std::optional<LiftoffRegister> Lookup(LiftoffRegister src) {
45 for (auto it = map_.begin(), end = map_.end(); it != end; it += 2) {
46 if (it->is_gp_pair() == src.is_gp_pair() &&
47 it->is_fp_pair() == src.is_fp_pair() && *it == src)
48 return *(it + 1);
49 }
50 return {};
51 }
52
53 private:
54 // {map_} holds pairs of <src, dst>.
55 base::SmallVector<LiftoffRegister, 8> map_;
56};
57
58enum MergeKeepStackSlots : bool {
59 kKeepStackSlots = true,
60 kTurnStackSlotsIntoRegisters = false
61};
62enum MergeAllowConstants : bool {
63 kConstantsAllowed = true,
64 kConstantsNotAllowed = false
65};
66enum MergeAllowRegisters : bool {
67 kRegistersAllowed = true,
68 kRegistersNotAllowed = false
69};
70enum ReuseRegisters : bool {
71 kReuseRegisters = true,
72 kNoReuseRegisters = false
73};
74// {InitMergeRegion} is a helper used by {MergeIntoNewState} to initialize
75// a part of the target stack ([target, target+count]) from [source,
76// source+count]. The parameters specify how to initialize the part. The goal is
77// to set up the region such that later merges (via {MergeStackWith} /
78// {MergeFullStackWith} can successfully transfer their values to this new
79// state.
80void InitMergeRegion(LiftoffAssembler::CacheState* target_state,
81 const VarState* source, VarState* target, uint32_t count,
82 MergeKeepStackSlots keep_stack_slots,
83 MergeAllowConstants allow_constants,
84 MergeAllowRegisters allow_registers,
85 ReuseRegisters reuse_registers, LiftoffRegList used_regs,
86 int new_stack_offset, ParallelMove& parallel_move) {
87 RegisterReuseMap register_reuse_map;
88 for (const VarState* source_end = source + count; source < source_end;
89 ++source, ++target) {
90 if (source->is_stack() && keep_stack_slots) {
91 *target = *source;
92 // If {new_stack_offset} is set, we want to recompute stack offsets for
93 // the region we are initializing such that they are contiguous. If
94 // {new_stack_offset} is zero (which is an illegal stack offset), we just
95 // keep the source offsets.
96 if (new_stack_offset) {
97 new_stack_offset =
98 LiftoffAssembler::NextSpillOffset(source->kind(), new_stack_offset);
99 if (new_stack_offset != source->offset()) {
100 target->set_offset(new_stack_offset);
101 parallel_move.TransferToStack(new_stack_offset, *source);
102 }
103 }
104 continue;
105 }
106 if (source->is_const() && allow_constants) {
107 *target = *source;
108 DCHECK(!new_stack_offset);
109 continue;
110 }
111 std::optional<LiftoffRegister> reg;
112 bool needs_reg_transfer = true;
113 if (allow_registers) {
114 // First try: Keep the same register, if it's free.
115 if (source->is_reg() && target_state->is_free(source->reg())) {
116 reg = source->reg();
117 needs_reg_transfer = false;
118 }
119 // Second try: Use the same register we used before (if we reuse
120 // registers).
121 if (!reg && reuse_registers) {
122 reg = register_reuse_map.Lookup(source->reg());
123 }
124 // Third try: Use any free register.
125 RegClass rc = reg_class_for(source->kind());
126 if (!reg && target_state->has_unused_register(rc, used_regs)) {
127 reg = target_state->unused_register(rc, used_regs);
128 }
129 }
130 // See above: Recompute the stack offset if requested.
131 int target_offset = source->offset();
132 if (new_stack_offset) {
133 new_stack_offset =
134 LiftoffAssembler::NextSpillOffset(source->kind(), new_stack_offset);
135 target_offset = new_stack_offset;
136 }
137 if (reg) {
138 if (needs_reg_transfer) parallel_move.LoadIntoRegister(*reg, *source);
139 if (reuse_registers) register_reuse_map.Add(source->reg(), *reg);
140 target_state->inc_used(*reg);
141 *target = VarState(source->kind(), *reg, target_offset);
142 } else {
143 // No free register; make this a stack slot.
144 *target = VarState(source->kind(), target_offset);
145 parallel_move.TransferToStack(target_offset, *source);
146 }
147 }
148}
149
150} // namespace
151
153 uint32_t num_locals, uint32_t arity, uint32_t stack_depth) {
154 CacheState target{zone()};
155
156 // The source state looks like this:
157 // |------locals------|---(stack prefix)---|--(discarded)--|----merge----|
158 // <-- num_locals --> <-- stack_depth --> <-- arity -->
159 //
160 // We compute the following target state from it:
161 // |------locals------|---(stack prefix)----|----merge----|
162 // <-- num_locals --> <-- stack_depth --> <-- arity -->
163 //
164 // The target state will have dropped the "(discarded)" region, and the
165 // "locals" and "merge" regions have been modified to avoid any constants and
166 // avoid duplicate register uses. This ensures that later merges can
167 // successfully transfer into the target state.
168 // The "stack prefix" region will be identical for any source that merges into
169 // that state.
170
172 target.SetInstanceCacheRegister(cache_state_.cached_instance_data);
173 }
174
178 target.SetMemStartCacheRegister(cache_state_.cached_mem_start,
180 }
181
182 uint32_t target_height = num_locals + stack_depth + arity;
183
184 target.stack_state.resize(target_height);
185
186 const VarState* source_begin = cache_state_.stack_state.data();
187 VarState* target_begin = target.stack_state.data();
188
189 // Compute the starts of the different regions, for source and target (see
190 // pictograms above).
191 const VarState* locals_source = source_begin;
192 const VarState* stack_prefix_source = source_begin + num_locals;
193 const VarState* discarded_source = stack_prefix_source + stack_depth;
194 const VarState* merge_source = cache_state_.stack_state.end() - arity;
195 VarState* locals_target = target_begin;
196 VarState* stack_prefix_target = target_begin + num_locals;
197 VarState* merge_target = target_begin + num_locals + stack_depth;
198
199 // Try to keep locals and the merge region in their registers. Registers used
200 // multiple times need to be copied to another free register. Compute the list
201 // of used registers.
202 LiftoffRegList used_regs;
203 for (auto& src : base::VectorOf(locals_source, num_locals)) {
204 if (src.is_reg()) used_regs.set(src.reg());
205 }
206 // If there is more than one operand in the merge region, a stack-to-stack
207 // move can interfere with a register reload, which would not be handled
208 // correctly by the ParallelMove. To avoid this, spill all registers in
209 // this region.
210 MergeAllowRegisters allow_registers =
211 arity <= 1 ? kRegistersAllowed : kRegistersNotAllowed;
212 if (allow_registers) {
213 for (auto& src : base::VectorOf(merge_source, arity)) {
214 if (src.is_reg()) used_regs.set(src.reg());
215 }
216 }
217
218 ParallelMove parallel_move{this};
219
220 // The merge region is often empty, hence check for this before doing any
221 // work (even though not needed for correctness).
222 if (arity) {
223 // Initialize the merge region. If this region moves, try to turn stack
224 // slots into registers since we need to load the value anyways.
225 MergeKeepStackSlots keep_merge_stack_slots =
226 target_height == cache_state_.stack_height()
227 ? kKeepStackSlots
228 : kTurnStackSlotsIntoRegisters;
229 // Shift spill offsets down to keep slots contiguous. We place the merge
230 // region right after the "stack prefix", if it exists.
231 int merge_region_stack_offset = discarded_source == source_begin
233 : discarded_source[-1].offset();
234 InitMergeRegion(&target, merge_source, merge_target, arity,
235 keep_merge_stack_slots, kConstantsNotAllowed,
236 allow_registers, kNoReuseRegisters, used_regs,
237 merge_region_stack_offset, parallel_move);
238 }
239
240 // Initialize the locals region. Here, stack slots stay stack slots (because
241 // they do not move). Try to keep register in registers, but avoid duplicates.
242 if (num_locals) {
243 InitMergeRegion(&target, locals_source, locals_target, num_locals,
244 kKeepStackSlots, kConstantsNotAllowed, kRegistersAllowed,
245 kNoReuseRegisters, used_regs, 0, parallel_move);
246 }
247 // Consistency check: All the {used_regs} are really in use now.
248 DCHECK_EQ(used_regs, target.used_registers & used_regs);
249
250 // Last, initialize the "stack prefix" region. Here, constants are allowed,
251 // but registers which are already used for the merge region or locals must be
252 // moved to other registers or spilled. If a register appears twice in the
253 // source region, ensure to use the same register twice in the target region.
254 if (stack_depth) {
255 InitMergeRegion(&target, stack_prefix_source, stack_prefix_target,
256 stack_depth, kKeepStackSlots, kConstantsAllowed,
257 kRegistersAllowed, kReuseRegisters, used_regs, 0,
258 parallel_move);
259 }
260
261 return target;
262}
263
265 // Just use the move assignment operator.
266 *this = std::move(source);
267}
268
270 // Call the private copy assignment operator.
271 *this = source;
272}
273
274namespace {
275int GetSafepointIndexForStackSlot(const VarState& slot) {
276 // index = 0 is for the stack slot at 'fp + kFixedFrameSizeAboveFp -
277 // kSystemPointerSize', the location of the current stack slot is 'fp -
278 // slot.offset()'. The index we need is therefore '(fp +
279 // kFixedFrameSizeAboveFp - kSystemPointerSize) - (fp - slot.offset())' =
280 // 'slot.offset() + kFixedFrameSizeAboveFp - kSystemPointerSize'.
281 // Concretely, the index of the first stack slot is '4'.
285}
286} // namespace
287
290 SpillLocation spill_location) {
291 for (const auto& slot : stack_state) {
292 if (!is_reference(slot.kind())) continue;
293
294 if (spill_location == SpillLocation::kTopOfStack && slot.is_reg()) {
295 // Registers get spilled just before the call to the runtime. In {spills}
296 // we store which of the spilled registers contain references, so that we
297 // can add the spill slots to the safepoint.
298 spills->set(slot.reg());
299 continue;
300 }
301 DCHECK_IMPLIES(slot.is_reg(), spill_location == SpillLocation::kStackSlots);
302
303 slots->push_back(GetSafepointIndexForStackSlot(slot));
304 }
305}
306
309 // Go in reversed order to set the higher bits first; this avoids cost for
310 // growing the underlying bitvector.
311 for (const auto& slot : base::Reversed(stack_state)) {
312 if (is_reference(slot.kind())) {
313 // TODO(v8:14422): References that are not on the stack now will get lost
314 // at the moment. Once v8:14422 is resolved, this `continue` should be
315 // revisited and potentially updated to a DCHECK.
316 if (!slot.is_stack()) continue;
317 safepoint.DefineTaggedStackSlot(GetSafepointIndexForStackSlot(slot));
318 }
319 }
320}
321
324 for (const auto& slot : stack_state) {
325 if (!is_reference(slot.kind())) continue;
326 if (slot.is_stack()) {
327 safepoint.DefineTaggedStackSlot(GetSafepointIndexForStackSlot(slot));
328 } else {
329 DCHECK(slot.is_reg());
330 safepoint.DefineTaggedRegister(slot.reg().gp().code());
331 }
332 }
335 }
336}
337
339 // The GC does not care about the actual number of spill slots, just about
340 // the number of references that could be there in the spilling area. Note
341 // that the offset of the first spill slot is kSystemPointerSize and not
342 // '0'. Therefore we don't have to add '+1' here.
343 return (max_used_spill_offset_ +
347}
348
352
353namespace {
354
355AssemblerOptions DefaultLiftoffOptions() {
356 return AssemblerOptions{
357 .is_wasm = true,
358 };
359}
360
361} // namespace
362
364 std::unique_ptr<AssemblerBuffer> buffer)
365 : MacroAssembler(zone, DefaultLiftoffOptions(), CodeObjectRequired::kNo,
366 std::move(buffer)),
368 set_abort_hard(true); // Avoid calls to Abort.
369}
370
376
384
386 VarState slot, RegPairHalf half, LiftoffRegList pinned) {
387 if (slot.is_reg()) {
388 return half == kLowWord ? slot.reg().low() : slot.reg().high();
389 }
391 if (slot.is_stack()) {
392 FillI64Half(dst.gp(), slot.offset(), half);
393 return dst;
394 }
395 DCHECK(slot.is_const());
396 int32_t half_word =
397 static_cast<int32_t>(half == kLowWord ? slot.constant().to_i64()
398 : slot.constant().to_i64() >> 32);
399 LoadConstant(dst, WasmValue(half_word));
400 return dst;
401}
402
404 auto* dropped = cache_state_.stack_state.begin() + offset;
405 if (dropped->is_reg()) {
406 cache_state_.dec_used(dropped->reg());
407 }
408 // Compute the stack offset that the remaining slots are based on.
409 int stack_offset =
410 offset == 0 ? StaticStackFrameSize() : dropped[-1].offset();
411 // Move remaining slots down.
412 for (VarState *slot = dropped, *end = cache_state_.stack_state.end() - 1;
413 slot != end; ++slot) {
414 *slot = *(slot + 1);
415 stack_offset = NextSpillOffset(slot->kind(), stack_offset);
416 // Padding could cause some spill offsets to remain the same.
417 if (slot->offset() != stack_offset) {
418 if (slot->is_stack()) {
419 MoveStackValue(stack_offset, slot->offset(), slot->kind());
420 }
421 slot->set_offset(stack_offset);
422 }
423 }
424 cache_state_.stack_state.pop_back();
425}
426
428 for (VarState& slot :
429 base::VectorOf(cache_state_.stack_state.end() - num, num)) {
430 Spill(&slot);
431 }
432}
433
435 VarState* stack_base = cache_state_.stack_state.data();
436 for (auto slots :
437 {base::VectorOf(stack_base + cache_state_.stack_state.size() - arity,
438 arity),
439 base::VectorOf(stack_base, num_locals())}) {
440 for (VarState& slot : slots) {
441 if (slot.is_reg()) {
442 // Registers used more than once can't be used for merges.
443 if (cache_state_.get_use_count(slot.reg()) > 1) {
444 RegClass rc = reg_class_for(slot.kind());
445 if (cache_state_.has_unused_register(rc, pinned)) {
447 Move(dst_reg, slot.reg(), slot.kind());
448 cache_state_.inc_used(dst_reg);
449 cache_state_.dec_used(slot.reg());
450 slot.MakeRegister(dst_reg);
451 } else {
452 Spill(slot.offset(), slot.reg(), slot.kind());
453 cache_state_.dec_used(slot.reg());
454 slot.MakeStack();
455 }
456 }
457 continue;
458 }
459 // Materialize constants.
460 if (!slot.is_const()) continue;
461 RegClass rc = reg_class_for(slot.kind());
462 if (cache_state_.has_unused_register(rc, pinned)) {
464 LoadConstant(reg, slot.constant());
466 slot.MakeRegister(reg);
467 } else {
468 Spill(slot.offset(), slot.constant());
469 slot.MakeStack();
470 }
471 }
472 }
473}
474
475#ifdef DEBUG
476namespace {
477bool SlotInterference(const VarState& a, const VarState& b) {
478 return a.is_stack() && b.is_stack() &&
479 b.offset() > a.offset() - value_kind_size(a.kind()) &&
480 b.offset() - value_kind_size(b.kind()) < a.offset();
481}
482
483bool SlotInterference(const VarState& a, base::Vector<const VarState> v) {
484 // Check the first 16 entries in {v}, then increase the step size to avoid
485 // quadratic runtime on huge stacks. This logic checks 41 of the first 100
486 // slots, 77 of the first 1000 and 115 of the first 10000.
487 for (size_t idx = 0, end = v.size(); idx < end; idx += 1 + idx / 16) {
488 if (SlotInterference(a, v[idx])) return true;
489 }
490 return false;
491}
492} // namespace
493#endif
494
496 DCHECK_EQ(cache_state_.stack_height(), target.stack_height());
497 // TODO(clemensb): Reuse the same ParallelMove object to save some
498 // allocations.
499 ParallelMove parallel_move{this};
500 for (uint32_t i = 0, e = cache_state_.stack_height(); i < e; ++i) {
501 parallel_move.Transfer(target.stack_state[i], cache_state_.stack_state[i]);
502 DCHECK(!SlotInterference(target.stack_state[i],
504 }
505
506 // Full stack merging is only done for forward jumps, so we can just clear the
507 // cache registers at the target in case of mismatch.
508 if (cache_state_.cached_instance_data != target.cached_instance_data) {
509 target.ClearCachedInstanceRegister();
510 }
511 if (cache_state_.cached_mem_index != target.cached_mem_index ||
512 cache_state_.cached_mem_start != target.cached_mem_start) {
513 target.ClearCachedMemStartRegister();
514 }
515}
516
517void LiftoffAssembler::MergeStackWith(CacheState& target, uint32_t arity,
518 JumpDirection jump_direction) {
519 // Before: ----------------|----- (discarded) ----|--- arity ---|
520 // ^target_stack_height ^stack_base ^stack_height
521 // After: ----|-- arity --|
522 // ^ ^target_stack_height
523 // ^target_stack_base
524 uint32_t stack_height = cache_state_.stack_height();
525 uint32_t target_stack_height = target.stack_height();
526 DCHECK_LE(target_stack_height, stack_height);
527 DCHECK_LE(arity, target_stack_height);
528 uint32_t stack_base = stack_height - arity;
529 uint32_t target_stack_base = target_stack_height - arity;
530 ParallelMove parallel_move{this};
531 for (uint32_t i = 0; i < target_stack_base; ++i) {
532 parallel_move.Transfer(target.stack_state[i], cache_state_.stack_state[i]);
533 DCHECK(!SlotInterference(
534 target.stack_state[i],
536 target_stack_base - i - 1)));
537 DCHECK(!SlotInterference(
538 target.stack_state[i],
539 base::VectorOf(cache_state_.stack_state.data() + stack_base, arity)));
540 }
541 for (uint32_t i = 0; i < arity; ++i) {
542 parallel_move.Transfer(target.stack_state[target_stack_base + i],
543 cache_state_.stack_state[stack_base + i]);
544 DCHECK(!SlotInterference(
545 target.stack_state[target_stack_base + i],
546 base::VectorOf(cache_state_.stack_state.data() + stack_base + i + 1,
547 arity - i - 1)));
548 }
549
550 // Check whether the cached instance and/or memory start need to be moved to
551 // another register. Register moves are executed as part of the
552 // {ParallelMove}. Remember whether the register content has to be
553 // reloaded after executing the stack parallel_move.
554 bool reload_instance_data = false;
555 // If the instance cache registers match, or the destination has no instance
556 // cache register, nothing needs to be done.
557 if (cache_state_.cached_instance_data != target.cached_instance_data &&
558 target.cached_instance_data != no_reg) {
559 // On forward jumps, just reset the cached register in the target state.
560 if (jump_direction == kForwardJump) {
561 target.ClearCachedInstanceRegister();
563 // If the source has the instance cached but in the wrong register,
564 // execute a register move as part of the stack transfer.
565 parallel_move.MoveRegister(
566 LiftoffRegister{target.cached_instance_data},
568 } else {
569 // Otherwise (the source state has no cached instance), we reload later.
570 reload_instance_data = true;
571 }
572 }
573
574 bool reload_mem_start = false;
575 // If the cached memory start registers match, or the destination has no cache
576 // register, nothing needs to be done.
577 DCHECK_EQ(target.cached_mem_start == no_reg,
578 target.cached_mem_index == CacheState::kNoCachedMemIndex);
579 if ((cache_state_.cached_mem_start != target.cached_mem_start ||
580 cache_state_.cached_mem_index != target.cached_mem_index) &&
581 target.cached_mem_start != no_reg) {
582 // On forward jumps, just reset the cached register in the target state.
583 if (jump_direction == kForwardJump) {
584 target.ClearCachedMemStartRegister();
585 } else if (cache_state_.cached_mem_index == target.cached_mem_index) {
587 // If the source has the content but in the wrong register, execute a
588 // register move as part of the stack transfer.
589 parallel_move.MoveRegister(LiftoffRegister{target.cached_mem_start},
592 } else {
593 // Otherwise (the source state has no cached content), we reload later.
594 reload_mem_start = true;
595 }
596 }
597
598 // Now execute stack transfers and register moves/loads.
599 parallel_move.Execute();
600
601 if (reload_instance_data) {
602 LoadInstanceDataFromFrame(target.cached_instance_data);
603 }
604 if (reload_mem_start) {
605 // {target.cached_instance_data} already got restored above, so we can use
606 // it if it exists.
607 Register instance_data = target.cached_instance_data;
608 if (instance_data == no_reg) {
609 // We don't have the instance data available yet. Store it into the target
610 // mem_start, so that we can load the mem0_start from there.
611 instance_data = target.cached_mem_start;
613 }
614 if (target.cached_mem_index == 0) {
616 target.cached_mem_start, instance_data,
617 ObjectAccess::ToTagged(WasmTrustedInstanceData::kMemory0StartOffset),
618 sizeof(size_t));
619 } else {
621 target.cached_mem_start, instance_data,
623 WasmTrustedInstanceData::kProtectedMemoryBasesAndSizesOffset));
624 int buffer_offset =
626 kSystemPointerSize * target.cached_mem_index * 2;
627 LoadFullPointer(target.cached_mem_start, target.cached_mem_start,
628 buffer_offset);
629 }
630 }
631}
632
634 switch (slot->loc()) {
635 case VarState::kStack:
636 return;
638 Spill(slot->offset(), slot->reg(), slot->kind());
639 cache_state_.dec_used(slot->reg());
640 break;
642 Spill(slot->offset(), slot->constant());
643 break;
644 }
645 slot->MakeStack();
646}
647
649 for (VarState& local_slot :
651 Spill(&local_slot);
652 }
653}
654
656 for (VarState& slot : cache_state_.stack_state) {
657 if (!slot.is_reg()) continue;
658 Spill(slot.offset(), slot.reg(), slot.kind());
659 slot.MakeStack();
660 }
663}
664
666 Register reg, std::initializer_list<Register*> possible_uses,
667 LiftoffRegList pinned) {
670 // We can return immediately. The instance is only used to load information
671 // at the beginning of an instruction when values don't have to be in
672 // specific registers yet. Therefore the instance should never be one of the
673 // {possible_uses}.
674#ifdef DEBUG
675 for (Register* use : possible_uses) DCHECK_NE(reg, *use);
676#endif
677 return;
678 } else if (reg == cache_state()->cached_mem_start) {
680 // The memory start may be among the {possible_uses}, e.g. for an atomic
681 // compare exchange. Therefore it is necessary to iterate over the
682 // {possible_uses} below, and we cannot return early.
683 } else if (cache_state()->is_used(LiftoffRegister(reg))) {
685 }
686 Register replacement = no_reg;
687 for (Register* use : possible_uses) {
688 if (reg != *use) continue;
689 if (replacement == no_reg) {
690 replacement = GetUnusedRegister(kGpReg, pinned).gp();
691 Move(replacement, reg, kIntPtrKind);
692 }
693 // We cannot leave this loop early. There may be multiple uses of {reg}.
694 *use = replacement;
695 }
696}
697
698namespace {
699void PrepareStackTransfers(const ValueKindSig* sig,
700 compiler::CallDescriptor* call_descriptor,
701 const VarState* slots,
703 ParallelMove* parallel_move,
704 LiftoffRegList* param_regs) {
705 // Process parameters backwards, to reduce the amount of Slot sorting for
706 // the most common case - a normal Wasm Call. Slots will be mostly unsorted
707 // in the Builtin call case.
708 uint32_t call_desc_input_idx =
709 static_cast<uint32_t>(call_descriptor->InputCount());
710 uint32_t num_params = static_cast<uint32_t>(sig->parameter_count());
711 for (uint32_t i = num_params; i > 0; --i) {
712 const uint32_t param = i - 1;
713 ValueKind kind = sig->GetParam(param);
714 const bool is_gp_pair = kNeedI64RegPair && kind == kI64;
715 const int num_lowered_params = is_gp_pair ? 2 : 1;
716 const VarState& slot = slots[param];
717 DCHECK(CompatibleStackSlotTypes(slot.kind(), kind));
718 // Process both halfs of a register pair separately, because they are passed
719 // as separate parameters. One or both of them could end up on the stack.
720 for (int lowered_idx = 0; lowered_idx < num_lowered_params; ++lowered_idx) {
721 const RegPairHalf half =
722 is_gp_pair && lowered_idx == 0 ? kHighWord : kLowWord;
723 --call_desc_input_idx;
724 LinkageLocation loc =
725 call_descriptor->GetInputLocation(call_desc_input_idx);
726 if (loc.IsRegister()) {
727 DCHECK(!loc.IsAnyRegister());
728 RegClass rc = is_gp_pair ? kGpReg : reg_class_for(kind);
729 int reg_code = loc.AsRegister();
732 param_regs->set(reg);
733 if (is_gp_pair) {
734 parallel_move->LoadI64HalfIntoRegister(reg, slot, half);
735 } else {
736 parallel_move->LoadIntoRegister(reg, slot);
737 }
738 } else {
740 int param_offset = -loc.GetLocation() - 1;
741 stack_slots->Add(slot, slot.offset(), half, param_offset);
742 }
743 }
744 }
745}
746
747} // namespace
748
750 const ValueKindSig* sig, compiler::CallDescriptor* call_descriptor,
751 std::initializer_list<VarState> params) {
753 ParallelMove parallel_move{this};
754 LiftoffRegList param_regs;
755 PrepareStackTransfers(sig, call_descriptor, params.begin(), &stack_slots,
756 &parallel_move, &param_regs);
758 int param_slots = static_cast<int>(call_descriptor->ParameterSlotCount());
759 if (param_slots > 0) {
760 stack_slots.Construct(param_slots);
761 }
762 // Execute the stack transfers before filling the instance register.
763 parallel_move.Execute();
764
765 // Reset register use counters.
767}
768
770 compiler::CallDescriptor* call_descriptor,
771 Register* target,
772 Register target_instance_data) {
773 ASM_CODE_COMMENT(this);
774 uint32_t num_params = static_cast<uint32_t>(sig->parameter_count());
775
777 ParallelMove parallel_move{this};
778 LiftoffRegList param_regs;
779
780 // Move the target instance (if supplied) into the correct instance register.
781 Register instance_reg = wasm::kGpParamRegisters[0];
782 // Check that the call descriptor agrees. Input 0 is the call target, 1 is the
783 // instance.
784 DCHECK_EQ(
785 instance_reg,
786 Register::from_code(call_descriptor->GetInputLocation(1).AsRegister()));
787 param_regs.set(instance_reg);
788 if (target_instance_data == no_reg) {
789 target_instance_data = cache_state_.cached_instance_data;
790 }
791 if (target_instance_data != no_reg && target_instance_data != instance_reg) {
792 parallel_move.MoveRegister(LiftoffRegister(instance_reg),
793 LiftoffRegister(target_instance_data),
795 }
796
797 int param_slots = static_cast<int>(call_descriptor->ParameterSlotCount());
798 if (num_params) {
799 uint32_t param_base = cache_state_.stack_height() - num_params;
800 PrepareStackTransfers(sig, call_descriptor,
801 &cache_state_.stack_state[param_base], &stack_slots,
802 &parallel_move, &param_regs);
803 }
804
805 // If the target register overlaps with a parameter register, then move the
806 // target to another free register, or spill to the stack.
807 if (target && param_regs.has(LiftoffRegister(*target))) {
808 // Try to find another free register.
809 LiftoffRegList free_regs = kGpCacheRegList.MaskOut(param_regs);
810 static_assert(sizeof(WasmCodePointer) == kUInt32Size);
811 if (!free_regs.is_empty()) {
813 parallel_move.MoveRegister(new_target, LiftoffRegister(*target), kI32);
814 *target = new_target.gp();
815 } else {
816 stack_slots.Add(VarState(kI32, LiftoffRegister(*target), 0), param_slots);
817 param_slots++;
818 *target = no_reg;
819 }
820 }
821
822 // After figuring out all register and stack moves, drop the parameter slots
823 // from the stack.
824 DropValues(num_params);
825
826 // Spill all remaining cache slots.
828 // Iterate backwards, spilling register slots until all registers are free.
830 for (auto* slot = cache_state_.stack_state.end() - 1;; --slot) {
831 DCHECK_LE(cache_state_.stack_state.begin(), slot);
832 if (!slot->is_reg()) continue;
833 Spill(slot->offset(), slot->reg(), slot->kind());
834 cache_state_.dec_used(slot->reg());
835 slot->MakeStack();
837 }
838 }
839 // All slots are either spilled on the stack, or hold constants now.
840 DCHECK(std::all_of(
842 [](const VarState& slot) { return slot.is_stack() || slot.is_const(); }));
843
844 if (param_slots > 0) {
845 stack_slots.Construct(param_slots);
846 }
847 // Execute the stack transfers before filling the instance register.
848 parallel_move.Execute();
849
850 // Reload the instance from the stack if we do not have it in a register.
851 if (target_instance_data == no_reg) {
852 LoadInstanceDataFromFrame(instance_reg);
853 }
854}
855
856namespace {
857constexpr LiftoffRegList AllReturnRegs() {
861 return result;
862}
863} // namespace
864
866 compiler::CallDescriptor* call_descriptor) {
867 int call_desc_return_idx = 0;
868 for (ValueKind return_kind : sig->returns()) {
869 DCHECK_LT(call_desc_return_idx, call_descriptor->ReturnCount());
870 const bool needs_gp_pair = needs_gp_reg_pair(return_kind);
871 const int num_lowered_params = 1 + needs_gp_pair;
872 const ValueKind lowered_kind = needs_gp_pair ? kI32 : return_kind;
873 const RegClass rc = reg_class_for(lowered_kind);
874 // Initialize to anything, will be set in the loop and used afterwards.
877 // Make sure not to clobber results in registers (which might not be the
878 // first values to be processed) prematurely.
879 LiftoffRegList pinned = AllReturnRegs();
880 for (int pair_idx = 0; pair_idx < num_lowered_params; ++pair_idx) {
881 LinkageLocation loc =
882 call_descriptor->GetReturnLocation(call_desc_return_idx++);
883 if (loc.IsRegister()) {
884 DCHECK(!loc.IsAnyRegister());
885 reg_pair[pair_idx] = LiftoffRegister::from_external_code(
886 rc, lowered_kind, loc.AsRegister());
887 } else {
889 reg_pair[pair_idx] = GetUnusedRegister(rc, pinned);
890 // Get slot offset relative to the stack pointer.
891 int offset = call_descriptor->GetOffsetToReturns();
892 int return_slot = -loc.GetLocation() - offset - 1;
893 LoadReturnStackSlot(reg_pair[pair_idx],
894 return_slot * kSystemPointerSize, lowered_kind);
895 }
896 if (pair_idx == 0) {
897 pinned.set(reg_pair[0]);
898 }
899 }
900 if (num_lowered_params == 1) {
901 PushRegister(return_kind, reg_pair[0]);
902 } else {
903 PushRegister(return_kind, LiftoffRegister::ForPair(reg_pair[0].gp(),
904 reg_pair[1].gp()));
905 }
906 }
907 int return_slots = static_cast<int>(call_descriptor->ReturnSlotCount());
909}
910
912 ValueKind kind) {
913 DCHECK_EQ(dst.reg_class(), src.reg_class());
914 DCHECK_NE(dst, src);
915 if (kNeedI64RegPair && dst.is_gp_pair()) {
916 // Use the {ParallelMove} to move pairs, as the registers in the
917 // pairs might overlap.
918 ParallelMove{this}.MoveRegister(dst, src, kind);
919 } else if (kNeedS128RegPair && dst.is_fp_pair()) {
920 // Calling low_fp is fine, Move will automatically check the kind and
921 // convert this FP to its SIMD register, and use a SIMD move.
922 Move(dst.low_fp(), src.low_fp(), kind);
923 } else if (dst.is_gp()) {
924 Move(dst.gp(), src.gp(), kind);
925 } else {
926 Move(dst.fp(), src.fp(), kind);
927 }
928}
929
932 ParallelMove parallel_move{this};
933 for (auto tuple : tuples) {
934 if (tuple.dst == tuple.src) continue;
935 parallel_move.MoveRegister(tuple.dst, tuple.src, tuple.kind);
936 }
937}
938
940 const FunctionSig* sig, compiler::CallDescriptor* descriptor) {
941 DCHECK_LT(0, sig->return_count());
942 if (V8_UNLIKELY(sig->return_count() > 1)) {
944 return;
945 }
946
947 ValueKind return_kind = sig->GetReturn(0).kind();
948 // Defaults to a gp reg, will be set below if return kind is not gp.
950
951 if (needs_gp_reg_pair(return_kind)) {
952 return_reg =
954 } else if (needs_fp_reg_pair(return_kind)) {
956 } else if (reg_class_for(return_kind) == kFpReg) {
957 return_reg = LiftoffRegister(kFpReturnRegisters[0]);
958 } else {
959 DCHECK_EQ(kGpReg, reg_class_for(return_kind));
960 }
961 VarState& slot = cache_state_.stack_state.back();
962 if (V8_LIKELY(slot.is_reg())) {
963 if (slot.reg() != return_reg) {
964 Move(return_reg, slot.reg(), slot.kind());
965 }
966 } else {
967 LoadToFixedRegister(cache_state_.stack_state.back(), return_reg);
968 }
969}
970
972 const FunctionSig* sig, compiler::CallDescriptor* descriptor) {
973 DCHECK_LT(1, sig->return_count());
974 ParallelMove parallel_move{this};
975
976 // We sometimes allocate a register to perform stack-to-stack moves, which can
977 // cause a spill in the cache state. Conservatively save and restore the
978 // original state in case it is needed after the current instruction
979 // (conditional branch).
980 CacheState saved_state{zone()};
981#if DEBUG
982 uint32_t saved_state_frozenness = cache_state_.frozen;
983 cache_state_.frozen = 0;
984#endif
985 saved_state.Split(*cache_state());
986 int call_desc_return_idx = 0;
987 DCHECK_LE(sig->return_count(), cache_state_.stack_height());
988 VarState* slots = cache_state_.stack_state.end() - sig->return_count();
989 LiftoffRegList pinned;
990 Register old_fp = LoadOldFramePointer();
991 if (v8_flags.experimental_wasm_growable_stacks) {
992 pinned.set(LiftoffRegister(old_fp));
993 }
994 // Fill return frame slots first to ensure that all potential spills happen
995 // before we prepare the stack transfers.
996 for (size_t i = 0; i < sig->return_count(); ++i) {
997 ValueKind return_kind = sig->GetReturn(i).kind();
998 bool needs_gp_pair = needs_gp_reg_pair(return_kind);
999 int num_lowered_params = 1 + needs_gp_pair;
1000 for (int pair_idx = 0; pair_idx < num_lowered_params; ++pair_idx) {
1001 LinkageLocation loc =
1002 descriptor->GetReturnLocation(call_desc_return_idx++);
1003 if (loc.IsCallerFrameSlot()) {
1004 RegPairHalf half = pair_idx == 0 ? kLowWord : kHighWord;
1005 VarState& slot = slots[i];
1006 LiftoffRegister reg = needs_gp_pair
1007 ? LoadI64HalfIntoRegister(slot, half, pinned)
1008 : LoadToRegister(slot, pinned);
1009 ValueKind lowered_kind = needs_gp_pair ? kI32 : return_kind;
1010 StoreCallerFrameSlot(reg, -loc.AsCallerFrameSlot(), lowered_kind,
1011 old_fp);
1012 }
1013 }
1014 }
1015 // Prepare and execute stack transfers.
1016 call_desc_return_idx = 0;
1017 for (size_t i = 0; i < sig->return_count(); ++i) {
1018 ValueKind return_kind = sig->GetReturn(i).kind();
1019 bool needs_gp_pair = needs_gp_reg_pair(return_kind);
1020 int num_lowered_params = 1 + needs_gp_pair;
1021 for (int pair_idx = 0; pair_idx < num_lowered_params; ++pair_idx) {
1022 RegPairHalf half = pair_idx == 0 ? kLowWord : kHighWord;
1023 LinkageLocation loc =
1024 descriptor->GetReturnLocation(call_desc_return_idx++);
1025 if (loc.IsRegister()) {
1026 DCHECK(!loc.IsAnyRegister());
1027 int reg_code = loc.AsRegister();
1028 ValueKind lowered_kind = needs_gp_pair ? kI32 : return_kind;
1029 RegClass rc = reg_class_for(lowered_kind);
1031 LiftoffRegister::from_external_code(rc, return_kind, reg_code);
1032 VarState& slot = slots[i];
1033 if (needs_gp_pair) {
1034 parallel_move.LoadI64HalfIntoRegister(reg, slot, half);
1035 } else {
1036 parallel_move.LoadIntoRegister(reg, slot);
1037 }
1038 }
1039 }
1040 }
1041 cache_state()->Steal(saved_state);
1042#if DEBUG
1043 cache_state_.frozen = saved_state_frozenness;
1044#endif
1045}
1046
1047#if DEBUG
1048void LiftoffRegList::Print() const {
1049 std::ostringstream os;
1050 os << *this << "\n";
1051 PrintF("%s", os.str().c_str());
1052}
1053#endif
1054
1055#ifdef ENABLE_SLOW_DCHECKS
1056bool LiftoffAssembler::ValidateCacheState() const {
1057 uint32_t register_use_count[kAfterMaxLiftoffRegCode] = {0};
1058 LiftoffRegList used_regs;
1060 for (const VarState& var : cache_state_.stack_state) {
1061 // Check for continuous stack offsets.
1062 offset = NextSpillOffset(var.kind(), offset);
1063 DCHECK_EQ(offset, var.offset());
1064 if (!var.is_reg()) continue;
1065 LiftoffRegister reg = var.reg();
1066 if ((kNeedI64RegPair || kNeedS128RegPair) && reg.is_pair()) {
1067 ++register_use_count[reg.low().liftoff_code()];
1068 ++register_use_count[reg.high().liftoff_code()];
1069 } else {
1070 ++register_use_count[reg.liftoff_code()];
1071 }
1072 used_regs.set(reg);
1073 }
1074 for (Register cache_reg :
1076 if (cache_reg != no_reg) {
1077 DCHECK(!used_regs.has(cache_reg));
1078 int liftoff_code = LiftoffRegister{cache_reg}.liftoff_code();
1079 used_regs.set(cache_reg);
1080 DCHECK_EQ(0, register_use_count[liftoff_code]);
1081 register_use_count[liftoff_code] = 1;
1082 }
1083 }
1084 bool valid = memcmp(register_use_count, cache_state_.register_use_count,
1085 sizeof(register_use_count)) == 0 &&
1086 used_regs == cache_state_.used_registers;
1087 if (valid) return true;
1088 std::ostringstream os;
1089 os << "Error in LiftoffAssembler::ValidateCacheState().\n";
1090 os << "expected: used_regs " << used_regs << ", counts "
1091 << PrintCollection(register_use_count) << "\n";
1092 os << "found: used_regs " << cache_state_.used_registers << ", counts "
1094 os << "Use --trace-wasm-decoder and --trace-liftoff to debug.";
1095 FATAL("%s", os.str().c_str());
1096}
1097#endif
1098
1100 // Before spilling a regular stack slot, try to drop a "volatile" register
1101 // (used for caching the memory start or the instance itself). Those can be
1102 // reloaded without requiring a spill here.
1105 }
1106
1108 SpillRegister(spilled_reg);
1109 return spilled_reg;
1110}
1111
1113 LiftoffRegList pinned) {
1114 // We end up in this call only when:
1115 // [1] kNeedS128RegPair, and
1116 // [2] there are no pair of adjacent FP registers that are free
1121
1122 // Special logic, if the top fp register is even, we might hit a case of an
1123 // invalid register in case 2.
1125 if (last_fp.fp().code() % 2 == 0) {
1126 pinned.set(last_fp);
1127 }
1128 // If half of an adjacent pair is pinned, consider the whole pair pinned.
1129 // Otherwise the code below would potentially spill the pinned register
1130 // (after first spilling the unpinned half of the pair).
1131 pinned = pinned.SpreadSetBitsToAdjacentFpRegs();
1132
1133 // We can try to optimize the spilling here:
1134 // 1. Try to get a free fp register, either:
1135 // a. This register is already free, or
1136 // b. it had to be spilled.
1137 // 2. If 1a, the adjacent register is used (invariant [2]), spill it.
1138 // 3. If 1b, check the adjacent register:
1139 // a. If free, done!
1140 // b. If used, spill it.
1141 // We spill one register in 2 and 3a, and two registers in 3b.
1142
1143 LiftoffRegister first_reg = GetUnusedRegister(kFpReg, pinned);
1144 LiftoffRegister second_reg = first_reg, low_reg = first_reg;
1145
1146 if (first_reg.fp().code() % 2 == 0) {
1147 second_reg =
1149 } else {
1150 second_reg =
1152 low_reg = second_reg;
1153 }
1154
1155 if (cache_state_.is_used(second_reg)) {
1156 SpillRegister(second_reg);
1157 }
1158
1159 return low_reg;
1160}
1161
1163 DCHECK(!cache_state_.frozen);
1164 int remaining_uses = cache_state_.get_use_count(reg);
1165 DCHECK_LT(0, remaining_uses);
1166 for (uint32_t idx = cache_state_.stack_height() - 1;; --idx) {
1168 auto* slot = &cache_state_.stack_state[idx];
1169 if (!slot->is_reg() || !slot->reg().overlaps(reg)) continue;
1170 if (slot->reg().is_pair()) {
1171 // Make sure to decrement *both* registers in a pair, because the
1172 // {clear_used} call below only clears one of them.
1173 cache_state_.dec_used(slot->reg().low());
1174 cache_state_.dec_used(slot->reg().high());
1177 }
1178 Spill(slot->offset(), slot->reg(), slot->kind());
1179 slot->MakeStack();
1180 if (--remaining_uses == 0) break;
1181 }
1184}
1185
1186void LiftoffAssembler::set_num_locals(uint32_t num_locals) {
1187 DCHECK_EQ(0, num_locals_); // only call this once.
1190 more_local_kinds_ = reinterpret_cast<ValueKind*>(
1191 base::Malloc(num_locals * sizeof(ValueKind)));
1193 }
1194}
1195
1196std::ostream& operator<<(std::ostream& os, LiftoffVarState slot) {
1197 os << name(slot.kind()) << ":";
1198 switch (slot.loc()) {
1200 return os << "s0x" << std::hex << slot.offset() << std::dec;
1202 return os << slot.reg();
1204 return os << "c" << slot.i32_const();
1205 }
1206 UNREACHABLE();
1207}
1208
1209#if DEBUG
1210bool CompatibleStackSlotTypes(ValueKind a, ValueKind b) {
1211 // Since Liftoff doesn't do accurate type tracking (e.g. on loop back edges,
1212 // ref.as_non_null/br_on_cast results), we only care that pointer types stay
1213 // amongst pointer types. It's fine if ref/ref null overwrite each other.
1214 return a == b || (is_object_reference(a) && is_object_reference(b));
1215}
1216#endif
1217
1218} // namespace v8::internal::wasm
Builtins::Kind kind
Definition builtins.cc:40
constexpr size_t size() const
Definition vector.h:70
Simd128Register Simd128Register Simd128Register Simd128Register rc
static constexpr int kFixedFrameSizeAboveFp
NO_INLINE_FOR_ARM64_MSVC bool IsRegister() const
constexpr int8_t code() const
static constexpr Register from_code(int code)
LinkageLocation GetReturnLocation(size_t index) const
Definition linkage.h:270
LinkageLocation GetInputLocation(size_t index) const
Definition linkage.h:274
void ClearRegister(Register reg, std::initializer_list< Register * > possible_uses, LiftoffRegList pinned)
static constexpr ValueKind kSmiKind
static constexpr uint32_t kInlineLocalKinds
V8_NOINLINE V8_PRESERVE_MOST LiftoffRegister LoadToRegister_Slow(VarState slot, LiftoffRegList pinned)
void FillI64Half(Register, int offset, RegPairHalf)
LiftoffAssembler(Zone *, std::unique_ptr< AssemblerBuffer >)
V8_NODISCARD CacheState MergeIntoNewState(uint32_t num_locals, uint32_t arity, uint32_t stack_depth)
void MergeStackWith(CacheState &target, uint32_t arity, JumpDirection)
void LoadFullPointer(Register dst, Register src_addr, int32_t offset_imm)
static V8_INLINE int NextSpillOffset(ValueKind kind, int top_spill_offset)
V8_INLINE LiftoffRegister LoadToRegister(VarState slot, LiftoffRegList pinned)
void PrepareCall(const ValueKindSig *, compiler::CallDescriptor *, Register *target=nullptr, Register target_instance=no_reg)
void StoreCallerFrameSlot(LiftoffRegister, uint32_t caller_slot_idx, ValueKind, Register frame_pointer)
LiftoffRegister LoadI64HalfIntoRegister(VarState slot, RegPairHalf half, LiftoffRegList pinned)
void PrepareForBranch(uint32_t arity, LiftoffRegList pinned)
void LoadConstant(LiftoffRegister, WasmValue)
void LoadFromInstance(Register dst, Register instance, int offset, int size)
void LoadProtectedPointer(Register dst, Register src_addr, int32_t offset)
LiftoffRegister SpillAdjacentFpRegisters(LiftoffRegList pinned)
void ParallelRegisterMove(base::Vector< const ParallelRegisterMoveTuple >)
void MoveStackValue(uint32_t dst_offset, uint32_t src_offset, ValueKind)
void Move(LiftoffRegister dst, LiftoffRegister src, ValueKind)
void PushRegister(ValueKind kind, LiftoffRegister reg)
void FinishCall(const ValueKindSig *, compiler::CallDescriptor *)
void MoveToReturnLocations(const FunctionSig *, compiler::CallDescriptor *)
void LoadToFixedRegister(VarState slot, LiftoffRegister reg)
LiftoffRegister GetUnusedRegister(RegClass rc, std::initializer_list< LiftoffRegister > try_first, LiftoffRegList pinned)
void MergeFullStackWith(CacheState &target)
V8_NOINLINE V8_PRESERVE_MOST LiftoffRegister SpillOneRegister(LiftoffRegList candidates)
static constexpr ValueKind kIntPtrKind
void PrepareBuiltinCall(const ValueKindSig *sig, compiler::CallDescriptor *call_descriptor, std::initializer_list< VarState > params)
void LoadReturnStackSlot(LiftoffRegister, int offset, ValueKind)
V8_NOINLINE V8_PRESERVE_MOST void MoveToReturnLocationsMultiReturn(const FunctionSig *, compiler::CallDescriptor *)
V8_NOINLINE V8_PRESERVE_MOST void SpillRegister(LiftoffRegister)
constexpr Register set(Register reg)
LiftoffRegister GetLastRegSet() const
LiftoffRegList MaskOut(const LiftoffRegList mask) const
constexpr bool HasAdjacentFpRegsSet() const
bool has(LiftoffRegister reg) const
LiftoffRegister GetFirstRegSet() const
constexpr LiftoffRegList SpreadSetBitsToAdjacentFpRegs() const
constexpr DoubleRegister fp() const
static LiftoffRegister from_liftoff_code(int code)
static LiftoffRegister ForFpPair(DoubleRegister low)
bool overlaps(const LiftoffRegister other) const
constexpr RegClass reg_class() const
static LiftoffRegister from_external_code(RegClass rc, ValueKind kind, int code)
static LiftoffRegister ForPair(Register low, Register high)
static constexpr int ToTagged(int offset)
void MoveRegister(LiftoffRegister dst, LiftoffRegister src, ValueKind kind)
V8_INLINE void LoadIntoRegister(LiftoffRegister dst, const VarState &src)
void LoadI64HalfIntoRegister(LiftoffRegister dst, const VarState &src, RegPairHalf half)
#define ASM_CODE_COMMENT(asm)
Definition assembler.h:617
const MapRef map_
int end
ZoneVector< OpIndex > candidates
LineAndColumn previous
DirectHandle< Object > new_target
Definition execution.cc:75
int32_t offset
TNode< Object > target
ZoneVector< RpoNumber > & result
LiftoffRegister reg
LiftoffRegList spills
Register cached_instance_data
base::SmallVector< int32_t, 1 > stack_slots
InstructionOperand source
int r
Definition mul-fft.cc:298
STL namespace.
auto Reversed(T &t)
Definition iterator.h:105
constexpr Vector< T > VectorOf(T *start, size_t size)
Definition vector.h:360
void * Malloc(size_t size)
Definition memory.h:36
void Free(void *memory)
Definition memory.h:63
static constexpr RegClass reg_class_for(ValueKind kind)
constexpr DoubleRegister kFpReturnRegisters[]
constexpr Register kGpParamRegisters[]
static constexpr bool needs_gp_reg_pair(ValueKind kind)
static constexpr bool needs_fp_reg_pair(ValueKind kind)
LiftoffAssembler::VarState VarState
static constexpr int kAfterMaxLiftoffRegCode
static constexpr bool kNeedS128RegPair
constexpr Register kGpReturnRegisters[]
constexpr bool is_object_reference(ValueKind kind)
constexpr int value_kind_size(ValueKind kind)
static constexpr LiftoffRegList kGpCacheRegList
std::ostream & operator<<(std::ostream &os, LiftoffVarState slot)
static constexpr LiftoffRegList kFpCacheRegList
constexpr bool is_reference(ValueKind kind)
static constexpr bool kNeedI64RegPair
constexpr Register no_reg
void PrintF(const char *format,...)
Definition utils.cc:39
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
V8_EXPORT_PRIVATE FlagValues v8_flags
constexpr int kUInt32Size
Definition globals.h:403
auto PrintCollection(const T &collection) -> PrintIteratorRange< typename std::common_type< decltype(std::begin(collection)), decltype(std::end(collection))>::type >
Definition ostreams.h:186
#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(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define DCHECK_GT(v1, v2)
Definition logging.h:487
uint32_t register_use_count[kAfterMaxLiftoffRegCode]
LiftoffRegister GetNextSpillReg(LiftoffRegList candidates)
LiftoffRegister unused_register(RegClass rc, LiftoffRegList pinned={}) const
LiftoffRegister take_volatile_register(LiftoffRegList candidates)
void DefineSafepoint(SafepointTableBuilder::Safepoint &safepoint)
void GetTaggedSlotsForOOLCode(ZoneVector< int > *slots, LiftoffRegList *spills, SpillLocation spill_location)
void DefineSafepointWithCalleeSavedRegisters(SafepointTableBuilder::Safepoint &safepoint)
bool has_unused_register(RegClass rc, LiftoffRegList pinned={}) const
uint32_t get_use_count(LiftoffRegister reg) const
bool has_volatile_register(LiftoffRegList candidates)
#define OFFSET_OF_DATA_START(Type)
#define V8_LIKELY(condition)
Definition v8config.h:661
#define V8_UNLIKELY(condition)
Definition v8config.h:660