v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
instruction-selector-x64.cc
Go to the documentation of this file.
1// Copyright 2014 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
6#include <cstdint>
7#include <limits>
8#include <optional>
9
10#include "src/base/bounds.h"
11#include "src/base/iterator.h"
12#include "src/base/logging.h"
17#include "src/common/globals.h"
30#include "src/roots/roots-inl.h"
31
32#if V8_ENABLE_WEBASSEMBLY
34#endif // V8_ENABLE_WEBASSEMBLY
35
36namespace v8 {
37namespace internal {
38namespace compiler {
39
40using namespace turboshaft;
41// using OpIndex;
42// using OptionalOpIndex;
43
44namespace {
45
46bool IsCompressed(InstructionSelectorT* selector, OpIndex node) {
47 if (!node.valid()) return false;
48 if (selector->is_load(node)) {
49 auto load = selector->load_view(node);
50 return load.loaded_rep().IsCompressed();
51 } else if (const PhiOp* phi = selector->TryCast<PhiOp>(node)) {
52 MachineRepresentation phi_rep = phi->rep.machine_representation();
53 return phi_rep == MachineRepresentation::kCompressed ||
55 }
56 return false;
57}
58
59#ifdef DEBUG
60// {left_idx} and {right_idx} are assumed to be the inputs of a commutative
61// binop. This function checks that {left_idx} is not the only constant input of
62// this binop (since the graph should have been normalized before, putting
63// constants on the right input of binops when possible).
64bool LhsIsNotOnlyConstant(Graph* graph, OpIndex left_idx, OpIndex right_idx) {
65 const Operation& left = graph->Get(left_idx);
66 const Operation& right = graph->Get(right_idx);
67
68 if (right.Is<ConstantOp>()) {
69 // There is a constant on the right.
70 return true;
71 }
72 if (left.Is<ConstantOp>()) {
73 // Constant on the left but not on the right.
74 return false;
75 }
76
77 // Left is not a constant
78 return true;
79}
80
81#endif
82
83} // namespace
84
85// TODO(dmercadier): consider taking a DisplacementMode as input so that we can
86// be more permissive towards min_int.
87bool ValueFitsIntoImmediate(int64_t value) {
88 // int32_t min will overflow if displacement mode is kNegativeDisplacement.
89 constexpr int64_t kImmediateMin = std::numeric_limits<int32_t>::min() + 1;
90 constexpr int64_t kImmediateMax = std::numeric_limits<int32_t>::max();
91 static_assert(kImmediateMin ==
93 static_assert(kImmediateMax ==
95 return kImmediateMin <= value && value <= kImmediateMax;
96}
97
99 // TODO(dmercadier): this is not in sync with GetImmediateIntegerValue, which
100 // is surprising because we often use the pattern
101 // `if (CanBeImmediate()) { GetImmediateIntegerValue }`. We should make sure
102 // that both functions are in sync.
103 const Operation& op = selector->Get(node);
104 if (!op.Is<ConstantOp>()) return false;
105 const ConstantOp& constant = op.Cast<ConstantOp>();
106 switch (constant.kind) {
107 case ConstantOp::Kind::kCompressedHeapObject: {
108 if (!COMPRESS_POINTERS_BOOL) return false;
109 // For builtin code we need static roots
110 if (selector->isolate()->bootstrapper() && !V8_STATIC_ROOTS_BOOL) {
111 return false;
112 }
113 const RootsTable& roots_table = selector->isolate()->roots_table();
114 RootIndex root_index;
115 Handle<HeapObject> value = constant.handle();
116 if (roots_table.IsRootHandle(value, &root_index)) {
117 return RootsTable::IsReadOnly(root_index);
118 }
119 return false;
120 }
121 case ConstantOp::Kind::kWord32: {
122 const int32_t value = constant.word32();
123 // int32_t min will overflow if displacement mode is
124 // kNegativeDisplacement.
125 return value != std::numeric_limits<int32_t>::min();
126 }
127 case ConstantOp::Kind::kWord64: {
128 const int64_t value = constant.word64();
129 return ValueFitsIntoImmediate(value);
130 }
131 case ConstantOp::Kind::kSmi: {
132 if (Is64()) {
133 const int64_t value = constant.smi().ptr();
134 return ValueFitsIntoImmediate(value);
135 } else {
136 const int32_t value = constant.smi().ptr();
137 // int32_t min will overflow if displacement mode is
138 // kNegativeDisplacement.
139 return value != std::numeric_limits<int32_t>::min();
140 }
141 }
142 case ConstantOp::Kind::kNumber:
143 return constant.number().get_bits() == 0;
144 default:
145 return false;
146 }
147}
148
150 DCHECK(CanBeImmediate(selector, node));
151 const ConstantOp& constant = selector->Get(node).Cast<ConstantOp>();
152 switch (constant.kind) {
153 case ConstantOp::Kind::kWord32:
154 return constant.word32();
155 case ConstantOp::Kind::kWord64:
156 return static_cast<int32_t>(constant.word64());
157 case ConstantOp::Kind::kSmi:
158 return static_cast<int32_t>(constant.smi().ptr());
159 case ConstantOp::Kind::kNumber:
160 DCHECK_EQ(constant.number().get_bits(), 0);
161 return 0;
162 default:
163 UNREACHABLE();
164 }
165}
166
167struct ScaledIndexMatch {
170 int scale;
171};
172
174 OpIndex* index, int* scale, bool* power_of_two_plus_one) {
175 DCHECK_NOT_NULL(index);
177
178 auto MatchScaleConstant = [](const Operation& op, int& scale,
179 bool* plus_one) {
180 const ConstantOp* constant = op.TryCast<ConstantOp>();
181 if (constant == nullptr) return false;
182 if (constant->kind != ConstantOp::Kind::kWord32 &&
183 constant->kind != ConstantOp::Kind::kWord64) {
184 return false;
185 }
186 uint64_t value = constant->integral();
187 if (plus_one) *plus_one = false;
188 if (value == 1) return (scale = 0), true;
189 if (value == 2) return (scale = 1), true;
190 if (value == 4) return (scale = 2), true;
191 if (value == 8) return (scale = 3), true;
192 if (plus_one == nullptr) return false;
193 *plus_one = true;
194 if (value == 3) return (scale = 1), true;
195 if (value == 5) return (scale = 2), true;
196 if (value == 9) return (scale = 3), true;
197 return false;
198 };
199
200 const Operation& op = selector->Get(node);
201 if (const WordBinopOp* binop = op.TryCast<WordBinopOp>()) {
202 if (binop->kind != WordBinopOp::Kind::kMul) return false;
203 if (MatchScaleConstant(selector->Get(binop->right()), *scale,
204 power_of_two_plus_one)) {
205 *index = binop->left();
206 return true;
207 }
208 if (MatchScaleConstant(selector->Get(binop->left()), *scale,
209 power_of_two_plus_one)) {
210 *index = binop->right();
211 return true;
212 }
213 return false;
214 } else if (const ShiftOp* shift = op.TryCast<ShiftOp>()) {
215 if (shift->kind != ShiftOp::Kind::kShiftLeft) return false;
216 int64_t scale_value;
217 if (selector->MatchSignedIntegralConstant(shift->right(), &scale_value)) {
218 if (scale_value < 0 || scale_value > 3) return false;
219 *index = shift->left();
220 *scale = static_cast<int>(scale_value);
221 if (power_of_two_plus_one) *power_of_two_plus_one = false;
222 return true;
223 }
224 }
225 return false;
226}
227
228std::optional<ScaledIndexMatch> TryMatchScaledIndex(
229 InstructionSelectorT* selector, OpIndex node,
230 bool allow_power_of_two_plus_one) {
231 ScaledIndexMatch match;
232 bool plus_one = false;
233 if (MatchScaledIndex(selector, node, &match.index, &match.scale,
234 allow_power_of_two_plus_one ? &plus_one : nullptr)) {
235 match.base = plus_one ? match.index : OpIndex{};
236 return match;
237 }
238 return std::nullopt;
239}
240
241std::optional<ScaledIndexMatch> TryMatchScaledIndex32(
242 InstructionSelectorT* selector, OpIndex node,
243 bool allow_power_of_two_plus_one) {
244 return TryMatchScaledIndex(selector, node, allow_power_of_two_plus_one);
245}
246
247std::optional<ScaledIndexMatch> TryMatchScaledIndex64(
248 InstructionSelectorT* selector, OpIndex node,
249 bool allow_power_of_two_plus_one) {
250 return TryMatchScaledIndex(selector, node, allow_power_of_two_plus_one);
251}
252
253struct BaseWithScaledIndexAndDisplacementMatch {
254 OpIndex base = {};
255 OpIndex index = {};
256 int scale = 0;
257 int64_t displacement = 0;
259};
260
261std::optional<BaseWithScaledIndexAndDisplacementMatch>
263 InstructionSelectorT* selector, OpIndex left, OpIndex right,
264 bool is_commutative);
265
266std::optional<BaseWithScaledIndexAndDisplacementMatch>
268 OpIndex node) {
269 // The BaseWithIndexAndDisplacementMatcher canonicalizes the order of
270 // displacements and scale factors that are used as inputs, so instead of
271 // enumerating all possible patterns by brute force, checking for node
272 // clusters using the following templates in the following order suffices
273 // to find all of the interesting cases (S = index * scale, B = base
274 // input, D = displacement input):
275 //
276 // (S + (B + D))
277 // (S + (B + B))
278 // (S + D)
279 // (S + B)
280 // ((S + D) + B)
281 // ((S + B) + D)
282 // ((B + D) + B)
283 // ((B + B) + D)
284 // (B + D)
285 // (B + B)
286 BaseWithScaledIndexAndDisplacementMatch result;
287 result.displacement_mode = kPositiveDisplacement;
288
289 const Operation& op = selector->Get(node);
290 if (const LoadOp* load = op.TryCast<LoadOp>()) {
291 result.base = load->base();
292 result.index = load->index().value_or_invalid();
293 result.scale = load->element_size_log2;
294 result.displacement = load->offset;
295 if (load->kind.tagged_base) result.displacement -= kHeapObjectTag;
296 return result;
297 } else if (const StoreOp* store = op.TryCast<StoreOp>()) {
298 result.base = store->base();
299 result.index = store->index().value_or_invalid();
300 result.scale = store->element_size_log2;
301 result.displacement = store->offset;
302 if (store->kind.tagged_base) result.displacement -= kHeapObjectTag;
303 return result;
304 } else if (op.Is<WordBinopOp>()) {
305 // Nothing to do here, fall into the case below.
306#ifdef V8_ENABLE_WEBASSEMBLY
307 } else if (const Simd128LaneMemoryOp* lane_op =
308 op.TryCast<Simd128LaneMemoryOp>()) {
309 result.base = lane_op->base();
310 result.index = lane_op->index();
311 result.scale = 0;
312 result.displacement = 0;
313 if (lane_op->kind.tagged_base) result.displacement -= kHeapObjectTag;
314 return result;
315 } else if (const Simd128LoadTransformOp* load_transform_128 =
316 op.TryCast<Simd128LoadTransformOp>()) {
317 result.base = load_transform_128->base();
318 DCHECK_EQ(load_transform_128->offset, 0);
319
320 if (CanBeImmediate(selector, load_transform_128->index())) {
321 result.index = {};
322 result.displacement =
323 GetImmediateIntegerValue(selector, load_transform_128->index());
324 } else {
325 result.index = load_transform_128->index();
326 result.displacement = 0;
327 }
328
329 result.scale = 0;
330 DCHECK(!load_transform_128->load_kind.tagged_base);
331 return result;
332#if V8_ENABLE_WASM_SIMD256_REVEC
333 } else if (const Simd256LoadTransformOp* load_transform_256 =
334 op.TryCast<Simd256LoadTransformOp>()) {
335 result.base = load_transform_256->base();
336 result.index = load_transform_256->index();
337 DCHECK_EQ(load_transform_256->offset, 0);
338 result.scale = 0;
339 result.displacement = 0;
340 DCHECK(!load_transform_256->load_kind.tagged_base);
341 return result;
342#endif // V8_ENABLE_WASM_SIMD256_REVEC
343#endif // V8_ENABLE_WEBASSEMBLY
344 } else {
345 return std::nullopt;
346 }
347
348 const WordBinopOp& binop = op.Cast<WordBinopOp>();
349 OpIndex left = binop.left();
350 OpIndex right = binop.right();
352 selector, left, right, binop.IsCommutative());
353}
354
355std::optional<BaseWithScaledIndexAndDisplacementMatch>
357 InstructionSelectorT* selector, OpIndex left, OpIndex right,
358 bool is_commutative) {
359 // In the comments of this function, the following letters have the following
360 // meaning:
361 //
362 // S: scaled index. That is, "OpIndex * constant" or "OpIndex << constant",
363 // where "constant" is a small power of 2 (1, 2, 4, 8 for the
364 // multiplication, 0, 1, 2 or 3 for the shift). The "constant" is called
365 // "scale" in the BaseWithScaledIndexAndDisplacementMatch struct that is
366 // returned.
367 //
368 // B: base. Just a regular OpIndex.
369 //
370 // D: displacement. An integral constant.
371
372 // Helper to check (S + ...)
373 auto match_S_plus = [&selector](OpIndex left, OpIndex right)
374 -> std::optional<BaseWithScaledIndexAndDisplacementMatch> {
375 BaseWithScaledIndexAndDisplacementMatch result;
376 result.displacement_mode = kPositiveDisplacement;
377
378 // Check (S + ...)
379 if (MatchScaledIndex(selector, left, &result.index, &result.scale,
380 nullptr)) {
381 result.displacement_mode = kPositiveDisplacement;
382
383 // Check (S + (... binop ...))
384 if (const WordBinopOp* right_binop =
385 selector->Get(right).TryCast<WordBinopOp>()) {
386 // Check (S + (B - D))
387 if (right_binop->kind == WordBinopOp::Kind::kSub) {
388 if (!selector->MatchSignedIntegralConstant(right_binop->right(),
389 &result.displacement)) {
390 return std::nullopt;
391 }
392 result.base = right_binop->left();
393 result.displacement_mode = kNegativeDisplacement;
394 return result;
395 }
396 // Check (S + (... + ...))
397 if (right_binop->kind == WordBinopOp::Kind::kAdd) {
398 if (selector->MatchSignedIntegralConstant(right_binop->right(),
399 &result.displacement)) {
400 // (S + (B + D))
401 result.base = right_binop->left();
402 } else if (selector->MatchSignedIntegralConstant(
403 right_binop->left(), &result.displacement)) {
404 // (S + (D + B))
405 result.base = right_binop->right();
406 } else {
407 // Treat it as (S + B)
408 result.base = right;
409 result.displacement = 0;
410 }
411 return result;
412 }
413 }
414
415 // Check (S + D)
416 if (selector->MatchSignedIntegralConstant(right, &result.displacement)) {
417 result.base = OpIndex{};
418 return result;
419 }
420
421 // Treat it as (S + B)
422 result.base = right;
423 result.displacement = 0;
424 return result;
425 }
426
427 return std::nullopt;
428 };
429
430 // Helper to check ((S + ...) + ...)
431 auto match_S_plus_plus = [&selector](OpIndex left, OpIndex right,
432 OpIndex left_add_left,
433 OpIndex left_add_right)
434 -> std::optional<BaseWithScaledIndexAndDisplacementMatch> {
435 DCHECK_EQ(selector->Get(left).Cast<WordBinopOp>().kind,
436 WordBinopOp::Kind::kAdd);
437
438 BaseWithScaledIndexAndDisplacementMatch result;
439 result.displacement_mode = kPositiveDisplacement;
440
441 if (MatchScaledIndex(selector, left_add_left, &result.index, &result.scale,
442 nullptr)) {
443 result.displacement_mode = kPositiveDisplacement;
444 // Check ((S + D) + B)
445 if (selector->MatchSignedIntegralConstant(left_add_right,
446 &result.displacement)) {
447 result.base = right;
448 return result;
449 }
450 // Check ((S + B) + D)
451 if (selector->MatchSignedIntegralConstant(right, &result.displacement)) {
452 result.base = left_add_right;
453 return result;
454 }
455 // Treat it as (B + B) and use index as right B.
456 result.base = left;
457 result.index = right;
458 result.scale = 0;
459 DCHECK_EQ(result.displacement, 0);
460 return result;
461 }
462 return std::nullopt;
463 };
464
465 // Helper to check ((... + ...) + ...)
466 auto match_plus_plus = [&selector, &match_S_plus_plus](OpIndex left,
467 OpIndex right)
468 -> std::optional<BaseWithScaledIndexAndDisplacementMatch> {
469 BaseWithScaledIndexAndDisplacementMatch result;
470 result.displacement_mode = kPositiveDisplacement;
471
472 // Check ((... + ...) + ...)
473 if (const WordBinopOp* left_add =
474 selector->Get(left).TryCast<WordBinopOp>();
475 left_add && left_add->kind == WordBinopOp::Kind::kAdd) {
476 // Check ((S + ...) + ...)
477 auto maybe_res =
478 match_S_plus_plus(left, right, left_add->left(), left_add->right());
479 if (maybe_res) return maybe_res;
480 // Check ((... + S) + ...)
481 maybe_res =
482 match_S_plus_plus(left, right, left_add->right(), left_add->left());
483 if (maybe_res) return maybe_res;
484 }
485
486 return std::nullopt;
487 };
488
489 // Check (S + ...)
490 auto maybe_res = match_S_plus(left, right);
491 if (maybe_res) return maybe_res;
492
493 if (is_commutative) {
494 // Check (... + S)
495 maybe_res = match_S_plus(right, left);
496 if (maybe_res) {
497 return maybe_res;
498 }
499 }
500
501 // Check ((... + ...) + ...)
502 maybe_res = match_plus_plus(left, right);
503 if (maybe_res) return maybe_res;
504
505 if (is_commutative) {
506 // Check (... + (... + ...))
507 maybe_res = match_plus_plus(right, left);
508 if (maybe_res) {
509 return maybe_res;
510 }
511 }
512
513 BaseWithScaledIndexAndDisplacementMatch result;
514 result.displacement_mode = kPositiveDisplacement;
515
516 // Check (B + D)
517 if (selector->MatchSignedIntegralConstant(right, &result.displacement)) {
518 result.base = left;
519 return result;
520 }
521
522 // Treat as (B + B) and use index as left B.
523 result.index = left;
524 result.base = right;
525 return result;
526}
527
528std::optional<BaseWithScaledIndexAndDisplacementMatch>
533
534// Adds X64-specific methods for generating operands.
536 public:
539
541 return compiler::CanBeImmediate(this->selector(), node);
542 }
543
545 return compiler::GetImmediateIntegerValue(this->selector(), node);
546 }
547
549 int effect_level) {
550 if (!this->IsLoadOrLoadImmutable(input)) return false;
551 if (!selector()->CanCover(node, input)) return false;
552
553 if (effect_level != selector()->GetEffectLevel(input)) {
554 return false;
555 }
556
558 this->load_view(input).loaded_rep().representation();
559 switch (opcode) {
560 case kX64And:
561 case kX64Or:
562 case kX64Xor:
563 case kX64Add:
564 case kX64Sub:
565 case kX64Push:
566 case kX64Cmp:
567 case kX64Test:
568 // When pointer compression is enabled 64-bit memory operands can't be
569 // used for tagged values.
570 return rep == MachineRepresentation::kWord64 ||
572 case kX64And32:
573 case kX64Or32:
574 case kX64Xor32:
575 case kX64Add32:
576 case kX64Sub32:
577 case kX64Cmp32:
578 case kX64Test32:
579 // When pointer compression is enabled 32-bit memory operands can be
580 // used for tagged values.
581 return rep == MachineRepresentation::kWord32 ||
583 (IsAnyTagged(rep) || IsAnyCompressed(rep)));
584 case kAVXFloat64Add:
585 case kAVXFloat64Sub:
586 case kAVXFloat64Mul:
588 return true;
589 case kAVXFloat32Add:
590 case kAVXFloat32Sub:
591 case kAVXFloat32Mul:
593 return true;
594 case kX64Cmp16:
595 case kX64Test16:
596 return rep == MachineRepresentation::kWord16;
597 case kX64Cmp8:
598 case kX64Test8:
599 return rep == MachineRepresentation::kWord8;
600 default:
601 break;
602 }
603 return false;
604 }
605
606 bool IsZeroIntConstant(OpIndex node) const {
607 if (ConstantOp* op = this->turboshaft_graph()
608 ->Get(node)
609 .template TryCast<ConstantOp>()) {
610 switch (op->kind) {
611 case ConstantOp::Kind::kWord32:
612 return op->word32() == 0;
613 case ConstantOp::Kind::kWord64:
614 return op->word64() == 0;
615 default:
616 break;
617 }
618 }
619 return false;
620 }
621
623 OptionalOpIndex index, int scale_exponent, OpIndex base,
625 InstructionOperand inputs[], size_t* input_count,
627 AddressingMode mode = kMode_MRI;
628 OpIndex base_before_folding = base;
629 bool fold_base_into_displacement = false;
630 int64_t fold_value = 0;
631 if (base.valid() && (index.valid() || displacement != 0)) {
632 if (CanBeImmediate(base) && index.valid() &&
634 fold_value = GetImmediateIntegerValue(base);
636 fold_value -= displacement;
637 } else {
638 fold_value += displacement;
639 }
640 if (V8_UNLIKELY(fold_value == 0)) {
641 base = OpIndex{};
642 displacement = 0;
643 } else if (ValueFitsIntoImmediate(fold_value)) {
644 base = OpIndex{};
645 fold_base_into_displacement = true;
646 }
647 } else if (IsZeroIntConstant(base)) {
648 base = OpIndex{};
649 }
650 }
651 if (base.valid()) {
652 inputs[(*input_count)++] = UseRegister(base, reg_kind);
653 if (index.valid()) {
654 DCHECK(scale_exponent >= 0 && scale_exponent <= 3);
655 inputs[(*input_count)++] = UseRegister(this->value(index), reg_kind);
656 if (displacement != 0) {
657 inputs[(*input_count)++] = UseImmediate64(
659 : displacement);
660 static const AddressingMode kMRnI_modes[] = {kMode_MR1I, kMode_MR2I,
661 kMode_MR4I, kMode_MR8I};
662 mode = kMRnI_modes[scale_exponent];
663 } else {
664 static const AddressingMode kMRn_modes[] = {kMode_MR1, kMode_MR2,
665 kMode_MR4, kMode_MR8};
666 mode = kMRn_modes[scale_exponent];
667 }
668 } else {
669 if (displacement == 0) {
670 mode = kMode_MR;
671 } else {
672 inputs[(*input_count)++] = UseImmediate64(
674 : displacement);
675 mode = kMode_MRI;
676 }
677 }
678 } else {
679 DCHECK(scale_exponent >= 0 && scale_exponent <= 3);
680 if (fold_base_into_displacement) {
681 DCHECK(!base.valid());
682 DCHECK(index.valid());
683 inputs[(*input_count)++] = UseRegister(this->value(index), reg_kind);
684 inputs[(*input_count)++] = UseImmediate(static_cast<int>(fold_value));
685 static const AddressingMode kMnI_modes[] = {kMode_MRI, kMode_M2I,
686 kMode_M4I, kMode_M8I};
687 mode = kMnI_modes[scale_exponent];
688 } else if (displacement != 0) {
689 if (!index.valid()) {
690 // This seems to only occur in (0 + k) cases, but we don't have an
691 // addressing mode for a simple constant, so we use the base in a
692 // register for kMode_MRI.
693 CHECK(IsZeroIntConstant(base_before_folding));
694 inputs[(*input_count)++] = UseRegister(base_before_folding, reg_kind);
695 inputs[(*input_count)++] = UseImmediate64(
697 : displacement);
698 mode = kMode_MRI;
699 } else {
700 inputs[(*input_count)++] = UseRegister(this->value(index), reg_kind);
701 inputs[(*input_count)++] = UseImmediate64(
703 : displacement);
704 static const AddressingMode kMnI_modes[] = {kMode_MRI, kMode_M2I,
705 kMode_M4I, kMode_M8I};
706 mode = kMnI_modes[scale_exponent];
707 }
708 } else {
709 DCHECK(index.valid());
710 inputs[(*input_count)++] = UseRegister(this->value(index), reg_kind);
711 static const AddressingMode kMn_modes[] = {kMode_MR, kMode_MR1,
712 kMode_M4, kMode_M8};
713 mode = kMn_modes[scale_exponent];
714 if (mode == kMode_MR1) {
715 // [%r1 + %r1*1] has a smaller encoding than [%r1*2+0]
716 inputs[(*input_count)++] = UseRegister(this->value(index), reg_kind);
717 }
718 }
719 }
720 return mode;
721 }
722
724 OpIndex operand, InstructionOperand inputs[], size_t* input_count,
726
728 AddressingMode* mode) {
729 if (CanBeImmediate(index)) {
730 *mode = kMode_MRI;
731 return UseImmediate(index);
732 } else {
733 *mode = kMode_MR1;
734 return UseUniqueRegister(index);
735 }
736 }
737
739 return !selector()->IsReallyLive(node);
740 }
741};
742
743namespace {
744
745struct LoadStoreView {
746 explicit LoadStoreView(const Operation& op) {
747 DCHECK(op.Is<LoadOp>() || op.Is<StoreOp>());
748 if (const LoadOp* load = op.TryCast<LoadOp>()) {
749 base = load->base();
750 index = load->index();
751 offset = load->offset;
752 } else {
753 DCHECK(op.Is<StoreOp>());
754 const StoreOp& store = op.Cast<StoreOp>();
755 base = store.base();
756 index = store.index();
757 offset = store.offset;
758 }
759 }
762 int32_t offset;
763};
764
765} // namespace
766
768 OpIndex operand, InstructionOperand inputs[], size_t* input_count,
769 RegisterUseKind reg_kind) {
770 const Operation& op = Get(operand);
771 if (op.Is<LoadOp>() || op.Is<StoreOp>()) {
772 LoadStoreView load_or_store(op);
773 if (ExternalReference reference;
774 MatchExternalConstant(load_or_store.base, &reference) &&
775 !load_or_store.index.valid()) {
776 if (selector()->CanAddressRelativeToRootsRegister(reference)) {
777 const ptrdiff_t delta =
778 load_or_store.offset +
780 selector()->isolate(), reference);
781 if (is_int32(delta)) {
782 inputs[(*input_count)++] = TempImmediate(static_cast<int32_t>(delta));
783 return kMode_Root;
784 }
785 }
786 }
787 }
788
790 DCHECK(m.has_value());
791 if (IsCompressed(selector(), m->base)) {
792 DCHECK(!m->index.valid());
793 DCHECK(m->displacement == 0 || ValueFitsIntoImmediate(m->displacement));
794 AddressingMode mode = kMode_MCR;
795 inputs[(*input_count)++] = UseRegister(m->base, reg_kind);
796 if (m->displacement != 0) {
797 inputs[(*input_count)++] =
798 m->displacement_mode == kNegativeDisplacement
799 ? UseImmediate(static_cast<int>(-m->displacement))
800 : UseImmediate(static_cast<int>(m->displacement));
801 mode = kMode_MCRI;
802 }
803 return mode;
804 }
805 if (m->base.valid() && this->Get(m->base).Is<LoadRootRegisterOp>()) {
806 DCHECK(!m->index.valid());
807 DCHECK_EQ(m->scale, 0);
808 DCHECK(ValueFitsIntoImmediate(m->displacement));
809 inputs[(*input_count)++] = UseImmediate(static_cast<int>(m->displacement));
810 return kMode_Root;
811 } else if (ValueFitsIntoImmediate(m->displacement)) {
812 return GenerateMemoryOperandInputs(m->index, m->scale, m->base,
813 m->displacement, m->displacement_mode,
814 inputs, input_count, reg_kind);
815 } else if (!m->base.valid() &&
816 m->displacement_mode == kPositiveDisplacement) {
817 // The displacement cannot be an immediate, but we can use the
818 // displacement as base instead and still benefit from addressing
819 // modes for the scale.
821 } else {
822 // TODO(nicohartmann@): Turn this into a `DCHECK` once we have some
823 // coverage.
824 CHECK_EQ(m->displacement, 0);
825 inputs[(*input_count)++] = UseRegister(m->base, reg_kind);
826 inputs[(*input_count)++] = UseRegister(m->index, reg_kind);
827 return kMode_MR1;
828 }
829}
830
831namespace {
832
833ArchOpcode GetLoadOpcode(MemoryRepresentation loaded_rep,
834 RegisterRepresentation result_rep) {
835 // NOTE: The meaning of `loaded_rep` = `MemoryRepresentation::AnyTagged()` is
836 // we are loading a compressed tagged field, while `result_rep` =
837 // `RegisterRepresentation::Tagged()` refers to an uncompressed tagged value.
838 switch (loaded_rep) {
841 return kX64Movsxbl;
844 return kX64Movzxbl;
847 return kX64Movsxwl;
850 return kX64Movzxwl;
854 return kX64Movl;
856 case MemoryRepresentation::Uint64():
858 return kX64Movq;
861 return kX64Movsh;
864 return kX64Movss;
867 return kX64Movsd;
868#ifdef V8_COMPRESS_POINTERS
870 case MemoryRepresentation::TaggedPointer():
871 if (result_rep == RegisterRepresentation::Compressed()) {
872 return kX64Movl;
873 }
875 return kX64MovqDecompressTagged;
877 if (result_rep == RegisterRepresentation::Compressed()) {
878 return kX64Movl;
879 }
881 return kX64MovqDecompressTaggedSigned;
882#else
884 case MemoryRepresentation::TaggedPointer():
885 case MemoryRepresentation::TaggedSigned():
886 DCHECK_EQ(result_rep, RegisterRepresentation::Tagged());
887 return kX64Movq;
888#endif
890 case MemoryRepresentation::UncompressedTaggedPointer():
891 case MemoryRepresentation::UncompressedTaggedSigned():
892 DCHECK_EQ(result_rep, RegisterRepresentation::Tagged());
893 return kX64Movq;
896 return kX64MovqDecompressProtected;
898 UNREACHABLE();
900 return kX64MovqDecodeSandboxedPointer;
902 DCHECK_EQ(result_rep, RegisterRepresentation::Simd128());
903 return kX64Movdqu;
905 DCHECK_EQ(result_rep, RegisterRepresentation::Simd256());
906 return kX64Movdqu256;
907 }
908}
909
910ArchOpcode GetLoadOpcode(LoadRepresentation load_rep) {
911 ArchOpcode opcode;
912 switch (load_rep.representation()) {
914 opcode = kX64Movsh;
915 break;
917 opcode = kX64Movss;
918 break;
920 opcode = kX64Movsd;
921 break;
922 case MachineRepresentation::kBit: // Fall through.
924 opcode = load_rep.IsSigned() ? kX64Movsxbl : kX64Movzxbl;
925 break;
927 opcode = load_rep.IsSigned() ? kX64Movsxwl : kX64Movzxwl;
928 break;
930 opcode = kX64Movl;
931 break;
932 case MachineRepresentation::kCompressedPointer: // Fall through.
934#ifdef V8_COMPRESS_POINTERS
935 opcode = kX64Movl;
936 break;
937#else
938 UNREACHABLE();
939#endif
940#ifdef V8_COMPRESS_POINTERS
942 opcode = kX64MovqDecompressTaggedSigned;
943 break;
946 opcode = kX64MovqDecompressTagged;
947 break;
948#else
949 case MachineRepresentation::kTaggedSigned: // Fall through.
950 case MachineRepresentation::kTaggedPointer: // Fall through.
951 case MachineRepresentation::kTagged: // Fall through.
952#endif
954 opcode = kX64Movq;
955 break;
958 opcode = kX64MovqDecompressProtected;
959 break;
961 opcode = kX64MovqDecodeSandboxedPointer;
962 break;
964 opcode = kX64Movdqu;
965 break;
966 case MachineRepresentation::kSimd256: // Fall through.
967 opcode = kX64Movdqu256;
968 break;
969 case MachineRepresentation::kNone: // Fall through.
970 case MachineRepresentation::kMapWord: // Fall through.
971 case MachineRepresentation::kIndirectPointer: // Fall through.
973 UNREACHABLE();
974 }
975 return opcode;
976}
977
978ArchOpcode GetStoreOpcode(MemoryRepresentation stored_rep) {
979 switch (stored_rep) {
981 case MemoryRepresentation::Uint8():
982 return kX64Movb;
984 case MemoryRepresentation::Uint16():
985 return kX64Movw;
987 case MemoryRepresentation::Uint32():
988 return kX64Movl;
990 case MemoryRepresentation::Uint64():
991 return kX64Movq;
993 return kX64Movsh;
995 return kX64Movss;
997 return kX64Movsd;
999 case MemoryRepresentation::TaggedPointer():
1000 case MemoryRepresentation::TaggedSigned():
1001 return kX64MovqCompressTagged;
1003 case MemoryRepresentation::UncompressedTaggedPointer():
1004 case MemoryRepresentation::UncompressedTaggedSigned():
1005 return kX64Movq;
1007 // We never store directly to protected pointers from generated code.
1008 UNREACHABLE();
1010 return kX64MovqStoreIndirectPointer;
1012 return kX64MovqEncodeSandboxedPointer;
1014 return kX64Movdqu;
1016 return kX64Movdqu256;
1017 }
1018}
1019
1020ArchOpcode GetSeqCstStoreOpcode(StoreRepresentation store_rep) {
1021 switch (store_rep.representation()) {
1023 return kAtomicStoreWord8;
1025 return kAtomicStoreWord16;
1027 return kAtomicStoreWord32;
1029 return kX64Word64AtomicStoreWord64;
1030 case MachineRepresentation::kTaggedSigned: // Fall through.
1031 case MachineRepresentation::kTaggedPointer: // Fall through.
1033 if (COMPRESS_POINTERS_BOOL) return kAtomicStoreWord32;
1034 return kX64Word64AtomicStoreWord64;
1035 case MachineRepresentation::kCompressedPointer: // Fall through.
1038 return kAtomicStoreWord32;
1039 default:
1040 UNREACHABLE();
1041 }
1042}
1043
1044// Used for pmin/pmax and relaxed min/max.
1045template <VectorLength vec_len>
1046void VisitMinOrMax(InstructionSelectorT* selector, OpIndex node,
1047 ArchOpcode opcode, bool flip_inputs) {
1048 X64OperandGeneratorT g(selector);
1049 const Operation& op = selector->Get(node);
1050 DCHECK_EQ(op.input_count, 2);
1051 InstructionOperand dst = selector->IsSupported(AVX)
1052 ? g.DefineAsRegister(node)
1053 : g.DefineSameAsFirst(node);
1054 InstructionCode instr_code = opcode | VectorLengthField::encode(vec_len);
1055 if (flip_inputs) {
1056 // Due to the way minps/minpd work, we want the dst to be same as the second
1057 // input: b = pmin(a, b) directly maps to minps b a.
1058 selector->Emit(instr_code, dst, g.UseRegister(op.input(1)),
1059 g.UseRegister(op.input(0)));
1060 } else {
1061 selector->Emit(instr_code, dst, g.UseRegister(op.input(0)),
1062 g.UseRegister(op.input(1)));
1063 }
1064}
1065} // namespace
1066
1067void InstructionSelectorT::VisitTraceInstruction(OpIndex node) {
1068 // Currently not used by Turboshaft.
1069 UNIMPLEMENTED();
1070}
1071
1072void InstructionSelectorT::VisitStackSlot(OpIndex node) {
1073 const StackSlotOp& stack_slot = Cast<StackSlotOp>(node);
1074 int slot = frame_->AllocateSpillSlot(stack_slot.size, stack_slot.alignment,
1075 stack_slot.is_tagged);
1076 OperandGenerator g(this);
1077
1078 Emit(kArchStackSlot, g.DefineAsRegister(node),
1079 sequence()->AddImmediate(Constant(slot)), 0, nullptr);
1080}
1081
1082void InstructionSelectorT::VisitAbortCSADcheck(OpIndex node) {
1083 X64OperandGeneratorT g(this);
1084 const AbortCSADcheckOp& check = Cast<AbortCSADcheckOp>(node);
1085 DCHECK_EQ(check.input_count, 1);
1086 Emit(kArchAbortCSADcheck, g.NoOutput(), g.UseFixed(check.message(), rdx));
1087}
1088
1089#ifdef V8_ENABLE_WEBASSEMBLY
1090void InstructionSelectorT::VisitLoadLane(OpIndex node) {
1091 const Simd128LaneMemoryOp& load = this->Get(node).Cast<Simd128LaneMemoryOp>();
1092 InstructionCode opcode = kArchNop;
1093 switch (load.lane_kind) {
1094 case Simd128LaneMemoryOp::LaneKind::k8:
1095 opcode = kX64Pinsrb;
1096 break;
1097 case Simd128LaneMemoryOp::LaneKind::k16:
1098 opcode = kX64Pinsrw;
1099 break;
1100 case Simd128LaneMemoryOp::LaneKind::k32:
1101 opcode = kX64Pinsrd;
1102 break;
1103 case Simd128LaneMemoryOp::LaneKind::k64:
1104 opcode = kX64Pinsrq;
1105 break;
1106 }
1107
1108 X64OperandGeneratorT g(this);
1109 InstructionOperand outputs[] = {g.DefineAsRegister(node)};
1110 // Input 0 is value node, 1 is lane idx, and GetEffectiveAddressMemoryOperand
1111 // uses up to 3 inputs. This ordering is consistent with other operations that
1112 // use the same opcode.
1113 InstructionOperand inputs[5];
1114 size_t input_count = 0;
1115
1116 inputs[input_count++] = g.UseRegister(load.value());
1117 inputs[input_count++] = g.UseImmediate(load.lane);
1118
1119 AddressingMode mode =
1120 g.GetEffectiveAddressMemoryOperand(node, inputs, &input_count);
1122
1123 DCHECK_GE(5, input_count);
1124
1125 // x64 supports unaligned loads.
1126 DCHECK(!load.kind.maybe_unaligned);
1127 if (load.kind.with_trap_handler) {
1129 }
1130 Emit(opcode, 1, outputs, input_count, inputs);
1131}
1132
1134 const Simd128LoadTransformOp& op =
1135 this->Get(node).Cast<Simd128LoadTransformOp>();
1137 switch (op.transform_kind) {
1138 case Simd128LoadTransformOp::TransformKind::k8x8S:
1139 opcode = kX64S128Load8x8S;
1140 break;
1141 case Simd128LoadTransformOp::TransformKind::k8x8U:
1142 opcode = kX64S128Load8x8U;
1143 break;
1144 case Simd128LoadTransformOp::TransformKind::k16x4S:
1145 opcode = kX64S128Load16x4S;
1146 break;
1147 case Simd128LoadTransformOp::TransformKind::k16x4U:
1148 opcode = kX64S128Load16x4U;
1149 break;
1150 case Simd128LoadTransformOp::TransformKind::k32x2S:
1151 opcode = kX64S128Load32x2S;
1152 break;
1153 case Simd128LoadTransformOp::TransformKind::k32x2U:
1154 opcode = kX64S128Load32x2U;
1155 break;
1156 case Simd128LoadTransformOp::TransformKind::k8Splat:
1157 opcode = kX64S128Load8Splat;
1158 break;
1159 case Simd128LoadTransformOp::TransformKind::k16Splat:
1160 opcode = kX64S128Load16Splat;
1161 break;
1162 case Simd128LoadTransformOp::TransformKind::k32Splat:
1163 opcode = kX64S128Load32Splat;
1164 break;
1165 case Simd128LoadTransformOp::TransformKind::k64Splat:
1166 opcode = kX64S128Load64Splat;
1167 break;
1168 case Simd128LoadTransformOp::TransformKind::k32Zero:
1169 opcode = kX64Movss;
1170 break;
1171 case Simd128LoadTransformOp::TransformKind::k64Zero:
1172 opcode = kX64Movsd;
1173 break;
1174 }
1175
1176 // x64 supports unaligned loads
1177 DCHECK(!op.load_kind.maybe_unaligned);
1178 InstructionCode code = opcode;
1179 if (op.load_kind.with_trap_handler) {
1181 }
1182 VisitLoad(node, node, code);
1183}
1184
1185#if V8_ENABLE_WASM_SIMD256_REVEC
1186void InstructionSelectorT::VisitS256Const(OpIndex node) {
1187 X64OperandGeneratorT g(this);
1188 static const int kUint32Immediates = kSimd256Size / sizeof(uint32_t);
1189 uint32_t val[kUint32Immediates];
1190 const Simd256ConstantOp& constant = Cast<Simd256ConstantOp>(node);
1191 memcpy(val, constant.value, kSimd256Size);
1192 // If all bytes are zeros or ones, avoid emitting code for generic constants
1193 bool all_zeros = std::all_of(std::begin(val), std::end(val),
1194 [](uint32_t v) { return v == 0; });
1195 // It's should not happen for Turboshaft, IsZero is checked earlier in
1196 // instruction selector
1197 DCHECK(!all_zeros);
1198 bool all_ones = std::all_of(std::begin(val), std::end(val),
1199 [](uint32_t v) { return v == UINT32_MAX; });
1200 InstructionOperand dst = g.DefineAsRegister(node);
1201 if (all_zeros) {
1202 Emit(kX64SZero | VectorLengthField::encode(kV256), dst);
1203 } else if (all_ones) {
1204 Emit(kX64SAllOnes | VectorLengthField::encode(kV256), dst);
1205 } else {
1206 Emit(kX64S256Const, dst, g.UseImmediate(val[0]), g.UseImmediate(val[1]),
1207 g.UseImmediate(val[2]), g.UseImmediate(val[3]), g.UseImmediate(val[4]),
1208 g.UseImmediate(val[5]), g.UseImmediate(val[6]),
1209 g.UseImmediate(val[7]));
1210 }
1211}
1212
1213void InstructionSelectorT::VisitS256Zero(OpIndex node) {
1214 X64OperandGeneratorT g(this);
1215 Emit(kX64SZero | VectorLengthField::encode(kV256), g.DefineAsRegister(node));
1216}
1217
1218void InstructionSelectorT::VisitSimd256LoadTransform(OpIndex node) {
1219 const Simd256LoadTransformOp& op =
1220 this->Get(node).Cast<Simd256LoadTransformOp>();
1222 switch (op.transform_kind) {
1223 case Simd256LoadTransformOp::TransformKind::k8x16S:
1224 opcode = kX64S256Load8x16S;
1225 break;
1226 case Simd256LoadTransformOp::TransformKind::k8x16U:
1227 opcode = kX64S256Load8x16U;
1228 break;
1229 case Simd256LoadTransformOp::TransformKind::k8x8U:
1230 opcode = kX64S256Load8x8U;
1231 break;
1232 case Simd256LoadTransformOp::TransformKind::k16x8S:
1233 opcode = kX64S256Load16x8S;
1234 break;
1235 case Simd256LoadTransformOp::TransformKind::k16x8U:
1236 opcode = kX64S256Load16x8U;
1237 break;
1238 case Simd256LoadTransformOp::TransformKind::k32x4S:
1239 opcode = kX64S256Load32x4S;
1240 break;
1241 case Simd256LoadTransformOp::TransformKind::k32x4U:
1242 opcode = kX64S256Load32x4U;
1243 break;
1244 case Simd256LoadTransformOp::TransformKind::k8Splat:
1245 opcode = kX64S256Load8Splat;
1246 break;
1247 case Simd256LoadTransformOp::TransformKind::k16Splat:
1248 opcode = kX64S256Load16Splat;
1249 break;
1250 case Simd256LoadTransformOp::TransformKind::k32Splat:
1251 opcode = kX64S256Load32Splat;
1252 break;
1253 case Simd256LoadTransformOp::TransformKind::k64Splat:
1254 opcode = kX64S256Load64Splat;
1255 break;
1256 }
1257
1258 // x64 supports unaligned loads
1259 DCHECK(!op.load_kind.maybe_unaligned);
1260 InstructionCode code = opcode;
1261 if (op.load_kind.with_trap_handler) {
1263 }
1264 VisitLoad(node, node, code);
1265}
1266
1267void InstructionSelectorT::VisitF32x8RelaxedMin(OpIndex node) {
1268 VisitMinOrMax<kV256>(this, node, kX64Minps, false);
1269}
1270
1271void InstructionSelectorT::VisitF32x8RelaxedMax(OpIndex node) {
1272 VisitMinOrMax<kV256>(this, node, kX64Maxps, false);
1273}
1274
1275void InstructionSelectorT::VisitF64x4RelaxedMin(OpIndex node) {
1276 VisitMinOrMax<kV256>(this, node, kX64Minpd, false);
1277}
1278
1279void InstructionSelectorT::VisitF64x4RelaxedMax(OpIndex node) {
1280 VisitMinOrMax<kV256>(this, node, kX64Maxpd, false);
1281}
1282
1283#ifdef V8_TARGET_ARCH_X64
1284void InstructionSelectorT::VisitSimd256Shufd(OpIndex node) {
1285 X64OperandGeneratorT g(this);
1286 const Simd256ShufdOp& shufd = Get(node).Cast<Simd256ShufdOp>();
1287 InstructionOperand dst = g.DefineAsRegister(node);
1288 InstructionOperand src = g.UseUniqueRegister(shufd.input());
1289 InstructionOperand imm = g.UseImmediate(shufd.control);
1290 InstructionOperand inputs[] = {src, imm};
1291 Emit(kX64Vpshufd, 1, &dst, 2, inputs);
1292}
1293
1294void InstructionSelectorT::VisitSimd256Shufps(OpIndex node) {
1295 X64OperandGeneratorT g(this);
1296 const Simd256ShufpsOp& shufps = Get(node).Cast<Simd256ShufpsOp>();
1297 InstructionOperand dst = g.DefineAsRegister(node);
1298 InstructionOperand src1 = g.UseUniqueRegister(shufps.left());
1299 InstructionOperand src2 = g.UseUniqueRegister(shufps.right());
1300 InstructionOperand imm = g.UseImmediate(shufps.control);
1301 InstructionOperand inputs[] = {src1, src2, imm};
1302 Emit(kX64Shufps, 1, &dst, 3, inputs);
1303}
1304
1305void InstructionSelectorT::VisitSimd256Unpack(OpIndex node) {
1306 X64OperandGeneratorT g(this);
1307 const Simd256UnpackOp& unpack = Get(node).Cast<Simd256UnpackOp>();
1308 InstructionOperand dst = g.DefineAsRegister(node);
1309 InstructionOperand src1 = g.UseUniqueRegister(unpack.left());
1310 InstructionOperand src2 = g.UseUniqueRegister(unpack.right());
1311 InstructionOperand inputs[] = {src1, src2};
1313 switch (unpack.kind) {
1314 case Simd256UnpackOp::Kind::k32x8High:
1315 code = kX64S32x8UnpackHigh;
1316 break;
1317 case Simd256UnpackOp::Kind::k32x8Low:
1318 code = kX64S32x8UnpackLow;
1319 break;
1320 default:
1321 UNIMPLEMENTED();
1322 }
1323 Emit(code, 1, &dst, 2, inputs);
1324}
1325
1326void InstructionSelectorT::VisitSimdPack128To256(OpIndex node) {
1327 X64OperandGeneratorT g(this);
1328
1329 const SimdPack128To256Op& op = Get(node).Cast<SimdPack128To256Op>();
1330
1331 OpIndex input0 = op.input(0);
1332 OpIndex input1 = op.input(1);
1333 constexpr int kHighLaneIndex = 1;
1334
1335 InstructionOperand dst = g.DefineAsRegister(node);
1336 InstructionOperand src0 = g.UseUniqueRegister(input0);
1337 InstructionOperand src1 = g.UseUniqueRegister(input1);
1338 InstructionOperand imm = g.UseImmediate(kHighLaneIndex);
1339
1340 InstructionOperand inputs[] = {src0, src1, imm};
1341
1342 Emit(kX64InsertI128, 1, &dst, 3, inputs);
1343}
1344#endif // V8_TARGET_ARCH_X64
1345
1346#endif // V8_ENABLE_WASM_SIMD256_REVEC
1347#endif // V8_ENABLE_WEBASSEMBLY
1348
1350 InstructionCode opcode) {
1351 X64OperandGeneratorT g(this);
1352#ifdef V8_IS_TSAN
1353 // On TSAN builds we require one scratch register. Because of this we also
1354 // have to modify the inputs to take into account possible aliasing and use
1355 // UseUniqueRegister which is not required for non-TSAN builds.
1356 InstructionOperand temps[] = {g.TempRegister()};
1357 size_t temp_count = arraysize(temps);
1359#else
1360 InstructionOperand* temps = nullptr;
1361 size_t temp_count = 0;
1363#endif // V8_IS_TSAN
1364 InstructionOperand outputs[] = {g.DefineAsRegister(node)};
1365 InstructionOperand inputs[3];
1366 size_t input_count = 0;
1367 AddressingMode mode =
1368 g.GetEffectiveAddressMemoryOperand(value, inputs, &input_count, reg_kind);
1370 if (this->is_load(node)) {
1371 auto load = this->load_view(node);
1372 bool traps_on_null;
1373 if (load.is_protected(&traps_on_null)) {
1374 if (traps_on_null) {
1376 } else {
1378 }
1379 }
1380 }
1381 Emit(code, 1, outputs, input_count, inputs, temp_count, temps);
1382}
1383
1385 TurboshaftAdapter::LoadView view = this->load_view(node);
1386 VisitLoad(node, node,
1387 GetLoadOpcode(view.ts_loaded_rep(), view.ts_result_rep()));
1388}
1389
1390void InstructionSelectorT::VisitProtectedLoad(OpIndex node) { VisitLoad(node); }
1391
1392namespace {
1393
1394// Shared routine for Word32/Word64 Atomic Exchange
1395void VisitAtomicExchange(InstructionSelectorT* selector, OpIndex node,
1396 ArchOpcode opcode, AtomicWidth width,
1397 MemoryAccessKind access_kind) {
1398 const AtomicRMWOp& atomic_op = selector->Cast<AtomicRMWOp>(node);
1399 X64OperandGeneratorT g(selector);
1400 AddressingMode addressing_mode;
1401 InstructionOperand inputs[] = {
1402 g.UseUniqueRegister(atomic_op.value()),
1403 g.UseUniqueRegister(atomic_op.base()),
1404 g.GetEffectiveIndexOperand(atomic_op.index(), &addressing_mode)};
1405 InstructionOperand outputs[] = {g.DefineSameAsFirst(node)};
1406 InstructionCode code = opcode | AddressingModeField::encode(addressing_mode) |
1408 if (access_kind == MemoryAccessKind::kProtectedByTrapHandler) {
1410 }
1411 selector->Emit(code, arraysize(outputs), outputs, arraysize(inputs), inputs);
1412}
1413
1414void VisitStoreCommon(InstructionSelectorT* selector,
1415 const TurboshaftAdapter::StoreView& store) {
1416 X64OperandGeneratorT g(selector);
1417 OpIndex base = store.base();
1418 OptionalOpIndex index = store.index();
1419 OpIndex value = store.value();
1420 int32_t displacement = store.displacement();
1421 uint8_t element_size_log2 = store.element_size_log2();
1422 std::optional<AtomicMemoryOrder> atomic_order = store.memory_order();
1423 MemoryAccessKind acs_kind = store.access_kind();
1424
1425 const StoreRepresentation store_rep = store.stored_rep();
1426 DCHECK_NE(store_rep.representation(), MachineRepresentation::kMapWord);
1427 WriteBarrierKind write_barrier_kind = store_rep.write_barrier_kind();
1428 const bool is_seqcst =
1429 atomic_order && *atomic_order == AtomicMemoryOrder::kSeqCst;
1430
1431 if (v8_flags.enable_unconditional_write_barriers &&
1432 CanBeTaggedOrCompressedPointer(store_rep.representation())) {
1433 write_barrier_kind = kFullWriteBarrier;
1434 }
1435
1436 const auto access_mode =
1438 ? (store.is_store_trap_on_null()
1442
1443 if (write_barrier_kind != kNoWriteBarrier &&
1444 !v8_flags.disable_write_barriers) {
1445 DCHECK(
1446 CanBeTaggedOrCompressedOrIndirectPointer(store_rep.representation()));
1447 // Uncompressed stores should not happen if we need a write barrier.
1448 CHECK((store.ts_stored_rep() !=
1450 (store.ts_stored_rep() !=
1452 (store.ts_stored_rep() !=
1454 AddressingMode addressing_mode;
1455 InstructionOperand inputs[5];
1456 size_t input_count = 0;
1457 addressing_mode = g.GenerateMemoryOperandInputs(
1458 index, element_size_log2, base, displacement,
1459 DisplacementMode::kPositiveDisplacement, inputs, &input_count,
1461 DCHECK_LT(input_count, 4);
1462 inputs[input_count++] = g.UseUniqueRegister(value);
1463 RecordWriteMode record_write_mode =
1464 WriteBarrierKindToRecordWriteMode(write_barrier_kind);
1465 InstructionOperand temps[] = {g.TempRegister(), g.TempRegister()};
1467 if (store_rep.representation() == MachineRepresentation::kIndirectPointer) {
1468 DCHECK_EQ(write_barrier_kind, kIndirectPointerWriteBarrier);
1469 // In this case we need to add the IndirectPointerTag as additional input.
1470 code = kArchStoreIndirectWithWriteBarrier;
1471 IndirectPointerTag tag = store.indirect_pointer_tag();
1472 inputs[input_count++] = g.UseImmediate64(static_cast<int64_t>(tag));
1473 } else {
1474 code = is_seqcst ? kArchAtomicStoreWithWriteBarrier
1475 : kArchStoreWithWriteBarrier;
1476 }
1477 code |= AddressingModeField::encode(addressing_mode);
1478 code |= RecordWriteModeField::encode(record_write_mode);
1479 code |= AccessModeField::encode(access_mode);
1480 selector->Emit(code, 0, nullptr, input_count, inputs, arraysize(temps),
1481 temps);
1482 } else {
1483#ifdef V8_IS_TSAN
1484 // On TSAN builds we require two scratch registers. Because of this we also
1485 // have to modify the inputs to take into account possible aliasing and use
1486 // UseUniqueRegister which is not required for non-TSAN builds.
1487 InstructionOperand temps[] = {g.TempRegister(), g.TempRegister()};
1488 size_t temp_count = arraysize(temps);
1490#else
1491 InstructionOperand* temps = nullptr;
1492 size_t temp_count = 0;
1494#endif // V8_IS_TSAN
1495
1496 // Release and non-atomic stores emit MOV and sequentially consistent stores
1497 // emit XCHG.
1498 // https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
1499
1500 ArchOpcode opcode;
1501 AddressingMode addressing_mode;
1502 InstructionOperand inputs[4];
1503 size_t input_count = 0;
1504
1505 if (is_seqcst) {
1506 // SeqCst stores emit XCHG instead of MOV, so encode the inputs as we
1507 // would for XCHG. XCHG can't encode the value as an immediate and has
1508 // fewer addressing modes available.
1509 inputs[input_count++] = g.UseUniqueRegister(value);
1510 inputs[input_count++] = g.UseUniqueRegister(base);
1511 DCHECK_EQ(element_size_log2, 0);
1512 if (index.valid()) {
1514 inputs[input_count++] = g.GetEffectiveIndexOperand(
1515 selector->value(index), &addressing_mode);
1516 } else if (displacement != 0) {
1518 inputs[input_count++] = g.UseImmediate(displacement);
1519 addressing_mode = kMode_MRI;
1520 } else {
1521 addressing_mode = kMode_MR;
1522 }
1523 opcode = GetSeqCstStoreOpcode(store_rep);
1524 } else {
1525 if (ElementSizeLog2Of(store_rep.representation()) <
1527 if (V<Word64> value64;
1528 selector->MatchTruncateWord64ToWord32(value, &value64)) {
1529 value = value64;
1530 }
1531 }
1532
1533 addressing_mode = g.GetEffectiveAddressMemoryOperand(
1534 store, inputs, &input_count, reg_kind);
1535 InstructionOperand value_operand = g.CanBeImmediate(value)
1536 ? g.UseImmediate(value)
1537 : g.UseRegister(value, reg_kind);
1538 inputs[input_count++] = value_operand;
1539 opcode = GetStoreOpcode(store.ts_stored_rep());
1540 }
1541
1542 InstructionCode code = opcode
1543 | AddressingModeField::encode(addressing_mode)
1544 | AccessModeField::encode(access_mode);
1545 selector->Emit(code, 0, static_cast<InstructionOperand*>(nullptr),
1546 input_count, inputs, temp_count, temps);
1547 }
1548}
1549
1550} // namespace
1551
1552void InstructionSelectorT::VisitStorePair(OpIndex node) { UNREACHABLE(); }
1553
1554void InstructionSelectorT::VisitStore(OpIndex node) {
1555 return VisitStoreCommon(this, this->store_view(node));
1556}
1557
1558void InstructionSelectorT::VisitProtectedStore(OpIndex node) {
1559 return VisitStoreCommon(this, this->store_view(node));
1560}
1561
1562// Architecture supports unaligned access, therefore VisitLoad is used instead
1563void InstructionSelectorT::VisitUnalignedLoad(OpIndex node) { UNREACHABLE(); }
1564
1565// Architecture supports unaligned access, therefore VisitStore is used instead
1566void InstructionSelectorT::VisitUnalignedStore(OpIndex node) { UNREACHABLE(); }
1567
1568#ifdef V8_ENABLE_WEBASSEMBLY
1569void InstructionSelectorT::VisitStoreLane(OpIndex node) {
1570 X64OperandGeneratorT g(this);
1571 const Simd128LaneMemoryOp& store = Get(node).Cast<Simd128LaneMemoryOp>();
1572 InstructionCode opcode = kArchNop;
1573 switch (store.lane_kind) {
1574 case Simd128LaneMemoryOp::LaneKind::k8:
1575 opcode = kX64Pextrb;
1576 break;
1577 case Simd128LaneMemoryOp::LaneKind::k16:
1578 opcode = kX64Pextrw;
1579 break;
1580 case Simd128LaneMemoryOp::LaneKind::k32:
1581 opcode = kX64S128Store32Lane;
1582 break;
1583 case Simd128LaneMemoryOp::LaneKind::k64:
1584 opcode = kX64S128Store64Lane;
1585 break;
1586 }
1587
1588 InstructionOperand inputs[4];
1589 size_t input_count = 0;
1590 AddressingMode addressing_mode =
1591 g.GetEffectiveAddressMemoryOperand(node, inputs, &input_count);
1592 opcode |= AddressingModeField::encode(addressing_mode);
1593
1594 if (store.kind.with_trap_handler) {
1596 }
1597
1598 InstructionOperand value_operand = g.UseRegister(store.value());
1599 inputs[input_count++] = value_operand;
1600 inputs[input_count++] = g.UseImmediate(store.lane);
1601 DCHECK_GE(4, input_count);
1602 Emit(opcode, 0, nullptr, input_count, inputs);
1603}
1604
1605#endif // V8_ENABLE_WEBASSEMBLY
1606
1607// Shared routine for multiple binary operations.
1608static void VisitBinop(InstructionSelectorT* selector, OpIndex node,
1609 InstructionCode opcode, FlagsContinuationT* cont) {
1610 X64OperandGeneratorT g(selector);
1611 const Operation& binop = selector->Get(node);
1612 DCHECK_EQ(binop.input_count, 2);
1613 auto left = binop.input(0);
1614 auto right = binop.input(1);
1615 if (selector->IsCommutative(node)) {
1616 if (selector->Is<ConstantOp>(left) && !selector->Is<ConstantOp>(right)) {
1617 std::swap(left, right);
1618 }
1619 }
1620 InstructionOperand inputs[8];
1621 size_t input_count = 0;
1622 InstructionOperand outputs[1];
1623 size_t output_count = 0;
1624
1625 // TODO(turbofan): match complex addressing modes.
1626 if (left == right) {
1627 // If both inputs refer to the same operand, enforce allocating a register
1628 // for both of them to ensure that we don't end up generating code like
1629 // this:
1630 //
1631 // mov rax, [rbp-0x10]
1632 // add rax, [rbp-0x10]
1633 // jo label
1634 InstructionOperand const input = g.UseRegister(left);
1635 inputs[input_count++] = input;
1636 inputs[input_count++] = input;
1637 } else if (g.CanBeImmediate(right)) {
1638 inputs[input_count++] = g.UseRegister(left);
1639 inputs[input_count++] = g.UseImmediate(right);
1640 } else {
1641 int effect_level = selector->GetEffectLevel(node, cont);
1642 if (selector->IsCommutative(node) && g.CanBeBetterLeftOperand(right) &&
1643 (!g.CanBeBetterLeftOperand(left) ||
1644 !g.CanBeMemoryOperand(opcode, node, right, effect_level))) {
1645 std::swap(left, right);
1646 }
1647 if (g.CanBeMemoryOperand(opcode, node, right, effect_level)) {
1648 inputs[input_count++] = g.UseRegister(left);
1649 AddressingMode addressing_mode =
1650 g.GetEffectiveAddressMemoryOperand(right, inputs, &input_count);
1651 opcode |= AddressingModeField::encode(addressing_mode);
1652 } else {
1653 inputs[input_count++] = g.UseRegister(left);
1654 inputs[input_count++] = g.Use(right);
1655 }
1656 }
1657
1658 outputs[output_count++] = g.DefineSameAsFirst(node);
1659
1660 DCHECK_NE(0u, input_count);
1661 DCHECK_EQ(1u, output_count);
1662 DCHECK_GE(arraysize(inputs), input_count);
1663 DCHECK_GE(arraysize(outputs), output_count);
1664
1665 selector->EmitWithContinuation(opcode, output_count, outputs, input_count,
1666 inputs, cont);
1667}
1668
1669// Shared routine for multiple binary operations.
1670std::optional<int32_t> GetWord32Constant(
1671 InstructionSelectorT* selector, OpIndex node,
1672 bool allow_implicit_int64_truncation =
1674 if (auto* constant = selector->Get(node).TryCast<ConstantOp>()) {
1675 if (constant->kind == ConstantOp::Kind::kWord32) {
1676 return constant->word32();
1677 }
1678 if (allow_implicit_int64_truncation &&
1679 constant->kind == ConstantOp::Kind::kWord64) {
1680 return static_cast<int32_t>(constant->word64());
1681 }
1682 }
1683 return std::nullopt;
1684}
1685
1686static void VisitBinop(InstructionSelectorT* selector, OpIndex node,
1687 InstructionCode opcode) {
1688 FlagsContinuationT cont;
1689 VisitBinop(selector, node, opcode, &cont);
1690}
1691
1692void InstructionSelectorT::VisitWord32And(OpIndex node) {
1693 X64OperandGeneratorT g(this);
1694 V<Word32> left;
1695 if (MatchWordBinop<Word32>(node, &left, 0xFF)) {
1696 if (this->is_load(left)) {
1697 LoadRepresentation load_rep = this->load_view(left).loaded_rep();
1698 if (load_rep.representation() == MachineRepresentation::kWord8 &&
1699 load_rep.IsUnsigned()) {
1700 EmitIdentity(node);
1701 return;
1702 }
1703 }
1704 Emit(kX64Movzxbl, g.DefineAsRegister(node), g.Use(left));
1705 return;
1706 } else if (MatchWordBinop<Word32>(node, &left, 0xFFFF)) {
1707 if (this->is_load(left)) {
1708 LoadRepresentation load_rep = this->load_view(left).loaded_rep();
1709 if ((load_rep.representation() == MachineRepresentation::kWord16 ||
1710 load_rep.representation() == MachineRepresentation::kWord8) &&
1711 load_rep.IsUnsigned()) {
1712 EmitIdentity(node);
1713 return;
1714 }
1715 }
1716 Emit(kX64Movzxwl, g.DefineAsRegister(node), g.Use(left));
1717 return;
1718 }
1719 VisitBinop(this, node, kX64And32);
1720}
1721
1722std::optional<uint64_t> TryGetRightWordConstant(InstructionSelectorT* selector,
1723 OpIndex node) {
1724 if (const WordBinopOp* binop = selector->Get(node).TryCast<WordBinopOp>()) {
1725 uint64_t value;
1726 if (selector->MatchUnsignedIntegralConstant(binop->right(), &value)) {
1727 return value;
1728 }
1729 }
1730 return std::nullopt;
1731}
1732
1733void InstructionSelectorT::VisitWord64And(OpIndex node) {
1734 X64OperandGeneratorT g(this);
1735 if (std::optional<uint64_t> constant = TryGetRightWordConstant(this, node)) {
1736 OpIndex left = Get(node).input(0);
1737 if (*constant == 0xFF) {
1738 Emit(kX64Movzxbq, g.DefineAsRegister(node), g.Use(left));
1739 return;
1740 } else if (*constant == 0xFFFF) {
1741 Emit(kX64Movzxwq, g.DefineAsRegister(node), g.Use(left));
1742 return;
1743 } else if (*constant == 0xFFFFFFFF) {
1744 Emit(kX64Movl, g.DefineAsRegister(node), g.Use(left));
1745 return;
1746 } else if (std::numeric_limits<uint32_t>::min() <= *constant &&
1747 *constant <= std::numeric_limits<uint32_t>::max()) {
1748 Emit(kX64And32, g.DefineSameAsFirst(node), g.UseRegister(left),
1749 g.UseImmediate(static_cast<int32_t>(*constant)));
1750 return;
1751 }
1752 }
1753 VisitBinop(this, node, kX64And);
1754}
1755
1756void InstructionSelectorT::VisitWord32Or(OpIndex node) {
1757 VisitBinop(this, node, kX64Or32);
1758}
1759
1760void InstructionSelectorT::VisitWord64Or(OpIndex node) {
1761 VisitBinop(this, node, kX64Or);
1762}
1763
1764void InstructionSelectorT::VisitWord32Xor(OpIndex node) {
1765 X64OperandGeneratorT g(this);
1766 if (std::optional<uint64_t> constant = TryGetRightWordConstant(this, node)) {
1767 if (*constant == static_cast<uint64_t>(-1)) {
1768 Emit(kX64Not32, g.DefineSameAsFirst(node),
1769 g.UseRegister(Get(node).input(0)));
1770 return;
1771 }
1772 }
1773 VisitBinop(this, node, kX64Xor32);
1774}
1775
1776void InstructionSelectorT::VisitWord64Xor(OpIndex node) {
1777 X64OperandGeneratorT g(this);
1778 if (std::optional<uint64_t> constant = TryGetRightWordConstant(this, node)) {
1779 if (*constant == static_cast<uint64_t>(-1)) {
1780 Emit(kX64Not, g.DefineSameAsFirst(node),
1781 g.UseRegister(Get(node).input(0)));
1782 return;
1783 }
1784 }
1785 VisitBinop(this, node, kX64Xor);
1786}
1787
1789 OpIndex node, FlagsContinuation* cont) {
1790 const StackPointerGreaterThanOp& op = Cast<StackPointerGreaterThanOp>(node);
1791 InstructionCode opcode = kArchStackPointerGreaterThan |
1792 MiscField::encode(static_cast<int>(op.kind));
1793
1794 int effect_level = GetEffectLevel(node, cont);
1795
1796 X64OperandGeneratorT g(this);
1797 OpIndex value = op.stack_limit();
1798 if (g.CanBeMemoryOperand(kX64Cmp, node, value, effect_level)) {
1799 DCHECK(this->IsLoadOrLoadImmutable(value));
1800
1801 // GetEffectiveAddressMemoryOperand can create at most 3 inputs.
1802 static constexpr int kMaxInputCount = 3;
1803
1804 size_t input_count = 0;
1805 InstructionOperand inputs[kMaxInputCount];
1806 AddressingMode addressing_mode =
1807 g.GetEffectiveAddressMemoryOperand(value, inputs, &input_count);
1808 opcode |= AddressingModeField::encode(addressing_mode);
1809 DCHECK_LE(input_count, kMaxInputCount);
1810
1811 EmitWithContinuation(opcode, 0, nullptr, input_count, inputs, cont);
1812 } else {
1813 EmitWithContinuation(opcode, g.UseRegister(value), cont);
1814 }
1815}
1816
1817namespace {
1818
1819// Shared routine for multiple 32-bit shift operations.
1820// TODO(bmeurer): Merge this with VisitWord64Shift using template magic?
1821void VisitWord32Shift(InstructionSelectorT* selector, OpIndex node,
1822 ArchOpcode opcode) {
1823 X64OperandGeneratorT g(selector);
1824 const ShiftOp& op = selector->Cast<ShiftOp>(node);
1825 DCHECK_EQ(op.input_count, 2);
1826 auto left = op.left();
1827 auto right = op.right();
1828
1829 if (V<Word64> left64; selector->MatchTruncateWord64ToWord32(left, &left64)) {
1830 left = left64;
1831 }
1832
1833 if (g.CanBeImmediate(right)) {
1834 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(left),
1835 g.UseImmediate(right));
1836 } else {
1837 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(left),
1838 g.UseFixed(right, rcx));
1839 }
1840}
1841
1842// Shared routine for multiple 64-bit shift operations.
1843// TODO(bmeurer): Merge this with VisitWord32Shift using template magic?
1844void VisitWord64Shift(InstructionSelectorT* selector, OpIndex node,
1845 ArchOpcode opcode) {
1846 X64OperandGeneratorT g(selector);
1847 const ShiftOp& op = selector->Cast<ShiftOp>(node);
1848 DCHECK_EQ(op.input_count, 2);
1849 auto left = op.left();
1850 auto right = op.right();
1851
1852 if (g.CanBeImmediate(right)) {
1853 // TODO(nicohartmann@): Implement this for Turboshaft.
1854#if 0
1855 Int64BinopMatcher m(node);
1856 if (opcode == kX64Shr && m.left().IsChangeUint32ToUint64() &&
1857 m.right().HasResolvedValue() && m.right().ResolvedValue() < 32 &&
1858 m.right().ResolvedValue() >= 0) {
1859 opcode = kX64Shr32;
1860 left = left->InputAt(0);
1861 }
1862#endif
1863 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(left),
1864 g.UseImmediate(right));
1865 } else {
1866 // TODO(nicohartmann@): Implement this for Turboshaft.
1867#if 0
1868 Int64BinopMatcher m(node);
1869 if (m.right().IsWord64And()) {
1870 Int64BinopMatcher mright(right);
1871 if (mright.right().Is(0x3F)) {
1872 right = mright.left().node();
1873 }
1874 }
1875#endif
1876 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(left),
1877 g.UseFixed(right, rcx));
1878 }
1879}
1880
1881// Shared routine for multiple shift operations with continuation.
1882bool TryVisitWordShift(InstructionSelectorT* selector, OpIndex node, int bits,
1883 ArchOpcode opcode, FlagsContinuationT* cont) {
1884 DCHECK(bits == 32 || bits == 64);
1885 X64OperandGeneratorT g(selector);
1886 const ShiftOp& op = selector->Cast<ShiftOp>(node);
1887 DCHECK_EQ(op.input_count, 2);
1888 auto left = op.left();
1889 auto right = op.right();
1890
1891 // If the shift count is 0, the flags are not affected.
1892 if (!g.CanBeImmediate(right) ||
1893 (g.GetImmediateIntegerValue(right) & (bits - 1)) == 0) {
1894 return false;
1895 }
1896 InstructionOperand output = g.DefineSameAsFirst(node);
1897 InstructionOperand inputs[2];
1898 inputs[0] = g.UseRegister(left);
1899 inputs[1] = g.UseImmediate(right);
1900 selector->EmitWithContinuation(opcode, 1, &output, 2, inputs, cont);
1901 return true;
1902}
1903
1904void EmitLea(InstructionSelectorT* selector, InstructionCode opcode,
1905 OpIndex result, OpIndex index, int scale, OpIndex base,
1907 X64OperandGeneratorT g(selector);
1908
1909 InstructionOperand inputs[4];
1910 size_t input_count = 0;
1911 AddressingMode mode =
1912 g.GenerateMemoryOperandInputs(index, scale, base, displacement,
1913 displacement_mode, inputs, &input_count);
1914
1915 DCHECK_NE(0u, input_count);
1916 DCHECK_GE(arraysize(inputs), input_count);
1917
1918 InstructionOperand outputs[1];
1919 outputs[0] = g.DefineAsRegister(result);
1920
1921 opcode = AddressingModeField::encode(mode) | opcode;
1922
1923 selector->Emit(opcode, 1, outputs, input_count, inputs);
1924}
1925
1926} // namespace
1927
1928void InstructionSelectorT::VisitWord32Shl(OpIndex node) {
1929 bool plus_one;
1930 OpIndex index;
1931 int scale;
1932 if (MatchScaledIndex(this, node, &index, &scale, &plus_one)) {
1933 OpIndex base = plus_one ? index : OpIndex{};
1934 EmitLea(this, kX64Lea32, node, index, scale, base, 0,
1936 return;
1937 }
1938 VisitWord32Shift(this, node, kX64Shl32);
1939}
1940
1941void InstructionSelectorT::VisitWord64Shl(OpIndex node) {
1942 X64OperandGeneratorT g(this);
1943 // TODO(nicohartmann,dmercadier): Port the Int64ScaleMatcher part of the
1944 // Turbofan version. This is used in the builtin pipeline.
1945 const ShiftOp& shift = this->Get(node).template Cast<ShiftOp>();
1946 OpIndex left = shift.left();
1947 OpIndex right = shift.right();
1948 int32_t cst;
1949 if ((this->Get(left).template Is<Opmask::kChangeUint32ToUint64>() ||
1950 this->Get(left).template Is<Opmask::kChangeInt32ToInt64>()) &&
1951 this->MatchIntegralWord32Constant(right, &cst) &&
1952 base::IsInRange(cst, 32, 63)) {
1953 // There's no need to sign/zero-extend to 64-bit if we shift out the
1954 // upper 32 bits anyway.
1955 Emit(kX64Shl, g.DefineSameAsFirst(node),
1956 g.UseRegister(this->Get(left).input(0)), g.UseImmediate(right));
1957 return;
1958 }
1959 VisitWord64Shift(this, node, kX64Shl);
1960}
1961
1962void InstructionSelectorT::VisitWord32Shr(OpIndex node) {
1963 VisitWord32Shift(this, node, kX64Shr32);
1964}
1965
1966namespace {
1967
1968inline AddressingMode AddDisplacementToAddressingMode(AddressingMode mode) {
1969 switch (mode) {
1970 case kMode_MR:
1971 return kMode_MRI;
1972 case kMode_MR1:
1973 return kMode_MR1I;
1974 case kMode_MR2:
1975 return kMode_MR2I;
1976 case kMode_MR4:
1977 return kMode_MR4I;
1978 case kMode_MR8:
1979 return kMode_MR8I;
1980 case kMode_M1:
1981 return kMode_M1I;
1982 case kMode_M2:
1983 return kMode_M2I;
1984 case kMode_M4:
1985 return kMode_M4I;
1986 case kMode_M8:
1987 return kMode_M8I;
1988 case kMode_None:
1989 case kMode_MRI:
1990 case kMode_MR1I:
1991 case kMode_MR2I:
1992 case kMode_MR4I:
1993 case kMode_MR8I:
1994 case kMode_M1I:
1995 case kMode_M2I:
1996 case kMode_M4I:
1997 case kMode_M8I:
1998 case kMode_Root:
1999 case kMode_MCR:
2000 case kMode_MCRI:
2001 UNREACHABLE();
2002 }
2003 UNREACHABLE();
2004}
2005
2006// {node} should be a right shift. If its input is a 64-bit Load and {node}
2007// shifts it to the right by 32 bits, then this function emits a 32-bit Load of
2008// the high bits only (allowing 1. to load fewer bits and 2. to get rid of the
2009// shift).
2010bool TryEmitLoadForLoadWord64AndShiftRight(InstructionSelectorT* selector,
2011 OpIndex node,
2012 InstructionCode opcode) {
2013 DCHECK(selector->Get(node).template Cast<ShiftOp>().IsRightShift());
2014 const ShiftOp& shift = selector->Get(node).template Cast<ShiftOp>();
2015 X64OperandGeneratorT g(selector);
2016 if (selector->CanCover(node, shift.left()) &&
2017 selector->Get(shift.left()).Is<LoadOp>() &&
2018 selector->MatchIntegralWord32Constant(shift.right(), 32)) {
2019 DCHECK_EQ(selector->GetEffectLevel(node),
2020 selector->GetEffectLevel(shift.left()));
2021 // Just load and sign-extend the interesting 4 bytes instead. This happens,
2022 // for example, when we're loading and untagging SMIs.
2023 auto m =
2024 TryMatchBaseWithScaledIndexAndDisplacement64(selector, shift.left());
2025 if (m.has_value() &&
2026 (m->displacement == 0 || ValueFitsIntoImmediate(m->displacement))) {
2027#ifdef V8_IS_TSAN
2028 // On TSAN builds we require one scratch register. Because of this we also
2029 // have to modify the inputs to take into account possible aliasing and
2030 // use UseUniqueRegister which is not required for non-TSAN builds.
2031 InstructionOperand temps[] = {g.TempRegister()};
2032 size_t temp_count = arraysize(temps);
2034#else
2035 InstructionOperand* temps = nullptr;
2036 size_t temp_count = 0;
2038#endif // V8_IS_TSAN
2039 size_t input_count = 0;
2040 InstructionOperand inputs[3];
2041 AddressingMode mode = g.GetEffectiveAddressMemoryOperand(
2042 shift.left(), inputs, &input_count, reg_kind);
2043 if (m->displacement == 0) {
2044 // Make sure that the addressing mode indicates the presence of an
2045 // immediate displacement. It seems that we never use M1 and M2, but we
2046 // handle them here anyways.
2047 mode = AddDisplacementToAddressingMode(mode);
2048 inputs[input_count++] =
2049 ImmediateOperand(ImmediateOperand::INLINE_INT32, 4);
2050 } else {
2051 // In the case that the base address was zero, the displacement will be
2052 // in a register and replacing it with an immediate is not allowed. This
2053 // usually only happens in dead code anyway.
2054 if (!inputs[input_count - 1].IsImmediate()) return false;
2055 inputs[input_count - 1] =
2056 ImmediateOperand(ImmediateOperand::INLINE_INT32,
2057 static_cast<int32_t>(m->displacement) + 4);
2058 }
2059 InstructionOperand outputs[] = {g.DefineAsRegister(node)};
2060 InstructionCode code = opcode | AddressingModeField::encode(mode);
2061 selector->Emit(code, 1, outputs, input_count, inputs, temp_count, temps);
2062 return true;
2063 }
2064 }
2065 return false;
2066}
2067
2068} // namespace
2069
2070void InstructionSelectorT::VisitWord64Shr(OpIndex node) {
2071 if (TryEmitLoadForLoadWord64AndShiftRight(this, node, kX64Movl)) return;
2072 VisitWord64Shift(this, node, kX64Shr);
2073}
2074
2075void InstructionSelectorT::VisitWord32Sar(OpIndex node) {
2076 // TODO(nicohartmann@): Add this optimization for Turboshaft.
2077#if 0
2078 X64OperandGeneratorT g(this);
2079 Int32BinopMatcher m(node);
2080 if (CanCover(m.node(), m.left().node()) && m.left().IsWord32Shl()) {
2081 Int32BinopMatcher mleft(m.left().node());
2082 if (mleft.right().Is(16) && m.right().Is(16)) {
2083 Emit(kX64Movsxwl, g.DefineAsRegister(node), g.Use(mleft.left().node()));
2084 return;
2085 } else if (mleft.right().Is(24) && m.right().Is(24)) {
2086 Emit(kX64Movsxbl, g.DefineAsRegister(node), g.Use(mleft.left().node()));
2087 return;
2088 }
2089 }
2090#endif
2091 VisitWord32Shift(this, node, kX64Sar32);
2092}
2093
2094void InstructionSelectorT::VisitWord64Sar(OpIndex node) {
2095 if (TryEmitLoadForLoadWord64AndShiftRight(this, node, kX64Movsxlq)) return;
2096 VisitWord64Shift(this, node, kX64Sar);
2097}
2098
2099void InstructionSelectorT::VisitWord32Rol(OpIndex node) {
2100 VisitWord32Shift(this, node, kX64Rol32);
2101}
2102
2103void InstructionSelectorT::VisitWord64Rol(OpIndex node) {
2104 VisitWord64Shift(this, node, kX64Rol);
2105}
2106
2107void InstructionSelectorT::VisitWord32Ror(OpIndex node) {
2108 VisitWord32Shift(this, node, kX64Ror32);
2109}
2110
2111void InstructionSelectorT::VisitWord64Ror(OpIndex node) {
2112 VisitWord64Shift(this, node, kX64Ror);
2113}
2114
2115void InstructionSelectorT::VisitWord32ReverseBits(OpIndex node) {
2116 UNREACHABLE();
2117}
2118
2119void InstructionSelectorT::VisitWord64ReverseBits(OpIndex node) {
2120 UNREACHABLE();
2121}
2122
2123void InstructionSelectorT::VisitWord64ReverseBytes(OpIndex node) {
2124 X64OperandGeneratorT g(this);
2125 const WordUnaryOp& op = Cast<WordUnaryOp>(node);
2126 DCHECK_EQ(op.input_count, 1);
2127 Emit(kX64Bswap, g.DefineSameAsFirst(node), g.UseRegister(op.input()));
2128}
2129
2130void InstructionSelectorT::VisitWord32ReverseBytes(OpIndex node) {
2131 X64OperandGeneratorT g(this);
2132 const WordUnaryOp& op = Cast<WordUnaryOp>(node);
2133 DCHECK_EQ(op.input_count, 1);
2134 Emit(kX64Bswap32, g.DefineSameAsFirst(node), g.UseRegister(op.input()));
2135}
2136
2137void InstructionSelectorT::VisitSimd128ReverseBytes(OpIndex node) {
2138 UNREACHABLE();
2139}
2140
2141void InstructionSelectorT::VisitInt32Add(OpIndex node) {
2142 X64OperandGeneratorT g(this);
2143
2144 std::optional<BaseWithScaledIndexAndDisplacementMatch> m;
2145 const WordBinopOp& add = Cast<WordBinopOp>(node);
2146 OpIndex left = add.left();
2147 OpIndex right = add.right();
2148 // No need to truncate the values before Int32Add.
2149 if (V<Word64> left64; MatchTruncateWord64ToWord32(left, &left64)) {
2150 left = left64;
2151 }
2152 if (V<Word64> right64; MatchTruncateWord64ToWord32(right, &right64)) {
2153 right = right64;
2154 }
2155
2156 DCHECK(LhsIsNotOnlyConstant(this->turboshaft_graph(), left, right));
2157
2158 // Try to match the Add to a leal pattern
2160 right, true);
2161
2162 if (m.has_value()) {
2163 if (ValueFitsIntoImmediate(m->displacement)) {
2164 EmitLea(this, kX64Lea32, node, m->index, m->scale, m->base,
2165 m->displacement, m->displacement_mode);
2166 return;
2167 }
2168 }
2169
2170 // No leal pattern match, use addl
2171 VisitBinop(this, node, kX64Add32);
2172}
2173
2174void InstructionSelectorT::VisitInt64Add(OpIndex node) {
2175 X64OperandGeneratorT g(this);
2176 // Try to match the Add to a leaq pattern
2177 if (auto match = TryMatchBaseWithScaledIndexAndDisplacement64(this, node)) {
2178 if (ValueFitsIntoImmediate(match->displacement)) {
2179 EmitLea(this, kX64Lea, node, match->index, match->scale, match->base,
2180 match->displacement, match->displacement_mode);
2181 return;
2182 }
2183 }
2184
2185 // No leal pattern match, use addq
2186 VisitBinop(this, node, kX64Add);
2187}
2188
2189void InstructionSelectorT::VisitInt64AddWithOverflow(OpIndex node) {
2190 OptionalOpIndex ovf = FindProjection(node, 1);
2191 if (ovf.valid()) {
2193 return VisitBinop(this, node, kX64Add, &cont);
2194 }
2195 FlagsContinuation cont;
2196 VisitBinop(this, node, kX64Add, &cont);
2197}
2198
2199void InstructionSelectorT::VisitInt32Sub(OpIndex node) {
2200 X64OperandGeneratorT g(this);
2201 auto [left, right] = Inputs<WordBinopOp>(node);
2202 if (g.CanBeImmediate(right)) {
2203 int32_t imm = g.GetImmediateIntegerValue(right);
2204 if (imm == 0) {
2205 if (this->Get(left).outputs_rep()[0] ==
2207 // {EmitIdentity} reuses the virtual register of the first input
2208 // for the output. This is exactly what we want here.
2209 EmitIdentity(node);
2210 } else {
2211 // Emit "movl" for subtraction of 0.
2212 Emit(kX64Movl, g.DefineAsRegister(node), g.UseRegister(left));
2213 }
2214 } else {
2215 // Omit truncation and turn subtractions of constant values into immediate
2216 // "leal" instructions by negating the value.
2217 Emit(kX64Lea32 | AddressingModeField::encode(kMode_MRI),
2218 g.DefineAsRegister(node), g.UseRegister(left),
2219 g.TempImmediate(base::NegateWithWraparound(imm)));
2220 }
2221 return;
2222 }
2223
2224 if (MatchIntegralZero(left)) {
2225 Emit(kX64Neg32, g.DefineSameAsFirst(node), g.UseRegister(right));
2226 return;
2227 }
2228
2229 VisitBinop(this, node, kX64Sub32);
2230}
2231
2232void InstructionSelectorT::VisitInt64Sub(OpIndex node) {
2233 X64OperandGeneratorT g(this);
2234 const WordBinopOp& binop = this->Get(node).Cast<WordBinopOp>();
2235 DCHECK_EQ(binop.kind, WordBinopOp::Kind::kSub);
2236
2237 if (MatchIntegralZero(binop.left())) {
2238 Emit(kX64Neg, g.DefineSameAsFirst(node), g.UseRegister(binop.right()));
2239 return;
2240 }
2241 if (auto constant = TryGetRightWordConstant(this, node)) {
2242 int64_t immediate_value = -*constant;
2243 if (ValueFitsIntoImmediate(immediate_value)) {
2244 // Turn subtractions of constant values into immediate "leaq" instructions
2245 // by negating the value.
2246 Emit(kX64Lea | AddressingModeField::encode(kMode_MRI),
2247 g.DefineAsRegister(node), g.UseRegister(binop.left()),
2248 g.TempImmediate(static_cast<int32_t>(immediate_value)));
2249 return;
2250 }
2251 }
2252 VisitBinop(this, node, kX64Sub);
2253}
2254
2255void InstructionSelectorT::VisitInt64SubWithOverflow(OpIndex node) {
2256 OptionalOpIndex ovf = FindProjection(node, 1);
2257 if (ovf.valid()) {
2259 return VisitBinop(this, node, kX64Sub, &cont);
2260 }
2261 FlagsContinuation cont;
2262 VisitBinop(this, node, kX64Sub, &cont);
2263}
2264
2265namespace {
2266
2267void VisitMul(InstructionSelectorT* selector, OpIndex node, ArchOpcode opcode) {
2268 X64OperandGeneratorT g(selector);
2269 auto [left, right] = selector->Inputs<WordBinopOp>(node);
2270 if (g.CanBeImmediate(right)) {
2271 selector->Emit(opcode, g.DefineAsRegister(node), g.Use(left),
2272 g.UseImmediate(right));
2273 } else {
2274 if (g.CanBeBetterLeftOperand(right)) {
2275 std::swap(left, right);
2276 }
2277 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(left),
2278 g.Use(right));
2279 }
2280}
2281
2282void VisitMulHigh(InstructionSelectorT* selector, OpIndex node,
2283 ArchOpcode opcode) {
2284 X64OperandGeneratorT g(selector);
2285 auto [left, right] = selector->Inputs<WordBinopOp>(node);
2286 if (selector->IsLive(left) && !selector->IsLive(right)) {
2287 std::swap(left, right);
2288 }
2289 InstructionOperand temps[] = {g.TempRegister(rax)};
2290 // TODO(turbofan): We use UseUniqueRegister here to improve register
2291 // allocation.
2292 selector->Emit(opcode, g.DefineAsFixed(node, rdx), g.UseFixed(left, rax),
2293 g.UseUniqueRegister(right), arraysize(temps), temps);
2294}
2295
2296void VisitDiv(InstructionSelectorT* selector, OpIndex node, ArchOpcode opcode) {
2297 X64OperandGeneratorT g(selector);
2298 auto [left, right] = selector->Inputs<WordBinopOp>(node);
2299 InstructionOperand temps[] = {g.TempRegister(rdx)};
2300 selector->Emit(opcode, g.DefineAsFixed(node, rax), g.UseFixed(left, rax),
2301 g.UseUniqueRegister(right), arraysize(temps), temps);
2302}
2303
2304void VisitMod(InstructionSelectorT* selector, OpIndex node, ArchOpcode opcode) {
2305 X64OperandGeneratorT g(selector);
2306 auto [left, right] = selector->Inputs<WordBinopOp>(node);
2307 InstructionOperand temps[] = {g.TempRegister(rax)};
2308 selector->Emit(opcode, g.DefineAsFixed(node, rdx), g.UseFixed(left, rax),
2309 g.UseUniqueRegister(right), arraysize(temps), temps);
2310}
2311
2312} // namespace
2313
2314void InstructionSelectorT::VisitInt32Mul(OpIndex node) {
2315 if (auto m = TryMatchScaledIndex32(this, node, true)) {
2316 EmitLea(this, kX64Lea32, node, m->index, m->scale, m->base, 0,
2318 return;
2319 }
2320 VisitMul(this, node, kX64Imul32);
2321}
2322
2323void InstructionSelectorT::VisitInt32MulWithOverflow(OpIndex node) {
2324 OptionalOpIndex ovf = FindProjection(node, 1);
2325 if (ovf.valid()) {
2327 return VisitBinop(this, node, kX64Imul32, &cont);
2328 }
2329 FlagsContinuation cont;
2330 VisitBinop(this, node, kX64Imul32, &cont);
2331}
2332
2333void InstructionSelectorT::VisitInt64Mul(OpIndex node) {
2334 if (auto m = TryMatchScaledIndex64(this, node, true)) {
2335 EmitLea(this, kX64Lea, node, m->index, m->scale, m->base, 0,
2337 return;
2338 }
2339 VisitMul(this, node, kX64Imul);
2340}
2341
2342void InstructionSelectorT::VisitInt64MulWithOverflow(OpIndex node) {
2343 OptionalOpIndex ovf = FindProjection(node, 1);
2344 if (ovf.valid()) {
2346 return VisitBinop(this, node, kX64Imul, &cont);
2347 }
2348 FlagsContinuation cont;
2349 VisitBinop(this, node, kX64Imul, &cont);
2350}
2351
2352void InstructionSelectorT::VisitInt32MulHigh(OpIndex node) {
2353 VisitMulHigh(this, node, kX64ImulHigh32);
2354}
2355
2356void InstructionSelectorT::VisitInt64MulHigh(OpIndex node) {
2357 VisitMulHigh(this, node, kX64ImulHigh64);
2358}
2359
2360void InstructionSelectorT::VisitInt32Div(OpIndex node) {
2361 VisitDiv(this, node, kX64Idiv32);
2362}
2363
2364void InstructionSelectorT::VisitInt64Div(OpIndex node) {
2365 VisitDiv(this, node, kX64Idiv);
2366}
2367
2368void InstructionSelectorT::VisitUint32Div(OpIndex node) {
2369 VisitDiv(this, node, kX64Udiv32);
2370}
2371
2372void InstructionSelectorT::VisitUint64Div(OpIndex node) {
2373 VisitDiv(this, node, kX64Udiv);
2374}
2375
2376void InstructionSelectorT::VisitInt32Mod(OpIndex node) {
2377 VisitMod(this, node, kX64Idiv32);
2378}
2379
2380void InstructionSelectorT::VisitInt64Mod(OpIndex node) {
2381 VisitMod(this, node, kX64Idiv);
2382}
2383
2384void InstructionSelectorT::VisitUint32Mod(OpIndex node) {
2385 VisitMod(this, node, kX64Udiv32);
2386}
2387
2388void InstructionSelectorT::VisitUint64Mod(OpIndex node) {
2389 VisitMod(this, node, kX64Udiv);
2390}
2391
2392void InstructionSelectorT::VisitUint32MulHigh(OpIndex node) {
2393 VisitMulHigh(this, node, kX64UmulHigh32);
2394}
2395
2396void InstructionSelectorT::VisitUint64MulHigh(OpIndex node) {
2397 VisitMulHigh(this, node, kX64UmulHigh64);
2398}
2399
2400// TryTruncateFloat32ToInt64 and TryTruncateFloat64ToInt64 operations attempt
2401// truncation from 32|64-bit float to 64-bit integer by performing roughly the
2402// following steps:
2403// 1. Round the original FP value to zero, store in `rounded`;
2404// 2. Convert the original FP value to integer;
2405// 3. Convert the integer value back to floating point, store in
2406// `converted_back`;
2407// 4. If `rounded` == `converted_back`:
2408// Set Projection(1) := 1; -- the value was in range
2409// Else:
2410// Set Projection(1) := 0; -- the value was out of range
2411void InstructionSelectorT::VisitTryTruncateFloat32ToInt64(OpIndex node) {
2412 const TryChangeOp& op = Cast<TryChangeOp>(node);
2413 DCHECK_EQ(op.input_count, 1);
2414 X64OperandGeneratorT g(this);
2415 InstructionOperand inputs[] = {g.UseRegister(op.input())};
2416 InstructionOperand outputs[2];
2417 InstructionOperand temps[1];
2418 size_t output_count = 0;
2419 size_t temp_count = 0;
2420 outputs[output_count++] = g.DefineAsRegister(node);
2421
2422 OptionalOpIndex success_output = FindProjection(node, 1);
2423 if (success_output.valid()) {
2424 outputs[output_count++] = g.DefineAsRegister(success_output.value());
2425 temps[temp_count++] = g.TempSimd128Register();
2426 }
2427
2428 Emit(kSSEFloat32ToInt64, output_count, outputs, 1, inputs, temp_count, temps);
2429}
2430
2431// TryTruncateFloatNNToUintDD operations attempt truncation from NN-bit
2432// float to DD-bit integer by using ConvertFloatToUintDD macro instructions.
2433// It performs a float-to-int instruction, rounding to zero and tests whether
2434// the result is positive integer (the default, fast case), which means the
2435// value is in range. Then, we set Projection(1) := 1. Else, we perform
2436// additional subtraction, conversion and (in case the value was originally
2437// negative, but still within range) we restore it and set Projection(1) := 1.
2438// In all other cases we set Projection(1) := 0, denoting value out of range.
2439void InstructionSelectorT::VisitTryTruncateFloat64ToUint32(OpIndex node) {
2440 const TryChangeOp& op = Cast<TryChangeOp>(node);
2441 DCHECK_EQ(op.input_count, 1);
2442 X64OperandGeneratorT g(this);
2443 InstructionOperand inputs[] = {g.UseRegister(op.input())};
2444 InstructionOperand outputs[2];
2445 size_t output_count = 0;
2446 outputs[output_count++] = g.DefineAsRegister(node);
2447
2448 OptionalOpIndex success_output = FindProjection(node, 1);
2449 if (success_output.valid()) {
2450 outputs[output_count++] = g.DefineAsRegister(success_output.value());
2451 }
2452
2453 Emit(kSSEFloat64ToUint32, output_count, outputs, 1, inputs);
2454}
2455
2456void InstructionSelectorT::VisitTryTruncateFloat32ToUint64(OpIndex node) {
2457 const TryChangeOp& op = Cast<TryChangeOp>(node);
2458 DCHECK_EQ(op.input_count, 1);
2459 X64OperandGeneratorT g(this);
2460 InstructionOperand inputs[] = {g.UseRegister(op.input())};
2461 InstructionOperand outputs[2];
2462 size_t output_count = 0;
2463 outputs[output_count++] = g.DefineAsRegister(node);
2464
2465 OptionalOpIndex success_output = FindProjection(node, 1);
2466 if (success_output.valid()) {
2467 outputs[output_count++] = g.DefineAsRegister(success_output.value());
2468 }
2469
2470 Emit(kSSEFloat32ToUint64, output_count, outputs, 1, inputs);
2471}
2472
2473void InstructionSelectorT::VisitTryTruncateFloat64ToUint64(OpIndex node) {
2474 const TryChangeOp& op = Cast<TryChangeOp>(node);
2475 DCHECK_EQ(op.input_count, 1);
2476 X64OperandGeneratorT g(this);
2477 InstructionOperand inputs[] = {g.UseRegister(op.input())};
2478 InstructionOperand outputs[2];
2479 size_t output_count = 0;
2480 outputs[output_count++] = g.DefineAsRegister(node);
2481
2482 OptionalOpIndex success_output = FindProjection(node, 1);
2483 if (success_output.valid()) {
2484 outputs[output_count++] = g.DefineAsRegister(success_output.value());
2485 }
2486
2487 Emit(kSSEFloat64ToUint64, output_count, outputs, 1, inputs);
2488}
2489
2490void InstructionSelectorT::VisitTryTruncateFloat64ToInt64(OpIndex node) {
2491 const TryChangeOp& op = Cast<TryChangeOp>(node);
2492 DCHECK_EQ(op.input_count, 1);
2493 X64OperandGeneratorT g(this);
2494 InstructionOperand inputs[] = {g.UseRegister(op.input())};
2495 InstructionOperand outputs[2];
2496 InstructionOperand temps[1];
2497 size_t output_count = 0;
2498 size_t temp_count = 0;
2499 outputs[output_count++] = g.DefineAsRegister(node);
2500
2501 OptionalOpIndex success_output = FindProjection(node, 1);
2502 if (success_output.valid()) {
2503 outputs[output_count++] = g.DefineAsRegister(success_output.value());
2504 temps[temp_count++] = g.TempSimd128Register();
2505 }
2506
2507 Emit(kSSEFloat64ToInt64, output_count, outputs, 1, inputs, temp_count, temps);
2508}
2509
2510void InstructionSelectorT::VisitTryTruncateFloat64ToInt32(OpIndex node) {
2511 const TryChangeOp& op = Cast<TryChangeOp>(node);
2512 DCHECK_EQ(op.input_count, 1);
2513 X64OperandGeneratorT g(this);
2514 InstructionOperand inputs[] = {g.UseRegister(op.input())};
2515 InstructionOperand outputs[2];
2516 InstructionOperand temps[1];
2517 size_t output_count = 0;
2518 size_t temp_count = 0;
2519 outputs[output_count++] = g.DefineAsRegister(node);
2520
2521 OptionalOpIndex success_output = FindProjection(node, 1);
2522 if (success_output.valid()) {
2523 outputs[output_count++] = g.DefineAsRegister(success_output.value());
2524 temps[temp_count++] = g.TempSimd128Register();
2525 }
2526
2527 Emit(kSSEFloat64ToInt32, output_count, outputs, 1, inputs, temp_count, temps);
2528}
2529
2530void InstructionSelectorT::VisitBitcastWord32ToWord64(OpIndex node) {
2533 EmitIdentity(node);
2534}
2535
2536void InstructionSelectorT::VisitChangeInt32ToInt64(OpIndex node) {
2537 const ChangeOp& op = Cast<ChangeOp>(node);
2538 DCHECK_EQ(op.input_count, 1);
2539
2540 X64OperandGeneratorT g(this);
2541 auto value = op.input();
2542 if (this->IsLoadOrLoadImmutable(value) && CanCover(node, value)) {
2543 LoadRepresentation load_rep = this->load_view(value).loaded_rep();
2544 MachineRepresentation rep = load_rep.representation();
2546 switch (rep) {
2547 case MachineRepresentation::kBit: // Fall through.
2549 opcode = load_rep.IsSigned() ? kX64Movsxbq : kX64Movzxbq;
2550 break;
2552 opcode = load_rep.IsSigned() ? kX64Movsxwq : kX64Movzxwq;
2553 break;
2556 // Since BitcastElider may remove nodes of
2557 // IrOpcode::kTruncateInt64ToInt32 and directly use the inputs, values
2558 // with kWord64 can also reach this line.
2562 // ChangeInt32ToInt64 must interpret its input as a _signed_ 32-bit
2563 // integer, so here we must sign-extend the loaded value in any case.
2564 opcode = kX64Movsxlq;
2565 break;
2566 default:
2567 UNREACHABLE();
2568 }
2569 InstructionOperand outputs[] = {g.DefineAsRegister(node)};
2570 size_t input_count = 0;
2571 InstructionOperand inputs[3];
2572 AddressingMode mode =
2573 g.GetEffectiveAddressMemoryOperand(value, inputs, &input_count);
2575 Emit(opcode, 1, outputs, input_count, inputs);
2576 } else {
2577 Emit(kX64Movsxlq, g.DefineAsRegister(node), g.Use(value));
2578 }
2579}
2580
2581bool InstructionSelectorT::ZeroExtendsWord32ToWord64NoPhis(OpIndex node) {
2582 const Operation& op = Get(node);
2583 switch (op.opcode) {
2584 case Opcode::kWordBinop: {
2585 const auto& binop = op.Cast<WordBinopOp>();
2586 if (binop.rep != WordRepresentation::Word32()) return false;
2587 DCHECK(binop.kind == WordBinopOp::Kind::kBitwiseAnd ||
2588 binop.kind == WordBinopOp::Kind::kBitwiseOr ||
2589 binop.kind == WordBinopOp::Kind::kBitwiseXor ||
2590 binop.kind == WordBinopOp::Kind::kAdd ||
2591 binop.kind == WordBinopOp::Kind::kSub ||
2592 binop.kind == WordBinopOp::Kind::kMul ||
2593 binop.kind == WordBinopOp::Kind::kSignedDiv ||
2594 binop.kind == WordBinopOp::Kind::kUnsignedDiv ||
2595 binop.kind == WordBinopOp::Kind::kSignedMod ||
2596 binop.kind == WordBinopOp::Kind::kUnsignedMod ||
2597 binop.kind == WordBinopOp::Kind::kSignedMulOverflownBits ||
2598 binop.kind == WordBinopOp::Kind::kUnsignedMulOverflownBits);
2599 return true;
2600 }
2601 case Opcode::kShift: {
2602 const auto& shift = op.Cast<ShiftOp>();
2603 if (shift.rep != WordRepresentation::Word32()) return false;
2604 DCHECK(shift.kind == ShiftOp::Kind::kShiftLeft ||
2605 shift.kind == ShiftOp::Kind::kShiftRightLogical ||
2606 shift.kind == ShiftOp::Kind::kShiftRightArithmetic ||
2607 shift.kind == ShiftOp::Kind::kShiftRightArithmeticShiftOutZeros ||
2608 shift.kind == ShiftOp::Kind::kRotateLeft ||
2609 shift.kind == ShiftOp::Kind::kRotateRight);
2610 return true;
2611 }
2612 case Opcode::kComparison: {
2613 const auto& comparison = op.Cast<ComparisonOp>();
2614 DCHECK(comparison.kind == ComparisonOp::Kind::kEqual ||
2615 comparison.kind == ComparisonOp::Kind::kSignedLessThan ||
2616 comparison.kind == ComparisonOp::Kind::kSignedLessThanOrEqual ||
2617 comparison.kind == ComparisonOp::Kind::kUnsignedLessThan ||
2618 comparison.kind == ComparisonOp::Kind::kUnsignedLessThanOrEqual);
2619 return comparison.rep == RegisterRepresentation::Word32();
2620 }
2621 case Opcode::kProjection: {
2622 const auto& projection = op.Cast<ProjectionOp>();
2623 if (const auto* binop =
2624 this->Get(projection.input()).TryCast<OverflowCheckedBinopOp>()) {
2625 DCHECK(binop->kind == OverflowCheckedBinopOp::Kind::kSignedAdd ||
2626 binop->kind == OverflowCheckedBinopOp::Kind::kSignedSub ||
2627 binop->kind == OverflowCheckedBinopOp::Kind::kSignedMul);
2628 return binop->rep == RegisterRepresentation::Word32();
2629 }
2630 return false;
2631 }
2632 case Opcode::kLoad: {
2633 const auto& load = op.Cast<LoadOp>();
2634 // The movzxbl/movsxbl/movzxwl/movsxwl/movl operations implicitly
2635 // zero-extend to 64-bit on x64, so the zero-extension is a no-op.
2636 switch (load.loaded_rep.ToMachineType().representation()) {
2640 return true;
2641 default:
2642 break;
2643 }
2644 return false;
2645 }
2646 case Opcode::kConstant: {
2647 X64OperandGeneratorT g(this);
2648 // Constants are loaded with movl or movq, or xorl for zero; see
2649 // CodeGenerator::AssembleMove. So any non-negative constant that fits
2650 // in a 32-bit signed integer is zero-extended to 64 bits.
2651 if (g.CanBeImmediate(node)) {
2652 return g.GetImmediateIntegerValue(node) >= 0;
2653 }
2654 return false;
2655 }
2656 case Opcode::kChange:
2658 default:
2659 return false;
2660 }
2661}
2662
2663void InstructionSelectorT::VisitChangeUint32ToUint64(OpIndex node) {
2664 X64OperandGeneratorT g(this);
2665 const ChangeOp& op = Cast<ChangeOp>(node);
2666 DCHECK_EQ(op.input_count, 1);
2667 OpIndex value = op.input();
2668 if (ZeroExtendsWord32ToWord64(value)) {
2669 // These 32-bit operations implicitly zero-extend to 64-bit on x64, so the
2670 // zero-extension is a no-op.
2671 return EmitIdentity(node);
2672 }
2673 Emit(kX64Movl, g.DefineAsRegister(node), g.Use(value));
2674}
2675
2676namespace {
2677
2678void VisitRO(InstructionSelectorT* selector, OpIndex node,
2679 InstructionCode opcode) {
2680 X64OperandGeneratorT g(selector);
2681 const Operation& op = selector->Get(node);
2682 DCHECK_EQ(op.input_count, 1);
2683 selector->Emit(opcode, g.DefineAsRegister(node), g.Use(op.input(0)));
2684}
2685
2686void VisitRR(InstructionSelectorT* selector, OpIndex node,
2687 InstructionCode opcode) {
2688 X64OperandGeneratorT g(selector);
2689 const Operation& op = selector->Get(node);
2690 DCHECK_EQ(op.input_count, 1);
2691 selector->Emit(opcode, g.DefineAsRegister(node), g.UseRegister(op.input(0)));
2692}
2693
2694void VisitRRO(InstructionSelectorT* selector, OpIndex node,
2695 InstructionCode opcode) {
2696 X64OperandGeneratorT g(selector);
2697 const Operation& op = selector->Get(node);
2698 DCHECK_EQ(op.input_count, 2);
2699 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(op.input(0)),
2700 g.Use(op.input(1)));
2701}
2702
2703void VisitFloatBinop(InstructionSelectorT* selector, OpIndex node,
2704 InstructionCode avx_opcode, InstructionCode sse_opcode) {
2705 X64OperandGeneratorT g(selector);
2706 const FloatBinopOp& op = selector->Cast<FloatBinopOp>(node);
2707 DCHECK_EQ(op.input_count, 2);
2708 auto left = op.left();
2709 auto right = op.right();
2710 InstructionOperand inputs[8];
2711 size_t input_count = 0;
2712 InstructionOperand outputs[1];
2713 size_t output_count = 0;
2714 OpIndex trapping_load = {};
2715
2716 if (left == right) {
2717 // If both inputs refer to the same operand, enforce allocating a register
2718 // for both of them to ensure that we don't end up generating code like
2719 // this:
2720 //
2721 // movss rax, [rbp-0x10]
2722 // addss rax, [rbp-0x10]
2723 // jo label
2724 InstructionOperand const input = g.UseRegister(left);
2725 inputs[input_count++] = input;
2726 inputs[input_count++] = input;
2727 } else {
2728 int effect_level = selector->GetEffectLevel(node);
2729 if (selector->IsCommutative(node) &&
2730 (g.CanBeBetterLeftOperand(right) ||
2731 g.CanBeMemoryOperand(avx_opcode, node, left, effect_level)) &&
2732 (!g.CanBeBetterLeftOperand(left) ||
2733 !g.CanBeMemoryOperand(avx_opcode, node, right, effect_level))) {
2734 std::swap(left, right);
2735 }
2736 if (g.CanBeMemoryOperand(avx_opcode, node, right, effect_level)) {
2737 inputs[input_count++] = g.UseRegister(left);
2738 AddressingMode addressing_mode =
2739 g.GetEffectiveAddressMemoryOperand(right, inputs, &input_count);
2740 avx_opcode |= AddressingModeField::encode(addressing_mode);
2741 sse_opcode |= AddressingModeField::encode(addressing_mode);
2742 if (g.IsProtectedLoad(right) &&
2743 selector->CanCoverProtectedLoad(node, right)) {
2744 // In {CanBeMemoryOperand} we have already checked that
2745 // CanCover(node, right) succeds, which means that there is no
2746 // instruction with Effects required_when_unused or
2747 // produces.control_flow between right and node, and that the node has
2748 // no other uses. Therefore, we can record the fact that 'right' was
2749 // embedded in 'node' and we can later delete the Load instruction.
2750 selector->MarkAsProtected(node);
2751 avx_opcode |=
2753 sse_opcode |=
2755 selector->SetProtectedLoadToRemove(right);
2756 trapping_load = right;
2757 }
2758 } else {
2759 inputs[input_count++] = g.UseRegister(left);
2760 inputs[input_count++] = g.Use(right);
2761 }
2762 }
2763
2764 DCHECK_NE(0u, input_count);
2765 DCHECK_GE(arraysize(inputs), input_count);
2766 InstructionCode code = selector->IsSupported(AVX) ? avx_opcode : sse_opcode;
2767 outputs[output_count++] = selector->IsSupported(AVX)
2768 ? g.DefineAsRegister(node)
2769 : g.DefineSameAsFirst(node);
2770 DCHECK_EQ(1u, output_count);
2771 DCHECK_GE(arraysize(outputs), output_count);
2772 Instruction* instr =
2773 selector->Emit(code, output_count, outputs, input_count, inputs);
2774 if (trapping_load.valid()) {
2775 selector->UpdateSourcePosition(instr, trapping_load);
2776 }
2777}
2778
2779void VisitFloatUnop(InstructionSelectorT* selector, OpIndex node, OpIndex input,
2780 InstructionCode opcode) {
2781 X64OperandGeneratorT g(selector);
2782 if (selector->IsSupported(AVX)) {
2783 selector->Emit(opcode, g.DefineAsRegister(node), g.UseRegister(input));
2784 } else {
2785 selector->Emit(opcode, g.DefineSameAsFirst(node), g.UseRegister(input));
2786 }
2787}
2788
2789} // namespace
2790
2791#define RO_OP_T_LIST(V) \
2792 V(Word64Clz, kX64Lzcnt) \
2793 V(Word32Clz, kX64Lzcnt32) \
2794 V(Word64Ctz, kX64Tzcnt) \
2795 V(Word32Ctz, kX64Tzcnt32) \
2796 V(Word64Popcnt, kX64Popcnt) \
2797 V(Word32Popcnt, kX64Popcnt32) \
2798 V(Float64Sqrt, kSSEFloat64Sqrt) \
2799 V(Float32Sqrt, kSSEFloat32Sqrt) \
2800 V(RoundFloat64ToInt32, kSSEFloat64ToInt32) \
2801 V(ChangeInt32ToFloat64, kSSEInt32ToFloat64) \
2802 V(TruncateFloat64ToFloat32, kSSEFloat64ToFloat32) \
2803 V(ChangeFloat32ToFloat64, kSSEFloat32ToFloat64) \
2804 V(ChangeFloat64ToInt32, kSSEFloat64ToInt32) \
2805 V(ChangeFloat64ToUint32, kSSEFloat64ToUint32 | MiscField::encode(1)) \
2806 V(ChangeFloat64ToInt64, kSSEFloat64ToInt64) \
2807 V(ChangeFloat64ToUint64, kSSEFloat64ToUint64) \
2808 V(RoundInt32ToFloat32, kSSEInt32ToFloat32) \
2809 V(RoundInt64ToFloat32, kSSEInt64ToFloat32) \
2810 V(RoundUint64ToFloat32, kSSEUint64ToFloat32) \
2811 V(RoundInt64ToFloat64, kSSEInt64ToFloat64) \
2812 V(RoundUint64ToFloat64, kSSEUint64ToFloat64) \
2813 V(RoundUint32ToFloat32, kSSEUint32ToFloat32) \
2814 V(ChangeInt64ToFloat64, kSSEInt64ToFloat64) \
2815 V(ChangeUint32ToFloat64, kSSEUint32ToFloat64) \
2816 V(Float64ExtractLowWord32, kSSEFloat64ExtractLowWord32) \
2817 V(Float64ExtractHighWord32, kSSEFloat64ExtractHighWord32) \
2818 V(BitcastFloat32ToInt32, kX64BitcastFI) \
2819 V(BitcastFloat64ToInt64, kX64BitcastDL) \
2820 V(BitcastInt32ToFloat32, kX64BitcastIF) \
2821 V(BitcastInt64ToFloat64, kX64BitcastLD) \
2822 V(SignExtendWord8ToInt32, kX64Movsxbl) \
2823 V(SignExtendWord16ToInt32, kX64Movsxwl) \
2824 V(SignExtendWord8ToInt64, kX64Movsxbq) \
2825 V(SignExtendWord16ToInt64, kX64Movsxwq) \
2826 V(TruncateFloat64ToInt64, kSSEFloat64ToInt64) \
2827 V(TruncateFloat32ToInt32, kSSEFloat32ToInt32) \
2828 V(TruncateFloat32ToUint32, kSSEFloat32ToUint32)
2829
2830#ifdef V8_ENABLE_WEBASSEMBLY
2831#define RR_OP_T_LIST_WEBASSEMBLY(V) \
2832 V(F16x8Ceil, kX64F16x8Round | MiscField::encode(kRoundUp)) \
2833 V(F16x8Floor, kX64F16x8Round | MiscField::encode(kRoundDown)) \
2834 V(F16x8Trunc, kX64F16x8Round | MiscField::encode(kRoundToZero)) \
2835 V(F16x8NearestInt, kX64F16x8Round | MiscField::encode(kRoundToNearest)) \
2836 V(F32x4Ceil, kX64F32x4Round | MiscField::encode(kRoundUp)) \
2837 V(F32x4Floor, kX64F32x4Round | MiscField::encode(kRoundDown)) \
2838 V(F32x4Trunc, kX64F32x4Round | MiscField::encode(kRoundToZero)) \
2839 V(F32x4NearestInt, kX64F32x4Round | MiscField::encode(kRoundToNearest)) \
2840 V(F64x2Ceil, kX64F64x2Round | MiscField::encode(kRoundUp)) \
2841 V(F64x2Floor, kX64F64x2Round | MiscField::encode(kRoundDown)) \
2842 V(F64x2Trunc, kX64F64x2Round | MiscField::encode(kRoundToZero)) \
2843 V(F64x2NearestInt, kX64F64x2Round | MiscField::encode(kRoundToNearest))
2844#else
2845#define RR_OP_T_LIST_WEBASSEMBLY(V)
2846#endif // V8_ENABLE_WEBASSEMBLY
2847
2848#define RR_OP_T_LIST(V) \
2849 V(TruncateFloat64ToUint32, kSSEFloat64ToUint32 | MiscField::encode(0)) \
2850 V(SignExtendWord32ToInt64, kX64Movsxlq) \
2851 V(Float32RoundDown, kSSEFloat32Round | MiscField::encode(kRoundDown)) \
2852 V(Float64RoundDown, kSSEFloat64Round | MiscField::encode(kRoundDown)) \
2853 V(Float32RoundUp, kSSEFloat32Round | MiscField::encode(kRoundUp)) \
2854 V(Float64RoundUp, kSSEFloat64Round | MiscField::encode(kRoundUp)) \
2855 V(Float32RoundTruncate, kSSEFloat32Round | MiscField::encode(kRoundToZero)) \
2856 V(Float64RoundTruncate, kSSEFloat64Round | MiscField::encode(kRoundToZero)) \
2857 V(Float32RoundTiesEven, \
2858 kSSEFloat32Round | MiscField::encode(kRoundToNearest)) \
2859 V(Float64RoundTiesEven, \
2860 kSSEFloat64Round | MiscField::encode(kRoundToNearest)) \
2861 RR_OP_T_LIST_WEBASSEMBLY(V)
2862
2863#define RO_VISITOR(Name, opcode) \
2864 void InstructionSelectorT::Visit##Name(OpIndex node) { \
2865 VisitRO(this, node, opcode); \
2866 }
2868#undef RO_VIISTOR
2869#undef RO_OP_T_LIST
2870
2871#define RR_VISITOR(Name, opcode) \
2872 void InstructionSelectorT::Visit##Name(OpIndex node) { \
2873 VisitRR(this, node, opcode); \
2874 }
2876#undef RR_VISITOR
2877#undef RR_OP_T_LIST
2878
2879void InstructionSelectorT::VisitTruncateFloat64ToWord32(OpIndex node) {
2880 VisitRR(this, node, kArchTruncateDoubleToI);
2881}
2882
2883void InstructionSelectorT::VisitTruncateFloat64ToFloat16RawBits(OpIndex node) {
2884 X64OperandGeneratorT g(this);
2885 const ChangeOp& op = Cast<ChangeOp>(node);
2886 InstructionOperand temps[] = {g.TempDoubleRegister(), g.TempRegister()};
2887 Emit(kSSEFloat64ToFloat16RawBits, g.DefineAsRegister(node),
2888 g.UseUniqueRegister(op.input()), arraysize(temps), temps);
2889}
2890
2891void InstructionSelectorT::VisitChangeFloat16RawBitsToFloat64(OpIndex node) {
2892 X64OperandGeneratorT g(this);
2893 const ChangeOp& op = Cast<ChangeOp>(node);
2894 InstructionOperand temps[] = {g.TempDoubleRegister()};
2895 Emit(kSSEFloat16RawBitsToFloat64, g.DefineAsRegister(node),
2896 g.UseRegister(op.input()), arraysize(temps), temps);
2897}
2898
2899void InstructionSelectorT::VisitTruncateInt64ToInt32(OpIndex node) {
2900 // We rely on the fact that TruncateInt64ToInt32 zero extends the
2901 // value (see ZeroExtendsWord32ToWord64). So all code paths here
2902 // have to satisfy that condition.
2903 X64OperandGeneratorT g(this);
2904 const ChangeOp& op = Cast<ChangeOp>(node);
2905 OpIndex value = op.input();
2906 bool can_cover = false;
2907 if (const TaggedBitcastOp* value_op =
2909 can_cover = CanCover(node, value) && CanCover(node, value_op->input());
2910 value = value_op->input();
2911 } else {
2912 can_cover = CanCover(node, value);
2913 }
2914 if (can_cover) {
2915 const Operation& value_op = Get(value);
2916 if (const ShiftOp * shift;
2917 (shift = value_op.TryCast<Opmask::kWord64ShiftRightArithmetic>()) ||
2918 (shift = value_op.TryCast<Opmask::kWord64ShiftRightLogical>())) {
2919 if (this->MatchIntegralWord32Constant(shift->right(), 32)) {
2920 if (CanCover(value, shift->left()) &&
2921 TryEmitLoadForLoadWord64AndShiftRight(this, value, kX64Movl)) {
2922 // We just defined and emitted a 32-bit Load for {value} (the upper
2923 // 32 bits only since it was getting shifted by 32 bits to the right
2924 // afterwards); we now define {node} as a rename of {value} without
2925 // needing to do a truncation.
2926 return EmitIdentity(node);
2927 }
2928 Emit(kX64Shr, g.DefineSameAsFirst(node), g.UseRegister(shift->left()),
2929 g.TempImmediate(32));
2930 return;
2931 }
2932 }
2933 }
2934 Emit(kX64Movl, g.DefineAsRegister(node), g.Use(value));
2935}
2936
2937void InstructionSelectorT::VisitFloat32Add(OpIndex node) {
2938 VisitFloatBinop(this, node, kAVXFloat32Add, kSSEFloat32Add);
2939}
2940
2941void InstructionSelectorT::VisitFloat32Sub(OpIndex node) {
2942 VisitFloatBinop(this, node, kAVXFloat32Sub, kSSEFloat32Sub);
2943}
2944
2945void InstructionSelectorT::VisitFloat32Mul(OpIndex node) {
2946 VisitFloatBinop(this, node, kAVXFloat32Mul, kSSEFloat32Mul);
2947}
2948
2949void InstructionSelectorT::VisitFloat32Div(OpIndex node) {
2950 VisitFloatBinop(this, node, kAVXFloat32Div, kSSEFloat32Div);
2951}
2952
2953void InstructionSelectorT::VisitFloat32Abs(OpIndex node) {
2954 const FloatUnaryOp& op = Cast<FloatUnaryOp>(node);
2955 DCHECK_EQ(op.input_count, 1);
2956 VisitFloatUnop(this, node, op.input(), kX64Float32Abs);
2957}
2958
2959void InstructionSelectorT::VisitFloat32Max(OpIndex node) {
2960 VisitRRO(this, node, kSSEFloat32Max);
2961}
2962
2963void InstructionSelectorT::VisitFloat32Min(OpIndex node) {
2964 VisitRRO(this, node, kSSEFloat32Min);
2965}
2966
2967void InstructionSelectorT::VisitFloat64Add(OpIndex node) {
2968 VisitFloatBinop(this, node, kAVXFloat64Add, kSSEFloat64Add);
2969}
2970
2971void InstructionSelectorT::VisitFloat64Sub(OpIndex node) {
2972 VisitFloatBinop(this, node, kAVXFloat64Sub, kSSEFloat64Sub);
2973}
2974
2975void InstructionSelectorT::VisitFloat64Mul(OpIndex node) {
2976 VisitFloatBinop(this, node, kAVXFloat64Mul, kSSEFloat64Mul);
2977}
2978
2979void InstructionSelectorT::VisitFloat64Div(OpIndex node) {
2980 VisitFloatBinop(this, node, kAVXFloat64Div, kSSEFloat64Div);
2981}
2982
2983void InstructionSelectorT::VisitFloat64Mod(OpIndex node) {
2984 const FloatBinopOp& op = Cast<FloatBinopOp>(node);
2985 DCHECK_EQ(op.input_count, 2);
2986 X64OperandGeneratorT g(this);
2987 InstructionOperand temps[] = {g.TempRegister(rax)};
2988 Emit(kSSEFloat64Mod, g.DefineSameAsFirst(node), g.UseRegister(op.left()),
2989 g.UseRegister(op.right()), 1, temps);
2990}
2991
2992void InstructionSelectorT::VisitFloat64Max(OpIndex node) {
2993 VisitRRO(this, node, kSSEFloat64Max);
2994}
2995
2996void InstructionSelectorT::VisitFloat64Min(OpIndex node) {
2997 VisitRRO(this, node, kSSEFloat64Min);
2998}
2999
3000void InstructionSelectorT::VisitFloat64Abs(OpIndex node) {
3001 const FloatUnaryOp& op = Cast<FloatUnaryOp>(node);
3002 DCHECK_EQ(op.input_count, 1);
3003 VisitFloatUnop(this, node, op.input(), kX64Float64Abs);
3004}
3005
3006void InstructionSelectorT::VisitFloat64RoundTiesAway(OpIndex node) {
3007 UNREACHABLE();
3008}
3009
3010void InstructionSelectorT::VisitFloat32Neg(OpIndex node) {
3011 const FloatUnaryOp& op = Cast<FloatUnaryOp>(node);
3012 DCHECK_EQ(op.input_count, 1);
3013 VisitFloatUnop(this, node, op.input(), kX64Float32Neg);
3014}
3015
3016void InstructionSelectorT::VisitFloat64Neg(OpIndex node) {
3017 const FloatUnaryOp& op = Cast<FloatUnaryOp>(node);
3018 DCHECK_EQ(op.input_count, 1);
3019 VisitFloatUnop(this, node, op.input(), kX64Float64Neg);
3020}
3021
3023 InstructionCode opcode) {
3024 const FloatBinopOp& op = Cast<FloatBinopOp>(node);
3025 DCHECK_EQ(op.input_count, 2);
3026 X64OperandGeneratorT g(this);
3027 Emit(opcode, g.DefineAsFixed(node, xmm0), g.UseFixed(op.left(), xmm0),
3028 g.UseFixed(op.right(), xmm1))
3029 ->MarkAsCall();
3030}
3031
3033 InstructionCode opcode) {
3034 const FloatUnaryOp& op = Cast<FloatUnaryOp>(node);
3035 X64OperandGeneratorT g(this);
3036 DCHECK_EQ(op.input_count, 1);
3037 Emit(opcode, g.DefineAsFixed(node, xmm0), g.UseFixed(op.input(), xmm0))
3038 ->MarkAsCall();
3039}
3040
3042
3043void InstructionSelectorT::EmitMoveFPRToParam(InstructionOperand* op,
3044 LinkageLocation location) {}
3045
3047 ZoneVector<PushParameter>* arguments, const CallDescriptor* call_descriptor,
3048 OpIndex node) {
3049 X64OperandGeneratorT g(this);
3050
3051 // Prepare for C function call.
3052 if (call_descriptor->IsCFunctionCall()) {
3053 Emit(kArchPrepareCallCFunction | MiscField::encode(static_cast<int>(
3054 call_descriptor->ParameterCount())),
3055 0, nullptr, 0, nullptr);
3056
3057 // Poke any stack arguments.
3058 for (size_t n = 0; n < arguments->size(); ++n) {
3059 PushParameter input = (*arguments)[n];
3060 if (input.node.valid()) {
3061 int slot = static_cast<int>(n);
3062 InstructionOperand value = g.CanBeImmediate(input.node)
3063 ? g.UseImmediate(input.node)
3064 : g.UseRegister(input.node);
3065 Emit(kX64Poke | MiscField::encode(slot), g.NoOutput(), value);
3066 }
3067 }
3068 } else {
3069 // Push any stack arguments.
3070 int effect_level = GetEffectLevel(node);
3071 int stack_decrement = 0;
3072 for (PushParameter input : base::Reversed(*arguments)) {
3073 stack_decrement += kSystemPointerSize;
3074 // Skip holes in the param array. These represent both extra slots for
3075 // multi-slot values and padding slots for alignment.
3076 if (!input.node.valid()) continue;
3077 InstructionOperand decrement = g.UseImmediate(stack_decrement);
3078 stack_decrement = 0;
3079 if (g.CanBeImmediate(input.node)) {
3080 Emit(kX64Push, g.NoOutput(), decrement, g.UseImmediate(input.node));
3081 } else if (IsSupported(INTEL_ATOM) ||
3082 sequence()->IsFP(GetVirtualRegister(input.node))) {
3083 // TODO(titzer): X64Push cannot handle stack->stack double moves
3084 // because there is no way to encode fixed double slots.
3085 Emit(kX64Push, g.NoOutput(), decrement, g.UseRegister(input.node));
3086 } else if (g.CanBeMemoryOperand(kX64Push, node, input.node,
3087 effect_level)) {
3088 InstructionOperand outputs[1];
3089 InstructionOperand inputs[5];
3090 size_t input_count = 0;
3091 inputs[input_count++] = decrement;
3092 AddressingMode mode = g.GetEffectiveAddressMemoryOperand(
3093 input.node, inputs, &input_count);
3095 Emit(opcode, 0, outputs, input_count, inputs);
3096 } else {
3097 Emit(kX64Push, g.NoOutput(), decrement, g.UseAny(input.node));
3098 }
3099 }
3100 }
3101}
3102
3104 ZoneVector<PushParameter>* results, const CallDescriptor* call_descriptor,
3105 OpIndex node) {
3106 X64OperandGeneratorT g(this);
3107 for (PushParameter output : *results) {
3108 if (!output.location.IsCallerFrameSlot()) continue;
3109 // Skip any alignment holes in nodes.
3110 if (output.node.valid()) {
3111 DCHECK(!call_descriptor->IsCFunctionCall());
3112 if (output.location.GetType() == MachineType::Float32()) {
3113 MarkAsFloat32(output.node);
3114 } else if (output.location.GetType() == MachineType::Float64()) {
3115 MarkAsFloat64(output.node);
3116 } else if (output.location.GetType() == MachineType::Simd128()) {
3117 MarkAsSimd128(output.node);
3118 }
3119 InstructionOperand result = g.DefineAsRegister(output.node);
3120 int offset = call_descriptor->GetOffsetToReturns();
3121 int reverse_slot = -output.location.GetLocation() - offset;
3122 InstructionOperand slot = g.UseImmediate(reverse_slot);
3123 Emit(kX64Peek, 1, &result, 1, &slot);
3124 }
3125 }
3126}
3127
3129
3130namespace {
3131
3132void VisitCompareWithMemoryOperand(InstructionSelectorT* selector,
3133 InstructionCode opcode, OpIndex left,
3134 InstructionOperand right,
3135 FlagsContinuationT* cont) {
3136 DCHECK(selector->IsLoadOrLoadImmutable(left));
3137 X64OperandGeneratorT g(selector);
3138 size_t input_count = 0;
3139 InstructionOperand inputs[6];
3140 AddressingMode addressing_mode =
3141 g.GetEffectiveAddressMemoryOperand(left, inputs, &input_count);
3142 opcode |= AddressingModeField::encode(addressing_mode);
3143 inputs[input_count++] = right;
3144 if (cont->IsSelect()) {
3145 if (opcode == kUnorderedEqual) {
3146 cont->Negate();
3147 inputs[input_count++] = g.UseRegister(cont->true_value());
3148 inputs[input_count++] = g.Use(cont->false_value());
3149 } else {
3150 inputs[input_count++] = g.UseRegister(cont->false_value());
3151 inputs[input_count++] = g.Use(cont->true_value());
3152 }
3153 }
3154
3155 selector->EmitWithContinuation(opcode, 0, nullptr, input_count, inputs, cont);
3156}
3157
3158// Shared routine for multiple compare operations.
3159void VisitCompare(InstructionSelectorT* selector, InstructionCode opcode,
3160 InstructionOperand left, InstructionOperand right,
3161 FlagsContinuationT* cont) {
3162 if (cont->IsSelect()) {
3163 X64OperandGeneratorT g(selector);
3164 InstructionOperand inputs[4] = {left, right};
3165 if (cont->condition() == kUnorderedEqual) {
3166 cont->Negate();
3167 inputs[2] = g.UseRegister(cont->true_value());
3168 inputs[3] = g.Use(cont->false_value());
3169 } else {
3170 inputs[2] = g.UseRegister(cont->false_value());
3171 inputs[3] = g.Use(cont->true_value());
3172 }
3173 selector->EmitWithContinuation(opcode, 0, nullptr, 4, inputs, cont);
3174 return;
3175 }
3176 selector->EmitWithContinuation(opcode, left, right, cont);
3177}
3178
3179// Shared routine for multiple compare operations.
3180void VisitCompare(InstructionSelectorT* selector, InstructionCode opcode,
3181 OpIndex left, OpIndex right, FlagsContinuationT* cont,
3182 bool commutative) {
3183 X64OperandGeneratorT g(selector);
3184 if (commutative && g.CanBeBetterLeftOperand(right)) {
3185 std::swap(left, right);
3186 }
3187 VisitCompare(selector, opcode, g.UseRegister(left), g.Use(right), cont);
3188}
3189
3190MachineType MachineTypeForNarrow(InstructionSelectorT* selector, OpIndex node,
3191 OpIndex hint_node) {
3192 if (selector->IsLoadOrLoadImmutable(hint_node)) {
3193 MachineType hint = selector->load_view(hint_node).loaded_rep();
3194 int64_t constant;
3195 if (selector->MatchSignedIntegralConstant(node, &constant)) {
3196 if (hint == MachineType::Int8()) {
3197 if (constant >= std::numeric_limits<int8_t>::min() &&
3198 constant <= std::numeric_limits<int8_t>::max()) {
3199 return hint;
3200 }
3201 } else if (hint == MachineType::Uint8()) {
3202 if (constant >= std::numeric_limits<uint8_t>::min() &&
3203 constant <= std::numeric_limits<uint8_t>::max()) {
3204 return hint;
3205 }
3206 } else if (hint == MachineType::Int16()) {
3207 if (constant >= std::numeric_limits<int16_t>::min() &&
3208 constant <= std::numeric_limits<int16_t>::max()) {
3209 return hint;
3210 }
3211 } else if (hint == MachineType::Uint16()) {
3212 if (constant >= std::numeric_limits<uint16_t>::min() &&
3213 constant <= std::numeric_limits<uint16_t>::max()) {
3214 return hint;
3215 }
3216 } else if (hint == MachineType::Int32()) {
3217 if (constant >= std::numeric_limits<int32_t>::min() &&
3218 constant <= std::numeric_limits<int32_t>::max()) {
3219 return hint;
3220 }
3221 } else if (hint == MachineType::Uint32()) {
3222 if (constant >= std::numeric_limits<uint32_t>::min() &&
3223 constant <= std::numeric_limits<uint32_t>::max())
3224 return hint;
3225 }
3226 }
3227 }
3228 if (selector->IsLoadOrLoadImmutable(node)) {
3229 return selector->load_view(node).loaded_rep();
3230 }
3231 return MachineType::None();
3232}
3233
3234bool IsIntConstant(InstructionSelectorT* selector, OpIndex node) {
3235 if (auto constant = selector->Get(node).TryCast<ConstantOp>()) {
3236 return constant->kind == ConstantOp::Kind::kWord32 ||
3237 constant->kind == ConstantOp::Kind::kWord64;
3238 }
3239 return false;
3240}
3241bool IsWordAnd(InstructionSelectorT* selector, OpIndex node) {
3242 if (auto binop = selector->Get(node).TryCast<WordBinopOp>()) {
3243 return binop->kind == WordBinopOp::Kind::kBitwiseAnd;
3244 }
3245 return false;
3246}
3247
3248// The result of WordAnd with a positive interger constant in X64 is known to
3249// be sign(zero)-extended. Comparing this result with another positive interger
3250// constant can have narrowed operand.
3251MachineType MachineTypeForNarrowWordAnd(InstructionSelectorT* selector,
3252 OpIndex and_node,
3253 OpIndex constant_node) {
3254 const WordBinopOp& op = selector->Cast<WordBinopOp>(and_node);
3255 DCHECK_EQ(op.input_count, 2);
3256 auto and_left = op.left();
3257 auto and_right = op.right();
3258 auto and_constant_node = IsIntConstant(selector, and_right) ? and_right
3259 : IsIntConstant(selector, and_left) ? and_left
3260 : OpIndex{};
3261
3262 if (and_constant_node.valid()) {
3263 int64_t and_constant, cmp_constant;
3264 selector->MatchSignedIntegralConstant(and_constant_node, &and_constant);
3265 selector->MatchSignedIntegralConstant(constant_node, &cmp_constant);
3266 if (and_constant >= 0 && cmp_constant >= 0) {
3267 int64_t constant =
3268 and_constant > cmp_constant ? and_constant : cmp_constant;
3269 if (constant <= std::numeric_limits<int8_t>::max()) {
3270 return MachineType::Int8();
3271 } else if (constant <= std::numeric_limits<uint8_t>::max()) {
3272 return MachineType::Uint8();
3273 } else if (constant <= std::numeric_limits<int16_t>::max()) {
3274 return MachineType::Int16();
3275 } else if (constant <= std::numeric_limits<uint16_t>::max()) {
3276 return MachineType::Uint16();
3277 } else if (constant <= std::numeric_limits<int32_t>::max()) {
3278 return MachineType::Int32();
3279 } else if (constant <= std::numeric_limits<uint32_t>::max()) {
3280 return MachineType::Uint32();
3281 }
3282 }
3283 }
3284
3285 return MachineType::None();
3286}
3287
3288// Tries to match the size of the given opcode to that of the operands, if
3289// possible.
3290InstructionCode TryNarrowOpcodeSize(InstructionSelectorT* selector,
3291 InstructionCode opcode, OpIndex left,
3292 OpIndex right, FlagsContinuationT* cont) {
3293 MachineType left_type = MachineType::None();
3294 MachineType right_type = MachineType::None();
3295 if (IsWordAnd(selector, left) && IsIntConstant(selector, right)) {
3296 left_type = MachineTypeForNarrowWordAnd(selector, left, right);
3297 right_type = left_type;
3298 } else if (IsWordAnd(selector, right) && IsIntConstant(selector, left)) {
3299 right_type = MachineTypeForNarrowWordAnd(selector, right, left);
3300 left_type = right_type;
3301 } else {
3302 // TODO(epertoso): we can probably get some size information out phi nodes.
3303 // If the load representations don't match, both operands will be
3304 // zero/sign-extended to 32bit.
3305 left_type = MachineTypeForNarrow(selector, left, right);
3306 right_type = MachineTypeForNarrow(selector, right, left);
3307 }
3308 if (left_type == right_type) {
3309 switch (left_type.representation()) {
3312 if (opcode == kX64Test || opcode == kX64Test32) return kX64Test8;
3313 if (opcode == kX64Cmp || opcode == kX64Cmp32) {
3314 if (left_type.semantic() == MachineSemantic::kUint32) {
3315 cont->OverwriteUnsignedIfSigned();
3316 } else {
3317 CHECK_EQ(MachineSemantic::kInt32, left_type.semantic());
3318 }
3319 return kX64Cmp8;
3320 }
3321 break;
3322 }
3323 // Cmp16/Test16 may introduce LCP(Length-Changing-Prefixes) stall, use
3324 // Cmp32/Test32 instead.
3325 case MachineRepresentation::kWord16: // Fall through.
3327 if (opcode == kX64Test) return kX64Test32;
3328 if (opcode == kX64Cmp) {
3329 if (left_type.semantic() == MachineSemantic::kUint32) {
3330 cont->OverwriteUnsignedIfSigned();
3331 } else {
3332 CHECK_EQ(MachineSemantic::kInt32, left_type.semantic());
3333 }
3334 return kX64Cmp32;
3335 }
3336 break;
3337#ifdef V8_COMPRESS_POINTERS
3341 // When pointer compression is enabled the lower 32-bits uniquely
3342 // identify tagged value.
3343 if (opcode == kX64Cmp) return kX64Cmp32;
3344 break;
3345#endif
3346 default:
3347 break;
3348 }
3349 }
3350 return opcode;
3351}
3352
3353/*
3354Remove unnecessary WordAnd
3355For example:
335633: IfFalse(31)
3357517: Int32Constant[65535]
3358518: Word32And(18, 517)
335936: Int32Constant[266]
336037: Int32LessThanOrEqual(36, 518)
336138: Branch[None]
3362
3363If Int32LessThanOrEqual select cmp16, the above Word32And can be removed:
336433: IfFalse(31)
336536: Int32Constant[266]
336637: Int32LessThanOrEqual(36, 18)
336738: Branch[None]
3368*/
3369OpIndex RemoveUnnecessaryWordAnd(InstructionSelectorT* selector,
3370 InstructionCode opcode, OpIndex and_node) {
3371 int64_t mask = 0;
3372
3373 if (opcode == kX64Cmp32 || opcode == kX64Test32) {
3374 mask = std::numeric_limits<uint32_t>::max();
3375 } else if (opcode == kX64Cmp16 || opcode == kX64Test16) {
3376 mask = std::numeric_limits<uint16_t>::max();
3377 } else if (opcode == kX64Cmp8 || opcode == kX64Test8) {
3378 mask = std::numeric_limits<uint8_t>::max();
3379 } else {
3380 return and_node;
3381 }
3382
3383 const WordBinopOp& op = selector->Cast<WordBinopOp>(and_node);
3384 DCHECK_EQ(op.input_count, 2);
3385 auto and_left = op.left();
3386 auto and_right = op.right();
3387 auto and_constant_node = OpIndex{};
3388 auto and_other_node = OpIndex{};
3389 if (IsIntConstant(selector, and_left)) {
3390 and_constant_node = and_left;
3391 and_other_node = and_right;
3392 } else if (IsIntConstant(selector, and_right)) {
3393 and_constant_node = and_right;
3394 and_other_node = and_left;
3395 }
3396
3397 if (and_constant_node.valid()) {
3398 int64_t and_constant;
3399 selector->MatchSignedIntegralConstant(and_constant_node, &and_constant);
3400 if (and_constant == mask) return and_other_node;
3401 }
3402 return and_node;
3403}
3404
3405// Shared routine for multiple word compare operations.
3406void VisitWordCompare(InstructionSelectorT* selector, OpIndex node,
3407 InstructionCode opcode, FlagsContinuationT* cont) {
3408 X64OperandGeneratorT g(selector);
3409 const Operation& op = selector->Get(node);
3410 DCHECK_EQ(op.input_count, 2);
3411 auto left = op.input(0);
3412 auto right = op.input(1);
3413
3414 // The 32-bit comparisons automatically truncate Word64
3415 // values to Word32 range, no need to do that explicitly.
3416 if (opcode == kX64Cmp32 || opcode == kX64Test32) {
3417 if (V<Word64> left64;
3418 selector->MatchTruncateWord64ToWord32(left, &left64)) {
3419 left = left64;
3420 }
3421 if (V<Word64> right64;
3422 selector->MatchTruncateWord64ToWord32(right, &right64)) {
3423 right = right64;
3424 }
3425 }
3426
3427 opcode = TryNarrowOpcodeSize(selector, opcode, left, right, cont);
3428
3429 // If one of the two inputs is an immediate, make sure it's on the right, or
3430 // if one of the two inputs is a memory operand, make sure it's on the left.
3431 int effect_level = selector->GetEffectLevel(node, cont);
3432
3433 if ((!g.CanBeImmediate(right) && g.CanBeImmediate(left)) ||
3434 (g.CanBeMemoryOperand(opcode, node, right, effect_level) &&
3435 !g.CanBeMemoryOperand(opcode, node, left, effect_level))) {
3436 if (!selector->IsCommutative(node)) cont->Commute();
3437 std::swap(left, right);
3438 }
3439
3440 if (IsWordAnd(selector, left)) {
3441 left = RemoveUnnecessaryWordAnd(selector, opcode, left);
3442 }
3443
3444 // Match immediates on right side of comparison.
3445 if (g.CanBeImmediate(right)) {
3446 if (g.CanBeMemoryOperand(opcode, node, left, effect_level)) {
3447 return VisitCompareWithMemoryOperand(selector, opcode, left,
3448 g.UseImmediate(right), cont);
3449 }
3450 return VisitCompare(selector, opcode, g.Use(left), g.UseImmediate(right),
3451 cont);
3452 }
3453
3454 // Match memory operands on left side of comparison.
3455 if (g.CanBeMemoryOperand(opcode, node, left, effect_level)) {
3456 return VisitCompareWithMemoryOperand(selector, opcode, left,
3457 g.UseRegister(right), cont);
3458 }
3459
3460 return VisitCompare(selector, opcode, left, right, cont,
3461 selector->IsCommutative(node));
3462}
3463
3464void VisitWord64EqualImpl(InstructionSelectorT* selector, OpIndex node,
3465 FlagsContinuationT* cont) {
3466 if (selector->CanUseRootsRegister()) {
3467 X64OperandGeneratorT g(selector);
3468 const RootsTable& roots_table = selector->isolate()->roots_table();
3469 RootIndex root_index;
3470 const ComparisonOp& equal =
3471 selector->Get(node).template Cast<ComparisonOp>();
3472 DCHECK_EQ(equal.kind, ComparisonOp::Kind::kEqual);
3473 Handle<HeapObject> object;
3475 selector->MatchHeapConstant(equal.right(), &object)) {
3476 if (roots_table.IsRootHandle(object, &root_index)) {
3477 InstructionCode opcode =
3478 kX64Cmp | AddressingModeField::encode(kMode_Root);
3479 return VisitCompare(
3480 selector, opcode,
3481 g.TempImmediate(
3483 g.UseRegister(equal.left()), cont);
3484 }
3485 }
3486 }
3487 VisitWordCompare(selector, node, kX64Cmp, cont);
3488}
3489
3490bool MatchHeapObjectEqual(InstructionSelectorT* selector, OpIndex node,
3491 OpIndex* left, Handle<HeapObject>* right) {
3492 const ComparisonOp& equal = selector->Cast<ComparisonOp>(node);
3493 DCHECK_EQ(equal.kind, ComparisonOp::Kind::kEqual);
3494 if (selector->MatchHeapConstant(equal.right(), right)) {
3495 *left = equal.left();
3496 return true;
3497 }
3498 return false;
3499}
3500
3501void VisitWord32EqualImpl(InstructionSelectorT* selector, OpIndex node,
3502 FlagsContinuationT* cont) {
3503 if (COMPRESS_POINTERS_BOOL && selector->isolate()) {
3504 X64OperandGeneratorT g(selector);
3505 const RootsTable& roots_table = selector->isolate()->roots_table();
3506 RootIndex root_index;
3507 OpIndex left;
3508 Handle<HeapObject> right;
3509 // HeapConstants and CompressedHeapConstants can be treated the same when
3510 // using them as an input to a 32-bit comparison. Check whether either is
3511 // present.
3512 if (MatchHeapObjectEqual(selector, node, &left, &right)) {
3513 if (roots_table.IsRootHandle(right, &root_index)) {
3514 DCHECK(left.valid());
3515 if (RootsTable::IsReadOnly(root_index) &&
3516 (V8_STATIC_ROOTS_BOOL || !selector->isolate()->bootstrapper())) {
3517 return VisitCompare(
3518 selector, kX64Cmp32, g.UseRegister(left),
3520 root_index, selector->isolate())),
3521 cont);
3522 }
3523 if (selector->CanUseRootsRegister()) {
3524 InstructionCode opcode =
3525 kX64Cmp32 | AddressingModeField::encode(kMode_Root);
3526 return VisitCompare(
3527 selector, opcode,
3528 g.TempImmediate(
3530 root_index)),
3531 g.UseRegister(left), cont);
3532 }
3533 }
3534 }
3535 }
3536 VisitWordCompare(selector, node, kX64Cmp32, cont);
3537}
3538
3539void VisitCompareZero(InstructionSelectorT* selector, OpIndex user,
3540 OpIndex node, InstructionCode opcode,
3541 FlagsContinuationT* cont) {
3542 X64OperandGeneratorT g(selector);
3543 const Operation& op = selector->turboshaft_graph()->Get(node);
3544 if (cont->IsBranch() &&
3545 (cont->condition() == kNotEqual || cont->condition() == kEqual)) {
3546 if (const WordBinopOp* binop = op.TryCast<WordBinopOp>()) {
3547 if (selector->IsOnlyUserOfNodeInSameBlock(user, node)) {
3548 const bool is64 = binop->rep == WordRepresentation::Word64();
3549 switch (binop->kind) {
3550 case WordBinopOp::Kind::kAdd:
3551 return VisitBinop(selector, node, is64 ? kX64Add : kX64Add32, cont);
3552 case WordBinopOp::Kind::kSub:
3553 return VisitBinop(selector, node, is64 ? kX64Sub : kX64Sub32, cont);
3554 case WordBinopOp::Kind::kBitwiseAnd:
3555 return VisitBinop(selector, node, is64 ? kX64And : kX64And32, cont);
3556 case WordBinopOp::Kind::kBitwiseOr:
3557 return VisitBinop(selector, node, is64 ? kX64Or : kX64Or32, cont);
3558 default:
3559 break;
3560 }
3561 }
3562 } else if (const ShiftOp* shift = op.TryCast<ShiftOp>()) {
3563 if (selector->IsOnlyUserOfNodeInSameBlock(user, node)) {
3564 const bool is64 = shift->rep == WordRepresentation::Word64();
3565 switch (shift->kind) {
3566 case ShiftOp::Kind::kShiftLeft:
3567 if (TryVisitWordShift(selector, node, is64 ? 64 : 32,
3568 is64 ? kX64Shl : kX64Shl32, cont)) {
3569 return;
3570 }
3571 break;
3572 case ShiftOp::Kind::kShiftRightLogical:
3573 if (TryVisitWordShift(selector, node, is64 ? 64 : 32,
3574 is64 ? kX64Shr : kX64Shr32, cont)) {
3575 return;
3576 }
3577 break;
3578 default:
3579 break;
3580 }
3581 }
3582 }
3583 }
3584
3585 int effect_level = selector->GetEffectLevel(node, cont);
3586 if (const auto load = op.TryCast<LoadOp>()) {
3587 if (load->loaded_rep == MemoryRepresentation::Int8() ||
3588 load->loaded_rep == MemoryRepresentation::Uint8()) {
3589 if (opcode == kX64Cmp32) {
3590 opcode = kX64Cmp8;
3591 } else if (opcode == kX64Test32) {
3592 opcode = kX64Test8;
3593 }
3594 } else if (load->loaded_rep == MemoryRepresentation::Int16() ||
3595 load->loaded_rep == MemoryRepresentation::Uint16()) {
3596 if (opcode == kX64Cmp32) {
3597 opcode = kX64Cmp16;
3598 } else if (opcode == kX64Test32) {
3599 opcode = kX64Test16;
3600 }
3601 }
3602 }
3603 if (g.CanBeMemoryOperand(opcode, user, node, effect_level)) {
3604 VisitCompareWithMemoryOperand(selector, opcode, node, g.TempImmediate(0),
3605 cont);
3606 } else {
3607 VisitCompare(selector, opcode, g.Use(node), g.TempImmediate(0), cont);
3608 }
3609}
3610
3611// Shared routine for multiple float32 compare operations (inputs commuted).
3612void VisitFloat32Compare(InstructionSelectorT* selector, OpIndex node,
3613 FlagsContinuationT* cont) {
3614 const ComparisonOp& op = selector->Cast<ComparisonOp>(node);
3615 InstructionCode const opcode =
3616 selector->IsSupported(AVX) ? kAVXFloat32Cmp : kSSEFloat32Cmp;
3617 VisitCompare(selector, opcode, op.right(), op.left(), cont, false);
3618}
3619
3620// Shared routine for multiple float64 compare operations (inputs commuted).
3621void VisitFloat64Compare(InstructionSelectorT* selector, OpIndex node,
3622 FlagsContinuationT* cont) {
3623 const ComparisonOp& op = selector->Cast<ComparisonOp>(node);
3624 InstructionCode const opcode =
3625 selector->IsSupported(AVX) ? kAVXFloat64Cmp : kSSEFloat64Cmp;
3626 VisitCompare(selector, opcode, op.right(), op.left(), cont, false);
3627}
3628
3629// Shared routine for Word32/Word64 Atomic Binops
3630void VisitAtomicBinop(InstructionSelectorT* selector, OpIndex node,
3631 ArchOpcode opcode, AtomicWidth width,
3632 MemoryAccessKind access_kind) {
3633 const AtomicRMWOp& atomic_op = selector->Cast<AtomicRMWOp>(node);
3634 X64OperandGeneratorT g(selector);
3635 AddressingMode addressing_mode;
3636 InstructionOperand inputs[] = {
3637 g.UseUniqueRegister(atomic_op.value()),
3638 g.UseUniqueRegister(atomic_op.base()),
3639 g.GetEffectiveIndexOperand(atomic_op.index(), &addressing_mode)};
3640 InstructionOperand outputs[] = {g.DefineAsFixed(node, rax)};
3641 InstructionOperand temps[] = {g.TempRegister()};
3642 InstructionCode code = opcode | AddressingModeField::encode(addressing_mode) |
3644 if (access_kind == MemoryAccessKind::kProtectedByTrapHandler) {
3646 }
3647 selector->Emit(code, arraysize(outputs), outputs, arraysize(inputs), inputs,
3648 arraysize(temps), temps);
3649}
3650
3651// Shared routine for Word32/Word64 Atomic CmpExchg
3652void VisitAtomicCompareExchange(InstructionSelectorT* selector, OpIndex node,
3653 ArchOpcode opcode, AtomicWidth width,
3654 MemoryAccessKind access_kind) {
3655 const AtomicRMWOp& atomic_op = selector->Cast<AtomicRMWOp>(node);
3656 X64OperandGeneratorT g(selector);
3657 AddressingMode addressing_mode;
3658 InstructionOperand inputs[] = {
3659 g.UseFixed(atomic_op.expected().value(), rax),
3660 g.UseUniqueRegister(atomic_op.value()),
3661 g.UseUniqueRegister(atomic_op.base()),
3662 g.GetEffectiveIndexOperand(atomic_op.index(), &addressing_mode)};
3663 InstructionOperand outputs[] = {g.DefineAsFixed(node, rax)};
3664 InstructionCode code = opcode | AddressingModeField::encode(addressing_mode) |
3666 if (access_kind == MemoryAccessKind::kProtectedByTrapHandler) {
3668 }
3669 selector->Emit(code, arraysize(outputs), outputs, arraysize(inputs), inputs);
3670}
3671
3672} // namespace
3673
3674// Shared routine for word comparison against zero.
3676 FlagsContinuation* cont) {
3677 // Try to combine with comparisons against 0 by simply inverting the branch.
3678 ConsumeEqualZero(&user, &value, cont);
3679
3680 if (CanCover(user, value)) {
3681 const Operation& value_op = Get(value);
3682 if (const ComparisonOp* comparison = value_op.TryCast<ComparisonOp>()) {
3683 if (comparison->kind == ComparisonOp::Kind::kEqual) {
3684 switch (comparison->rep.MapTaggedToWord().value()) {
3686 cont->OverwriteAndNegateIfEqual(kEqual);
3687 return VisitWord32EqualImpl(this, value, cont);
3689 cont->OverwriteAndNegateIfEqual(kEqual);
3690 if (this->MatchIntegralZero(comparison->right())) {
3691 // Try to combine the branch with a comparison.
3692 if (CanCover(value, comparison->left())) {
3693 const Operation& left_op = this->Get(comparison->left());
3694 if (left_op.Is<Opmask::kWord64Sub>()) {
3695 return VisitWordCompare(this, comparison->left(), kX64Cmp,
3696 cont);
3697 } else if (left_op.Is<Opmask::kWord64BitwiseAnd>()) {
3698 return VisitWordCompare(this, comparison->left(), kX64Test,
3699 cont);
3700 }
3701 }
3702 return VisitCompareZero(this, value, comparison->left(), kX64Cmp,
3703 cont);
3704 }
3705 return VisitWord64EqualImpl(this, value, cont);
3706 }
3707 case RegisterRepresentation::Float32():
3708 cont->OverwriteAndNegateIfEqual(kUnorderedEqual);
3709 return VisitFloat32Compare(this, value, cont);
3710 case RegisterRepresentation::Float64(): {
3711 bool is_self_compare = comparison->left() == comparison->right();
3712 cont->OverwriteAndNegateIfEqual(is_self_compare ? kIsNotNaN
3713 : kUnorderedEqual);
3714 return VisitFloat64Compare(this, value, cont);
3715 }
3716 default:
3717 break;
3718 }
3719 } else {
3720 switch (comparison->rep.MapTaggedToWord().value()) {
3721 case RegisterRepresentation::Word32(): {
3722 cont->OverwriteAndNegateIfEqual(
3723 GetComparisonFlagCondition(*comparison));
3724 return VisitWordCompare(this, value, kX64Cmp32, cont);
3725 }
3726 case RegisterRepresentation::Word64(): {
3727 cont->OverwriteAndNegateIfEqual(
3728 GetComparisonFlagCondition(*comparison));
3729 return VisitWordCompare(this, value, kX64Cmp, cont);
3730 }
3731 case RegisterRepresentation::Float32():
3732 if (comparison->kind == ComparisonOp::Kind::kSignedLessThan) {
3733 cont->OverwriteAndNegateIfEqual(kUnsignedGreaterThan);
3734 return VisitFloat32Compare(this, value, cont);
3735 } else {
3736 DCHECK_EQ(comparison->kind,
3737 ComparisonOp::Kind::kSignedLessThanOrEqual);
3738 cont->OverwriteAndNegateIfEqual(kUnsignedGreaterThanOrEqual);
3739 return VisitFloat32Compare(this, value, cont);
3740 }
3741 case RegisterRepresentation::Float64():
3742 if (comparison->kind == ComparisonOp::Kind::kSignedLessThan) {
3743 if (MatchZero(comparison->left())) {
3744 const Operation& right = this->Get(comparison->right());
3745 if (right.Is<Opmask::kFloat64Abs>()) {
3746 // This matches the pattern
3747 //
3748 // Float64LessThan(#0.0, Float64Abs(x))
3749 //
3750 // which TurboFan generates for NumberToBoolean in the general
3751 // case, and which evaluates to false if x is 0, -0 or NaN. We
3752 // can compile this to a simple (v)ucomisd using not_equal
3753 // flags condition, which avoids the costly Float64Abs.
3754 cont->OverwriteAndNegateIfEqual(kNotEqual);
3755 InstructionCode const opcode =
3756 IsSupported(AVX) ? kAVXFloat64Cmp : kSSEFloat64Cmp;
3757 return VisitCompare(this, opcode, comparison->left(),
3758 right.Cast<FloatUnaryOp>().input(), cont,
3759 false);
3760 }
3761 }
3762 cont->OverwriteAndNegateIfEqual(kUnsignedGreaterThan);
3763 return VisitFloat64Compare(this, value, cont);
3764 } else {
3765 DCHECK_EQ(comparison->kind,
3766 ComparisonOp::Kind::kSignedLessThanOrEqual);
3767 cont->OverwriteAndNegateIfEqual(kUnsignedGreaterThanOrEqual);
3768 return VisitFloat64Compare(this, value, cont);
3769 }
3770 default:
3771 break;
3772 }
3773 }
3774 } else if (value_op.Is<Opmask::kWord32Sub>()) {
3775 return VisitWordCompare(this, value, kX64Cmp32, cont);
3776 } else if (value_op.Is<Opmask::kWord32BitwiseAnd>()) {
3777 return VisitWordCompare(this, value, kX64Test32, cont);
3778 } else if (const ProjectionOp* projection =
3779 value_op.TryCast<ProjectionOp>()) {
3780 // Check if this is the overflow output projection of an
3781 // OverflowCheckedBinop operation.
3782 if (projection->index == 1u) {
3783 // We cannot combine the OverflowCheckedBinop operation with this branch
3784 // unless the 0th projection (the use of the actual value of the
3785 // operation is either {OpIndex::Invalid()}, which means there's no use
3786 // of the actual value, or was already defined, which means it is
3787 // scheduled *AFTER* this branch).
3788 OpIndex node = projection->input();
3789 if (const OverflowCheckedBinopOp* binop =
3790 this->TryCast<OverflowCheckedBinopOp>(node);
3791 binop && CanDoBranchIfOverflowFusion(node)) {
3792 const bool is64 = binop->rep == WordRepresentation::Word64();
3793 cont->OverwriteAndNegateIfEqual(kOverflow);
3794 switch (binop->kind) {
3795 case OverflowCheckedBinopOp::Kind::kSignedAdd:
3796 return VisitBinop(this, node, is64 ? kX64Add : kX64Add32, cont);
3797 case OverflowCheckedBinopOp::Kind::kSignedSub:
3798 return VisitBinop(this, node, is64 ? kX64Sub : kX64Sub32, cont);
3799 case OverflowCheckedBinopOp::Kind::kSignedMul:
3800 return VisitBinop(this, node, is64 ? kX64Imul : kX64Imul32, cont);
3801 }
3802 UNREACHABLE();
3803 }
3804 }
3805 } else if (value_op.Is<StackPointerGreaterThanOp>()) {
3806 cont->OverwriteAndNegateIfEqual(kStackPointerGreaterThanCondition);
3807 return VisitStackPointerGreaterThan(value, cont);
3808 }
3809 }
3810
3811 // Branch could not be combined with a compare, emit compare against 0.
3812 VisitCompareZero(this, user, value, kX64Cmp32, cont);
3813}
3814
3815void InstructionSelectorT::VisitSwitch(OpIndex node, const SwitchInfo& sw) {
3816 X64OperandGeneratorT g(this);
3817 const SwitchOp& op = Cast<SwitchOp>(node);
3818 DCHECK_EQ(op.input_count, 1);
3819 InstructionOperand value_operand = g.UseRegister(op.input());
3820
3821 // Emit either ArchTableSwitch or ArchBinarySearchSwitch.
3822 if (enable_switch_jump_table_ ==
3823 InstructionSelector::kEnableSwitchJumpTable) {
3824 static const size_t kMaxTableSwitchValueRange = 2 << 16;
3825 size_t table_space_cost = 4 + sw.value_range();
3826 size_t table_time_cost = 3;
3827 size_t lookup_space_cost = 3 + 2 * sw.case_count();
3828 size_t lookup_time_cost = sw.case_count();
3829 if (sw.case_count() > 4 &&
3830 table_space_cost + 3 * table_time_cost <=
3831 lookup_space_cost + 3 * lookup_time_cost &&
3832 sw.min_value() > std::numeric_limits<int32_t>::min() &&
3833 sw.value_range() <= kMaxTableSwitchValueRange) {
3834 InstructionOperand index_operand = g.TempRegister();
3835 if (sw.min_value()) {
3836 // The leal automatically zero extends, so result is a valid 64-bit
3837 // index.
3838 Emit(kX64Lea32 | AddressingModeField::encode(kMode_MRI), index_operand,
3839 value_operand, g.TempImmediate(-sw.min_value()));
3840 } else {
3841 // Zero extend, because we use it as 64-bit index into the jump table.
3842 if (ZeroExtendsWord32ToWord64(op.input())) {
3843 // Input value has already been zero-extended.
3844 index_operand = value_operand;
3845 } else {
3846 Emit(kX64Movl, index_operand, value_operand);
3847 }
3848 }
3849 // Generate a table lookup.
3850 return EmitTableSwitch(sw, index_operand);
3851 }
3852 }
3853
3854 // Generate a tree of conditional jumps.
3855 return EmitBinarySearchSwitch(sw, value_operand);
3856}
3857
3858void InstructionSelectorT::VisitWord32Equal(OpIndex node) {
3859 FlagsContinuation cont = FlagsContinuation::ForSet(kEqual, node);
3860 const ComparisonOp& equal = Cast<ComparisonOp>(node);
3861 DCHECK_EQ(equal.kind, ComparisonOp::Kind::kEqual);
3862 DCHECK(equal.rep == RegisterRepresentation::Word32() ||
3863 equal.rep == RegisterRepresentation::Tagged());
3864 if (MatchIntegralZero(equal.right())) {
3865 return VisitWordCompareZero(node, equal.left(), &cont);
3866 }
3867 VisitWord32EqualImpl(this, node, &cont);
3868}
3869
3870void InstructionSelectorT::VisitInt32LessThan(OpIndex node) {
3871 FlagsContinuation cont = FlagsContinuation::ForSet(kSignedLessThan, node);
3872 VisitWordCompare(this, node, kX64Cmp32, &cont);
3873}
3874
3875void InstructionSelectorT::VisitInt32LessThanOrEqual(OpIndex node) {
3876 FlagsContinuation cont =
3877 FlagsContinuation::ForSet(kSignedLessThanOrEqual, node);
3878 VisitWordCompare(this, node, kX64Cmp32, &cont);
3879}
3880
3881void InstructionSelectorT::VisitUint32LessThan(OpIndex node) {
3882 FlagsContinuation cont = FlagsContinuation::ForSet(kUnsignedLessThan, node);
3883 VisitWordCompare(this, node, kX64Cmp32, &cont);
3884}
3885
3886void InstructionSelectorT::VisitUint32LessThanOrEqual(OpIndex node) {
3887 FlagsContinuation cont =
3888 FlagsContinuation::ForSet(kUnsignedLessThanOrEqual, node);
3889 VisitWordCompare(this, node, kX64Cmp32, &cont);
3890}
3891
3892void InstructionSelectorT::VisitWord64Equal(OpIndex node) {
3893 FlagsContinuation cont = FlagsContinuation::ForSet(kEqual, node);
3894 const ComparisonOp& equal = Cast<ComparisonOp>(node);
3895 DCHECK_EQ(equal.kind, ComparisonOp::Kind::kEqual);
3896 DCHECK(equal.rep == RegisterRepresentation::Word64() ||
3897 equal.rep == RegisterRepresentation::Tagged());
3898 if (MatchIntegralZero(equal.right())) {
3899 if (CanCover(node, equal.left())) {
3900 const Operation& left_op = Get(equal.left());
3901 if (left_op.Is<Opmask::kWord64Sub>()) {
3902 return VisitWordCompare(this, equal.left(), kX64Cmp, &cont);
3903 } else if (left_op.Is<Opmask::kWord64BitwiseAnd>()) {
3904 return VisitWordCompare(this, equal.left(), kX64Test, &cont);
3905 }
3906 }
3907 }
3908 VisitWord64EqualImpl(this, node, &cont);
3909}
3910
3911void InstructionSelectorT::VisitInt32AddWithOverflow(OpIndex node) {
3912 OptionalOpIndex ovf = FindProjection(node, 1);
3913 if (ovf.valid()) {
3914 FlagsContinuation cont = FlagsContinuation::ForSet(kOverflow, ovf.value());
3915 return VisitBinop(this, node, kX64Add32, &cont);
3916 }
3917 FlagsContinuation cont;
3918 VisitBinop(this, node, kX64Add32, &cont);
3919}
3920
3921void InstructionSelectorT::VisitInt32SubWithOverflow(OpIndex node) {
3922 OptionalOpIndex ovf = FindProjection(node, 1);
3923 if (ovf.valid()) {
3924 FlagsContinuation cont = FlagsContinuation::ForSet(kOverflow, ovf.value());
3925 return VisitBinop(this, node, kX64Sub32, &cont);
3926 }
3927 FlagsContinuation cont;
3928 VisitBinop(this, node, kX64Sub32, &cont);
3929}
3930
3931void InstructionSelectorT::VisitInt64LessThan(OpIndex node) {
3932 FlagsContinuation cont = FlagsContinuation::ForSet(kSignedLessThan, node);
3933 VisitWordCompare(this, node, kX64Cmp, &cont);
3934}
3935
3936void InstructionSelectorT::VisitInt64LessThanOrEqual(OpIndex node) {
3937 FlagsContinuation cont =
3938 FlagsContinuation::ForSet(kSignedLessThanOrEqual, node);
3939 VisitWordCompare(this, node, kX64Cmp, &cont);
3940}
3941
3942void InstructionSelectorT::VisitUint64LessThan(OpIndex node) {
3943 FlagsContinuation cont = FlagsContinuation::ForSet(kUnsignedLessThan, node);
3944 VisitWordCompare(this, node, kX64Cmp, &cont);
3945}
3946
3947void InstructionSelectorT::VisitUint64LessThanOrEqual(OpIndex node) {
3948 FlagsContinuation cont =
3949 FlagsContinuation::ForSet(kUnsignedLessThanOrEqual, node);
3950 VisitWordCompare(this, node, kX64Cmp, &cont);
3951}
3952
3953void InstructionSelectorT::VisitFloat32Equal(OpIndex node) {
3954 FlagsContinuation cont = FlagsContinuation::ForSet(kUnorderedEqual, node);
3955 VisitFloat32Compare(this, node, &cont);
3956}
3957
3958void InstructionSelectorT::VisitFloat32LessThan(OpIndex node) {
3959 FlagsContinuation cont =
3960 FlagsContinuation::ForSet(kUnsignedGreaterThan, node);
3961 VisitFloat32Compare(this, node, &cont);
3962}
3963
3964void InstructionSelectorT::VisitFloat32LessThanOrEqual(OpIndex node) {
3965 FlagsContinuation cont =
3966 FlagsContinuation::ForSet(kUnsignedGreaterThanOrEqual, node);
3967 VisitFloat32Compare(this, node, &cont);
3968}
3969
3970void InstructionSelectorT::VisitFloat64Equal(OpIndex node) {
3971 const ComparisonOp& op = Cast<ComparisonOp>(node);
3972 bool is_self_compare = op.left() == op.right();
3973 FlagsContinuation cont = FlagsContinuation::ForSet(
3974 is_self_compare ? kIsNotNaN : kUnorderedEqual, node);
3975 VisitFloat64Compare(this, node, &cont);
3976}
3977
3978void InstructionSelectorT::VisitFloat64LessThan(OpIndex node) {
3979 // Check for the pattern
3980 //
3981 // Float64LessThan(#0.0, Float64Abs(x))
3982 //
3983 // which TurboFan generates for NumberToBoolean in the general case,
3984 // and which evaluates to false if x is 0, -0 or NaN. We can compile
3985 // this to a simple (v)ucomisd using not_equal flags condition, which
3986 // avoids the costly Float64Abs.
3987 const ComparisonOp& cmp = Cast<ComparisonOp>(node);
3988 DCHECK_EQ(cmp.rep, RegisterRepresentation::Float64());
3989 DCHECK_EQ(cmp.kind, ComparisonOp::Kind::kSignedLessThan);
3990 if (MatchZero(cmp.left())) {
3991 if (const FloatUnaryOp* right_op =
3992 TryCast<Opmask::kFloat64Abs>(cmp.right())) {
3993 FlagsContinuation cont = FlagsContinuation::ForSet(kNotEqual, node);
3994 InstructionCode const opcode =
3995 IsSupported(AVX) ? kAVXFloat64Cmp : kSSEFloat64Cmp;
3996 return VisitCompare(this, opcode, cmp.left(), right_op->input(), &cont,
3997 false);
3998 }
3999 }
4000 FlagsContinuation cont =
4001 FlagsContinuation::ForSet(kUnsignedGreaterThan, node);
4002 VisitFloat64Compare(this, node, &cont);
4003}
4004
4005void InstructionSelectorT::VisitFloat64LessThanOrEqual(OpIndex node) {
4006 FlagsContinuation cont =
4007 FlagsContinuation::ForSet(kUnsignedGreaterThanOrEqual, node);
4008 VisitFloat64Compare(this, node, &cont);
4009}
4010
4011void InstructionSelectorT::VisitBitcastWord32PairToFloat64(OpIndex node) {
4012 X64OperandGeneratorT g(this);
4013 const auto& bitcast = Cast<BitcastWord32PairToFloat64Op>(node);
4014 OpIndex hi = bitcast.high_word32();
4015 OpIndex lo = bitcast.low_word32();
4016
4017 // TODO(nicohartmann@): We could try to emit a better sequence here.
4018 InstructionOperand zero = sequence()->AddImmediate(Constant(0.0));
4019 InstructionOperand temp = g.TempDoubleRegister();
4020 Emit(kSSEFloat64InsertHighWord32, temp, zero, g.Use(hi));
4021 Emit(kSSEFloat64InsertLowWord32, g.DefineSameAsFirst(node), temp, g.Use(lo));
4022}
4023
4024void InstructionSelectorT::VisitFloat64SilenceNaN(OpIndex node) {
4025 X64OperandGeneratorT g(this);
4026 const FloatUnaryOp& op = Cast<FloatUnaryOp>(node);
4027 DCHECK_EQ(op.input_count, 1);
4028 Emit(kSSEFloat64SilenceNaN, g.DefineSameAsFirst(node),
4029 g.UseRegister(op.input()));
4030}
4031
4032void InstructionSelectorT::VisitMemoryBarrier(OpIndex node) {
4033 AtomicMemoryOrder order;
4034 order = Cast<MemoryBarrierOp>(node).memory_order;
4035 // x64 is no weaker than release-acquire and only needs to emit an
4036 // instruction for SeqCst memory barriers.
4037 if (order == AtomicMemoryOrder::kSeqCst) {
4038 X64OperandGeneratorT g(this);
4039 Emit(kX64MFence, g.NoOutput());
4040 return;
4041 }
4042 DCHECK_EQ(AtomicMemoryOrder::kAcqRel, order);
4043}
4044
4045void InstructionSelectorT::VisitWord32AtomicLoad(OpIndex node) {
4046 LoadRepresentation load_rep = this->load_view(node).loaded_rep();
4047 DCHECK(IsIntegral(load_rep.representation()) ||
4048 IsAnyTagged(load_rep.representation()) ||
4050 CanBeCompressedPointer(load_rep.representation())));
4051 DCHECK_NE(load_rep.representation(), MachineRepresentation::kWord64);
4052 DCHECK(!load_rep.IsMapWord());
4053 // The memory order is ignored as both acquire and sequentially consistent
4054 // loads can emit MOV.
4055 // https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
4056 VisitLoad(node, node, GetLoadOpcode(load_rep));
4057}
4058
4059void InstructionSelectorT::VisitWord64AtomicLoad(OpIndex node) {
4060 LoadRepresentation load_rep = this->load_view(node).loaded_rep();
4061 DCHECK(!load_rep.IsMapWord());
4062 // The memory order is ignored as both acquire and sequentially consistent
4063 // loads can emit MOV.
4064 // https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
4065 VisitLoad(node, node, GetLoadOpcode(load_rep));
4066}
4067
4068void InstructionSelectorT::VisitWord32AtomicStore(OpIndex node) {
4069 auto store = this->store_view(node);
4070 DCHECK_NE(store.stored_rep().representation(),
4071 MachineRepresentation::kWord64);
4073 CanBeTaggedOrCompressedPointer(store.stored_rep().representation()),
4074 kTaggedSize == 4);
4075 VisitStoreCommon(this, store);
4076}
4077
4078void InstructionSelectorT::VisitWord64AtomicStore(OpIndex node) {
4079 auto store = this->store_view(node);
4081 CanBeTaggedOrCompressedPointer(store.stored_rep().representation()),
4082 kTaggedSize == 8);
4083 VisitStoreCommon(this, store);
4084}
4085
4086void InstructionSelectorT::VisitWord32AtomicExchange(OpIndex node) {
4087 const AtomicRMWOp& atomic_op = Cast<AtomicRMWOp>(node);
4088 ArchOpcode opcode;
4089 if (atomic_op.memory_rep == MemoryRepresentation::Int8()) {
4090 opcode = kAtomicExchangeInt8;
4091 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint8()) {
4092 opcode = kAtomicExchangeUint8;
4093 } else if (atomic_op.memory_rep == MemoryRepresentation::Int16()) {
4094 opcode = kAtomicExchangeInt16;
4095 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint16()) {
4096 opcode = kAtomicExchangeUint16;
4097 } else if (atomic_op.memory_rep == MemoryRepresentation::Int32() ||
4098 atomic_op.memory_rep == MemoryRepresentation::Uint32()) {
4099 opcode = kAtomicExchangeWord32;
4100 } else {
4101 UNREACHABLE();
4102 }
4103 VisitAtomicExchange(this, node, opcode, AtomicWidth::kWord32,
4104 atomic_op.memory_access_kind);
4105}
4106
4107void InstructionSelectorT::VisitWord64AtomicExchange(OpIndex node) {
4108 const AtomicRMWOp& atomic_op = Cast<AtomicRMWOp>(node);
4109 ArchOpcode opcode;
4110 if (atomic_op.memory_rep == MemoryRepresentation::Uint8()) {
4111 opcode = kAtomicExchangeUint8;
4112 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint16()) {
4113 opcode = kAtomicExchangeUint16;
4114 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint32()) {
4115 opcode = kAtomicExchangeWord32;
4116 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint64()) {
4117 opcode = kX64Word64AtomicExchangeUint64;
4118 } else {
4119 UNREACHABLE();
4120 }
4121 VisitAtomicExchange(this, node, opcode, AtomicWidth::kWord64,
4122 atomic_op.memory_access_kind);
4123}
4124
4125void InstructionSelectorT::VisitWord32AtomicCompareExchange(OpIndex node) {
4126 const AtomicRMWOp& atomic_op = Cast<AtomicRMWOp>(node);
4127 ArchOpcode opcode;
4128 if (atomic_op.memory_rep == MemoryRepresentation::Int8()) {
4129 opcode = kAtomicCompareExchangeInt8;
4130 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint8()) {
4131 opcode = kAtomicCompareExchangeUint8;
4132 } else if (atomic_op.memory_rep == MemoryRepresentation::Int16()) {
4133 opcode = kAtomicCompareExchangeInt16;
4134 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint16()) {
4135 opcode = kAtomicCompareExchangeUint16;
4136 } else if (atomic_op.memory_rep == MemoryRepresentation::Int32() ||
4137 atomic_op.memory_rep == MemoryRepresentation::Uint32()) {
4138 opcode = kAtomicCompareExchangeWord32;
4139 } else {
4140 UNREACHABLE();
4141 }
4142 VisitAtomicCompareExchange(this, node, opcode, AtomicWidth::kWord32,
4143 atomic_op.memory_access_kind);
4144}
4145
4146void InstructionSelectorT::VisitWord64AtomicCompareExchange(OpIndex node) {
4147 const AtomicRMWOp& atomic_op = Cast<AtomicRMWOp>(node);
4148 ArchOpcode opcode;
4149 if (atomic_op.memory_rep == MemoryRepresentation::Uint8()) {
4150 opcode = kAtomicCompareExchangeUint8;
4151 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint16()) {
4152 opcode = kAtomicCompareExchangeUint16;
4153 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint32()) {
4154 opcode = kAtomicCompareExchangeWord32;
4155 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint64()) {
4156 opcode = kX64Word64AtomicCompareExchangeUint64;
4157 } else {
4158 UNREACHABLE();
4159 }
4160 VisitAtomicCompareExchange(this, node, opcode, AtomicWidth::kWord64,
4161 atomic_op.memory_access_kind);
4162}
4163
4164void InstructionSelectorT::VisitWord32AtomicBinaryOperation(
4165 OpIndex node, ArchOpcode int8_op, ArchOpcode uint8_op, ArchOpcode int16_op,
4166 ArchOpcode uint16_op, ArchOpcode word32_op) {
4167 const AtomicRMWOp& atomic_op = Cast<AtomicRMWOp>(node);
4168 ArchOpcode opcode;
4169 if (atomic_op.memory_rep == MemoryRepresentation::Int8()) {
4170 opcode = int8_op;
4171 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint8()) {
4172 opcode = uint8_op;
4173 } else if (atomic_op.memory_rep == MemoryRepresentation::Int16()) {
4174 opcode = int16_op;
4175 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint16()) {
4176 opcode = uint16_op;
4177 } else if (atomic_op.memory_rep == MemoryRepresentation::Int32() ||
4178 atomic_op.memory_rep == MemoryRepresentation::Uint32()) {
4179 opcode = word32_op;
4180 } else {
4181 UNREACHABLE();
4182 }
4183 VisitAtomicBinop(this, node, opcode, AtomicWidth::kWord32,
4184 atomic_op.memory_access_kind);
4185}
4186
4187#define VISIT_ATOMIC_BINOP(op) \
4188 void InstructionSelectorT::VisitWord32Atomic##op(OpIndex node) { \
4189 VisitWord32AtomicBinaryOperation( \
4190 node, kAtomic##op##Int8, kAtomic##op##Uint8, kAtomic##op##Int16, \
4191 kAtomic##op##Uint16, kAtomic##op##Word32); \
4192 }
4198#undef VISIT_ATOMIC_BINOP
4199
4200void InstructionSelectorT::VisitWord64AtomicBinaryOperation(
4201 OpIndex node, ArchOpcode uint8_op, ArchOpcode uint16_op,
4202 ArchOpcode uint32_op, ArchOpcode word64_op) {
4203 const AtomicRMWOp& atomic_op = Cast<AtomicRMWOp>(node);
4204 ArchOpcode opcode;
4205 if (atomic_op.memory_rep == MemoryRepresentation::Uint8()) {
4206 opcode = uint8_op;
4207 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint16()) {
4208 opcode = uint16_op;
4209 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint32()) {
4210 opcode = uint32_op;
4211 } else if (atomic_op.memory_rep == MemoryRepresentation::Uint64()) {
4212 opcode = word64_op;
4213 } else {
4214 UNREACHABLE();
4215 }
4216 VisitAtomicBinop(this, node, opcode, AtomicWidth::kWord64,
4217 atomic_op.memory_access_kind);
4218}
4219
4220#define VISIT_ATOMIC_BINOP(op) \
4221 void InstructionSelectorT::VisitWord64Atomic##op(OpIndex node) { \
4222 VisitWord64AtomicBinaryOperation(node, kAtomic##op##Uint8, \
4223 kAtomic##op##Uint16, kAtomic##op##Word32, \
4224 kX64Word64Atomic##op##Uint64); \
4225 }
4231#undef VISIT_ATOMIC_BINOP
4232
4233#ifdef V8_ENABLE_WEBASSEMBLY
4234#define SIMD_BINOP_SSE_AVX_LIST(V) \
4235 V(I64x2ExtMulLowI32x4S) \
4236 V(I64x2ExtMulHighI32x4S) \
4237 V(I64x2ExtMulLowI32x4U) \
4238 V(I64x2ExtMulHighI32x4U) \
4239 V(I32x4DotI16x8S) \
4240 V(I32x8DotI16x16S) \
4241 V(I32x4ExtMulLowI16x8S) \
4242 V(I32x4ExtMulHighI16x8S) \
4243 V(I32x4ExtMulLowI16x8U) \
4244 V(I32x4ExtMulHighI16x8U) \
4245 V(I16x8SConvertI32x4) \
4246 V(I16x8UConvertI32x4) \
4247 V(I16x8ExtMulLowI8x16S) \
4248 V(I16x8ExtMulHighI8x16S) \
4249 V(I16x8ExtMulLowI8x16U) \
4250 V(I16x8ExtMulHighI8x16U) \
4251 V(I16x8Q15MulRSatS) \
4252 V(I16x8RelaxedQ15MulRS) \
4253 V(I8x16SConvertI16x8) \
4254 V(I8x16UConvertI16x8) \
4255 V(I16x16SConvertI32x8) \
4256 V(I16x16UConvertI32x8) \
4257 V(I8x32SConvertI16x16) \
4258 V(I8x32UConvertI16x16) \
4259 V(I64x4ExtMulI32x4S) \
4260 V(I64x4ExtMulI32x4U) \
4261 V(I32x8ExtMulI16x8S) \
4262 V(I32x8ExtMulI16x8U) \
4263 V(I16x16ExtMulI8x16S) \
4264 V(I16x16ExtMulI8x16U)
4265
4266#define SIMD_BINOP_SSE_AVX_LANE_SIZE_VECTOR_LENGTH_LIST(V) \
4267 V(F64x2Add, FAdd, kL64, kV128) \
4268 V(F64x4Add, FAdd, kL64, kV256) \
4269 V(F32x4Add, FAdd, kL32, kV128) \
4270 V(F32x8Add, FAdd, kL32, kV256) \
4271 V(I64x2Add, IAdd, kL64, kV128) \
4272 V(I64x4Add, IAdd, kL64, kV256) \
4273 V(I32x8Add, IAdd, kL32, kV256) \
4274 V(I16x16Add, IAdd, kL16, kV256) \
4275 V(I8x32Add, IAdd, kL8, kV256) \
4276 V(I32x4Add, IAdd, kL32, kV128) \
4277 V(I16x8Add, IAdd, kL16, kV128) \
4278 V(I8x16Add, IAdd, kL8, kV128) \
4279 V(F64x4Sub, FSub, kL64, kV256) \
4280 V(F64x2Sub, FSub, kL64, kV128) \
4281 V(F32x4Sub, FSub, kL32, kV128) \
4282 V(F32x8Sub, FSub, kL32, kV256) \
4283 V(I64x2Sub, ISub, kL64, kV128) \
4284 V(I64x4Sub, ISub, kL64, kV256) \
4285 V(I32x8Sub, ISub, kL32, kV256) \
4286 V(I16x16Sub, ISub, kL16, kV256) \
4287 V(I8x32Sub, ISub, kL8, kV256) \
4288 V(I32x4Sub, ISub, kL32, kV128) \
4289 V(I16x8Sub, ISub, kL16, kV128) \
4290 V(I8x16Sub, ISub, kL8, kV128) \
4291 V(F64x2Mul, FMul, kL64, kV128) \
4292 V(F32x4Mul, FMul, kL32, kV128) \
4293 V(F64x4Mul, FMul, kL64, kV256) \
4294 V(F32x8Mul, FMul, kL32, kV256) \
4295 V(I32x8Mul, IMul, kL32, kV256) \
4296 V(I16x16Mul, IMul, kL16, kV256) \
4297 V(I32x4Mul, IMul, kL32, kV128) \
4298 V(I16x8Mul, IMul, kL16, kV128) \
4299 V(F64x2Div, FDiv, kL64, kV128) \
4300 V(F32x4Div, FDiv, kL32, kV128) \
4301 V(F64x4Div, FDiv, kL64, kV256) \
4302 V(F32x8Div, FDiv, kL32, kV256) \
4303 V(I16x8AddSatS, IAddSatS, kL16, kV128) \
4304 V(I16x16AddSatS, IAddSatS, kL16, kV256) \
4305 V(I8x16AddSatS, IAddSatS, kL8, kV128) \
4306 V(I8x32AddSatS, IAddSatS, kL8, kV256) \
4307 V(I16x8SubSatS, ISubSatS, kL16, kV128) \
4308 V(I16x16SubSatS, ISubSatS, kL16, kV256) \
4309 V(I8x16SubSatS, ISubSatS, kL8, kV128) \
4310 V(I8x32SubSatS, ISubSatS, kL8, kV256) \
4311 V(I16x8AddSatU, IAddSatU, kL16, kV128) \
4312 V(I16x16AddSatU, IAddSatU, kL16, kV256) \
4313 V(I8x16AddSatU, IAddSatU, kL8, kV128) \
4314 V(I8x32AddSatU, IAddSatU, kL8, kV256) \
4315 V(I16x8SubSatU, ISubSatU, kL16, kV128) \
4316 V(I16x16SubSatU, ISubSatU, kL16, kV256) \
4317 V(I8x16SubSatU, ISubSatU, kL8, kV128) \
4318 V(I8x32SubSatU, ISubSatU, kL8, kV256) \
4319 V(F64x2Eq, FEq, kL64, kV128) \
4320 V(F32x4Eq, FEq, kL32, kV128) \
4321 V(F32x8Eq, FEq, kL32, kV256) \
4322 V(F64x4Eq, FEq, kL64, kV256) \
4323 V(I8x32Eq, IEq, kL8, kV256) \
4324 V(I16x16Eq, IEq, kL16, kV256) \
4325 V(I32x8Eq, IEq, kL32, kV256) \
4326 V(I64x4Eq, IEq, kL64, kV256) \
4327 V(I64x2Eq, IEq, kL64, kV128) \
4328 V(I32x4Eq, IEq, kL32, kV128) \
4329 V(I16x8Eq, IEq, kL16, kV128) \
4330 V(I8x16Eq, IEq, kL8, kV128) \
4331 V(F64x2Ne, FNe, kL64, kV128) \
4332 V(F32x4Ne, FNe, kL32, kV128) \
4333 V(F32x8Ne, FNe, kL32, kV256) \
4334 V(F64x4Ne, FNe, kL64, kV256) \
4335 V(I32x4GtS, IGtS, kL32, kV128) \
4336 V(I16x8GtS, IGtS, kL16, kV128) \
4337 V(I8x16GtS, IGtS, kL8, kV128) \
4338 V(I8x32GtS, IGtS, kL8, kV256) \
4339 V(I16x16GtS, IGtS, kL16, kV256) \
4340 V(I32x8GtS, IGtS, kL32, kV256) \
4341 V(I64x4GtS, IGtS, kL64, kV256) \
4342 V(F64x2Lt, FLt, kL64, kV128) \
4343 V(F32x4Lt, FLt, kL32, kV128) \
4344 V(F64x4Lt, FLt, kL64, kV256) \
4345 V(F32x8Lt, FLt, kL32, kV256) \
4346 V(F64x2Le, FLe, kL64, kV128) \
4347 V(F32x4Le, FLe, kL32, kV128) \
4348 V(F64x4Le, FLe, kL64, kV256) \
4349 V(F32x8Le, FLe, kL32, kV256) \
4350 V(I32x4MinS, IMinS, kL32, kV128) \
4351 V(I16x8MinS, IMinS, kL16, kV128) \
4352 V(I8x16MinS, IMinS, kL8, kV128) \
4353 V(I32x4MinU, IMinU, kL32, kV128) \
4354 V(I16x8MinU, IMinU, kL16, kV128) \
4355 V(I8x16MinU, IMinU, kL8, kV128) \
4356 V(I32x4MaxS, IMaxS, kL32, kV128) \
4357 V(I16x8MaxS, IMaxS, kL16, kV128) \
4358 V(I8x16MaxS, IMaxS, kL8, kV128) \
4359 V(I32x4MaxU, IMaxU, kL32, kV128) \
4360 V(I16x8MaxU, IMaxU, kL16, kV128) \
4361 V(I8x16MaxU, IMaxU, kL8, kV128) \
4362 V(I32x8MinS, IMinS, kL32, kV256) \
4363 V(I16x16MinS, IMinS, kL16, kV256) \
4364 V(I8x32MinS, IMinS, kL8, kV256) \
4365 V(I32x8MinU, IMinU, kL32, kV256) \
4366 V(I16x16MinU, IMinU, kL16, kV256) \
4367 V(I8x32MinU, IMinU, kL8, kV256) \
4368 V(I32x8MaxS, IMaxS, kL32, kV256) \
4369 V(I16x16MaxS, IMaxS, kL16, kV256) \
4370 V(I8x32MaxS, IMaxS, kL8, kV256) \
4371 V(I32x8MaxU, IMaxU, kL32, kV256) \
4372 V(I16x16MaxU, IMaxU, kL16, kV256) \
4373 V(I8x32MaxU, IMaxU, kL8, kV256) \
4374 V(I16x8RoundingAverageU, IRoundingAverageU, kL16, kV128) \
4375 V(I16x16RoundingAverageU, IRoundingAverageU, kL16, kV256) \
4376 V(I8x16RoundingAverageU, IRoundingAverageU, kL8, kV128) \
4377 V(I8x32RoundingAverageU, IRoundingAverageU, kL8, kV256) \
4378 V(S128And, SAnd, kL8, kV128) \
4379 V(S256And, SAnd, kL8, kV256) \
4380 V(S128Or, SOr, kL8, kV128) \
4381 V(S256Or, SOr, kL8, kV256) \
4382 V(S128Xor, SXor, kL8, kV128) \
4383 V(S256Xor, SXor, kL8, kV256)
4384
4385#define SIMD_F16x8_BINOP_LIST(V) \
4386 V(F16x8Add, FAdd) \
4387 V(F16x8Sub, FSub) \
4388 V(F16x8Mul, FMul) \
4389 V(F16x8Div, FDiv) \
4390 V(F16x8Min, FMin) \
4391 V(F16x8Max, FMax) \
4392 V(F16x8Eq, FEq) \
4393 V(F16x8Ne, FNe) \
4394 V(F16x8Lt, FLt) \
4395 V(F16x8Le, FLe)
4396
4397#define SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH_LIST(V) \
4398 V(F64x2Min, FMin, kL64, kV128) \
4399 V(F32x4Min, FMin, kL32, kV128) \
4400 V(F64x4Min, FMin, kL64, kV256) \
4401 V(F32x8Min, FMin, kL32, kV256) \
4402 V(F64x2Max, FMax, kL64, kV128) \
4403 V(F32x4Max, FMax, kL32, kV128) \
4404 V(F64x4Max, FMax, kL64, kV256) \
4405 V(F32x8Max, FMax, kL32, kV256) \
4406 V(I64x2Ne, INe, kL64, kV128) \
4407 V(I32x4Ne, INe, kL32, kV128) \
4408 V(I16x8Ne, INe, kL16, kV128) \
4409 V(I8x16Ne, INe, kL8, kV128) \
4410 V(I64x4Ne, INe, kL64, kV256) \
4411 V(I32x8Ne, INe, kL32, kV256) \
4412 V(I16x16Ne, INe, kL16, kV256) \
4413 V(I8x32Ne, INe, kL8, kV256) \
4414 V(I32x4GtU, IGtU, kL32, kV128) \
4415 V(I16x8GtU, IGtU, kL16, kV128) \
4416 V(I8x16GtU, IGtU, kL8, kV128) \
4417 V(I32x8GtU, IGtU, kL32, kV256) \
4418 V(I16x16GtU, IGtU, kL16, kV256) \
4419 V(I8x32GtU, IGtU, kL8, kV256) \
4420 V(I32x4GeS, IGeS, kL32, kV128) \
4421 V(I16x8GeS, IGeS, kL16, kV128) \
4422 V(I8x16GeS, IGeS, kL8, kV128) \
4423 V(I32x8GeS, IGeS, kL32, kV256) \
4424 V(I16x16GeS, IGeS, kL16, kV256) \
4425 V(I8x32GeS, IGeS, kL8, kV256) \
4426 V(I32x4GeU, IGeU, kL32, kV128) \
4427 V(I16x8GeU, IGeU, kL16, kV128) \
4428 V(I8x16GeU, IGeU, kL8, kV128) \
4429 V(I32x8GeU, IGeU, kL32, kV256) \
4430 V(I16x16GeU, IGeU, kL16, kV256) \
4431 V(I8x32GeU, IGeU, kL8, kV256)
4432
4433#define SIMD_UNOP_LIST(V) \
4434 V(F64x2ConvertLowI32x4S) \
4435 V(F64x4ConvertI32x4S) \
4436 V(F32x4SConvertI32x4) \
4437 V(F32x8SConvertI32x8) \
4438 V(F32x4DemoteF64x2Zero) \
4439 V(F32x4DemoteF64x4) \
4440 V(I16x8SConvertF16x8) \
4441 V(I16x8UConvertF16x8) \
4442 V(F16x8SConvertI16x8) \
4443 V(F16x8UConvertI16x8) \
4444 V(F16x8DemoteF32x4Zero) \
4445 V(F32x4PromoteLowF16x8) \
4446 V(I64x2SConvertI32x4Low) \
4447 V(I64x2SConvertI32x4High) \
4448 V(I64x4SConvertI32x4) \
4449 V(I64x2UConvertI32x4Low) \
4450 V(I64x2UConvertI32x4High) \
4451 V(I64x4UConvertI32x4) \
4452 V(I32x4SConvertI16x8Low) \
4453 V(I32x4SConvertI16x8High) \
4454 V(I32x8SConvertI16x8) \
4455 V(I32x4UConvertI16x8Low) \
4456 V(I32x4UConvertI16x8High) \
4457 V(I32x8UConvertI16x8) \
4458 V(I16x8SConvertI8x16Low) \
4459 V(I16x8SConvertI8x16High) \
4460 V(I16x16SConvertI8x16) \
4461 V(I16x8UConvertI8x16Low) \
4462 V(I16x8UConvertI8x16High) \
4463 V(I16x16UConvertI8x16)
4464
4465#define SIMD_UNOP_LANE_SIZE_VECTOR_LENGTH_LIST(V) \
4466 V(F32x4Abs, FAbs, kL32, kV128) \
4467 V(I32x4Abs, IAbs, kL32, kV128) \
4468 V(F16x8Abs, FAbs, kL16, kV128) \
4469 V(I16x8Abs, IAbs, kL16, kV128) \
4470 V(I8x16Abs, IAbs, kL8, kV128) \
4471 V(F32x4Neg, FNeg, kL32, kV128) \
4472 V(I32x4Neg, INeg, kL32, kV128) \
4473 V(F16x8Neg, FNeg, kL16, kV128) \
4474 V(I16x8Neg, INeg, kL16, kV128) \
4475 V(I8x16Neg, INeg, kL8, kV128) \
4476 V(F64x2Sqrt, FSqrt, kL64, kV128) \
4477 V(F32x4Sqrt, FSqrt, kL32, kV128) \
4478 V(F16x8Sqrt, FSqrt, kL16, kV128) \
4479 V(I64x2BitMask, IBitMask, kL64, kV128) \
4480 V(I32x4BitMask, IBitMask, kL32, kV128) \
4481 V(I16x8BitMask, IBitMask, kL16, kV128) \
4482 V(I8x16BitMask, IBitMask, kL8, kV128) \
4483 V(I64x2AllTrue, IAllTrue, kL64, kV128) \
4484 V(I32x4AllTrue, IAllTrue, kL32, kV128) \
4485 V(I16x8AllTrue, IAllTrue, kL16, kV128) \
4486 V(I8x16AllTrue, IAllTrue, kL8, kV128) \
4487 V(S128Not, SNot, kL8, kV128) \
4488 V(F64x4Abs, FAbs, kL64, kV256) \
4489 V(F32x8Abs, FAbs, kL32, kV256) \
4490 V(I32x8Abs, IAbs, kL32, kV256) \
4491 V(I16x16Abs, IAbs, kL16, kV256) \
4492 V(I8x32Abs, IAbs, kL8, kV256) \
4493 V(F64x4Neg, FNeg, kL64, kV256) \
4494 V(F32x8Neg, FNeg, kL32, kV256) \
4495 V(I32x8Neg, INeg, kL32, kV256) \
4496 V(I16x16Neg, INeg, kL16, kV256) \
4497 V(I8x32Neg, INeg, kL8, kV256) \
4498 V(F64x4Sqrt, FSqrt, kL64, kV256) \
4499 V(F32x8Sqrt, FSqrt, kL32, kV256) \
4500 V(S256Not, SNot, kL8, kV256)
4501
4502#define SIMD_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES(V) \
4503 V(I64x2Shl, IShl, kL64, kV128) \
4504 V(I32x4Shl, IShl, kL32, kV128) \
4505 V(I16x8Shl, IShl, kL16, kV128) \
4506 V(I32x4ShrS, IShrS, kL32, kV128) \
4507 V(I16x8ShrS, IShrS, kL16, kV128) \
4508 V(I64x2ShrU, IShrU, kL64, kV128) \
4509 V(I32x4ShrU, IShrU, kL32, kV128) \
4510 V(I16x8ShrU, IShrU, kL16, kV128) \
4511 V(I64x4Shl, IShl, kL64, kV256) \
4512 V(I32x8Shl, IShl, kL32, kV256) \
4513 V(I16x16Shl, IShl, kL16, kV256) \
4514 V(I32x8ShrS, IShrS, kL32, kV256) \
4515 V(I16x16ShrS, IShrS, kL16, kV256) \
4516 V(I64x4ShrU, IShrU, kL64, kV256) \
4517 V(I32x8ShrU, IShrU, kL32, kV256) \
4518 V(I16x16ShrU, IShrU, kL16, kV256)
4519
4520#define SIMD_NARROW_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES(V) \
4521 V(I8x16Shl, IShl, kL8, kV128) \
4522 V(I8x16ShrS, IShrS, kL8, kV128) \
4523 V(I8x16ShrU, IShrU, kL8, kV128)
4524
4525void InstructionSelectorT::VisitS128Const(OpIndex node) {
4526 X64OperandGeneratorT g(this);
4527 static const int kUint32Immediates = kSimd128Size / sizeof(uint32_t);
4528 uint32_t val[kUint32Immediates];
4529 const Simd128ConstantOp& constant = Cast<Simd128ConstantOp>(node);
4530 memcpy(val, constant.value, kSimd128Size);
4531 // If all bytes are zeros or ones, avoid emitting code for generic constants
4532 bool all_zeros = !(val[0] || val[1] || val[2] || val[3]);
4533 bool all_ones = val[0] == UINT32_MAX && val[1] == UINT32_MAX &&
4534 val[2] == UINT32_MAX && val[3] == UINT32_MAX;
4535 InstructionOperand dst = g.DefineAsRegister(node);
4536 if (all_zeros) {
4537 Emit(kX64SZero | VectorLengthField::encode(kV128), dst);
4538 } else if (all_ones) {
4539 Emit(kX64SAllOnes | VectorLengthField::encode(kV128), dst);
4540 } else {
4541 Emit(kX64S128Const, dst, g.UseImmediate(val[0]), g.UseImmediate(val[1]),
4542 g.UseImmediate(val[2]), g.UseImmediate(val[3]));
4543 }
4544}
4545
4546void InstructionSelectorT::VisitS128Zero(OpIndex node) {
4547 X64OperandGeneratorT g(this);
4548 Emit(kX64SZero | VectorLengthField::encode(kV128), g.DefineAsRegister(node));
4549}
4550// Name, LaneSize, VectorLength
4551#define SIMD_INT_TYPES_FOR_SPLAT(V) \
4552 V(I64x2, kL64, kV128) \
4553 V(I32x4, kL32, kV128) \
4554 V(I16x8, kL16, kV128) \
4555 V(I8x16, kL8, kV128) \
4556 V(I64x4, kL64, kV256) \
4557 V(I32x8, kL32, kV256) \
4558 V(I16x16, kL16, kV256) \
4559 V(I8x32, kL8, kV256)
4560
4561// Splat with an optimization for const 0.
4562#define VISIT_INT_SIMD_SPLAT(Type, LaneSize, VectorLength) \
4563 void InstructionSelectorT::Visit##Type##Splat(OpIndex node) { \
4564 X64OperandGeneratorT g(this); \
4565 const Operation& op = Get(node); \
4566 DCHECK_EQ(op.input_count, 1); \
4567 OpIndex input = op.input(0); \
4568 if (g.CanBeImmediate(input) && g.GetImmediateIntegerValue(input) == 0) { \
4569 Emit(kX64SZero | VectorLengthField::encode(VectorLength), \
4570 g.DefineAsRegister(node)); \
4571 } else { \
4572 Emit(kX64ISplat | LaneSizeField::encode(LaneSize) | \
4573 VectorLengthField::encode(VectorLength), \
4574 g.DefineAsRegister(node), g.Use(input)); \
4575 } \
4576 }
4577SIMD_INT_TYPES_FOR_SPLAT(VISIT_INT_SIMD_SPLAT)
4578#undef VISIT_INT_SIMD_SPLAT
4579#undef SIMD_INT_TYPES_FOR_SPLAT
4580
4581void InstructionSelectorT::VisitF64x2Splat(OpIndex node) {
4582 X64OperandGeneratorT g(this);
4583 const Simd128SplatOp& op = Cast<Simd128SplatOp>(node);
4584 DCHECK_EQ(op.input_count, 1);
4585 Emit(kX64FSplat | LaneSizeField::encode(kL64) |
4586 VectorLengthField::encode(kV128),
4587 g.DefineAsRegister(node), g.Use(op.input()));
4588}
4589
4590void InstructionSelectorT::VisitF32x4Splat(OpIndex node) {
4591 X64OperandGeneratorT g(this);
4592 const Simd128SplatOp& op = Cast<Simd128SplatOp>(node);
4593 DCHECK_EQ(op.input_count, 1);
4594 Emit(kX64FSplat | LaneSizeField::encode(kL32) |
4595 VectorLengthField::encode(kV128),
4596 g.DefineAsRegister(node), g.UseRegister(op.input()));
4597}
4598
4599void InstructionSelectorT::VisitF16x8Splat(OpIndex node) {
4600 X64OperandGeneratorT g(this);
4601 const Simd128SplatOp& op = Cast<Simd128SplatOp>(node);
4602 DCHECK_EQ(op.input_count, 1);
4603 Emit(kX64FSplat | LaneSizeField::encode(kL16) |
4604 VectorLengthField::encode(kV128),
4605 g.DefineAsRegister(node), g.UseRegister(op.input()));
4606}
4607
4608void InstructionSelectorT::VisitF64x4Splat(OpIndex node) {
4609#ifdef V8_ENABLE_WASM_SIMD256_REVEC
4610 X64OperandGeneratorT g(this);
4611 const Simd256SplatOp& op = Cast<Simd256SplatOp>(node);
4612 DCHECK_EQ(op.input_count, 1);
4613 Emit(kX64FSplat | LaneSizeField::encode(kL64) |
4614 VectorLengthField::encode(kV256),
4615 g.DefineAsRegister(node), g.UseRegister(op.input()));
4616#else
4617 UNREACHABLE();
4618#endif
4619}
4620
4621void InstructionSelectorT::VisitF32x8Splat(OpIndex node) {
4622#ifdef V8_ENABLE_WASM_SIMD256_REVEC
4623 X64OperandGeneratorT g(this);
4624 const Simd256SplatOp& op = Cast<Simd256SplatOp>(node);
4625 DCHECK_EQ(op.input_count, 1);
4626 Emit(kX64FSplat | LaneSizeField::encode(kL32) |
4627 VectorLengthField::encode(kV256),
4628 g.DefineAsRegister(node), g.UseRegister(op.input()));
4629#else
4630 UNREACHABLE();
4631#endif
4632}
4633
4634#define SIMD_VISIT_EXTRACT_LANE(IF, Type, Sign, LaneSize, VectorLength) \
4635 void InstructionSelectorT::Visit##Type##ExtractLane##Sign(OpIndex node) { \
4636 X64OperandGeneratorT g(this); \
4637 const Simd128ExtractLaneOp& op = Cast<Simd128ExtractLaneOp>(node); \
4638 int32_t lane = op.lane; \
4639 Emit(kX64##IF##ExtractLane##Sign | LaneSizeField::encode(LaneSize) | \
4640 VectorLengthField::encode(VectorLength), \
4641 g.DefineAsRegister(node), g.UseRegister(op.input()), \
4642 g.UseImmediate(lane)); \
4643 }
4644
4645SIMD_VISIT_EXTRACT_LANE(F, F64x2, , kL64, kV128)
4646SIMD_VISIT_EXTRACT_LANE(F, F32x4, , kL32, kV128)
4647SIMD_VISIT_EXTRACT_LANE(F, F16x8, , kL16, kV128)
4648SIMD_VISIT_EXTRACT_LANE(I, I64x2, , kL64, kV128)
4649SIMD_VISIT_EXTRACT_LANE(I, I32x4, , kL32, kV128)
4650SIMD_VISIT_EXTRACT_LANE(I, I16x8, S, kL16, kV128)
4651SIMD_VISIT_EXTRACT_LANE(I, I8x16, S, kL8, kV128)
4652#undef SIMD_VISIT_EXTRACT_LANE
4653
4654void InstructionSelectorT::VisitI16x8ExtractLaneU(OpIndex node) {
4655 X64OperandGeneratorT g(this);
4656 const Simd128ExtractLaneOp& op = Cast<Simd128ExtractLaneOp>(node);
4657 Emit(kX64Pextrw, g.DefineAsRegister(node), g.UseRegister(op.input()),
4658 g.UseImmediate(static_cast<int32_t>(op.lane)));
4659}
4660
4661void InstructionSelectorT::VisitI8x16ExtractLaneU(OpIndex node) {
4662 X64OperandGeneratorT g(this);
4663 const Simd128ExtractLaneOp& op = Cast<Simd128ExtractLaneOp>(node);
4664 Emit(kX64Pextrb, g.DefineAsRegister(node), g.UseRegister(op.input()),
4665 g.UseImmediate(op.lane));
4666}
4667
4668void InstructionSelectorT::VisitF16x8ReplaceLane(OpIndex node) {
4669 X64OperandGeneratorT g(this);
4670 auto& op = Cast<Simd128ReplaceLaneOp>(node);
4671 Emit(kX64FReplaceLane | LaneSizeField::encode(kL16) |
4672 VectorLengthField::encode(kV128),
4673 g.DefineSameAsFirst(node), g.UseRegister(op.into()),
4674 g.UseImmediate(op.lane), g.Use(op.new_lane()));
4675}
4676
4677void InstructionSelectorT::VisitF32x4ReplaceLane(OpIndex node) {
4678 X64OperandGeneratorT g(this);
4679 const Simd128ReplaceLaneOp& op = Cast<Simd128ReplaceLaneOp>(node);
4680 Emit(kX64FReplaceLane | LaneSizeField::encode(kL32) |
4681 VectorLengthField::encode(kV128),
4682 g.DefineSameAsFirst(node), g.UseRegister(op.into()),
4683 g.UseImmediate(op.lane), g.Use(op.new_lane()));
4684}
4685
4686void InstructionSelectorT::VisitF64x2ReplaceLane(OpIndex node) {
4687 X64OperandGeneratorT g(this);
4688 // When no-AVX, define dst == src to save a move.
4689 InstructionOperand dst =
4690 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node);
4691 const Simd128ReplaceLaneOp& op = Cast<Simd128ReplaceLaneOp>(node);
4692 Emit(kX64FReplaceLane | LaneSizeField::encode(kL64) |
4693 VectorLengthField::encode(kV128),
4694 dst, g.UseRegister(op.into()), g.UseImmediate(op.lane),
4695 g.UseRegister(op.new_lane()));
4696}
4697
4698#define VISIT_SIMD_REPLACE_LANE(TYPE, OPCODE) \
4699 void InstructionSelectorT::Visit##TYPE##ReplaceLane(OpIndex node) { \
4700 X64OperandGeneratorT g(this); \
4701 const Simd128ReplaceLaneOp& op = Cast<Simd128ReplaceLaneOp>(node); \
4702 Emit(OPCODE, g.DefineAsRegister(node), g.UseRegister(op.into()), \
4703 g.UseImmediate(op.lane), g.Use(op.new_lane())); \
4704 }
4705
4706#define SIMD_TYPES_FOR_REPLACE_LANE(V) \
4707 V(I64x2, kX64Pinsrq) \
4708 V(I32x4, kX64Pinsrd) \
4709 V(I16x8, kX64Pinsrw) \
4710 V(I8x16, kX64Pinsrb)
4711
4712SIMD_TYPES_FOR_REPLACE_LANE(VISIT_SIMD_REPLACE_LANE)
4713#undef SIMD_TYPES_FOR_REPLACE_LANE
4714#undef VISIT_SIMD_REPLACE_LANE
4715
4716#define VISIT_SIMD_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES( \
4717 Name, Opcode, LaneSize, VectorLength) \
4718 void InstructionSelectorT::Visit##Name(OpIndex node) { \
4719 X64OperandGeneratorT g(this); \
4720 const Operation& op = Get(node); \
4721 DCHECK_EQ(op.input_count, 2); \
4722 InstructionOperand dst = IsSupported(AVX) ? g.DefineAsRegister(node) \
4723 : g.DefineSameAsFirst(node); \
4724 if (g.CanBeImmediate(op.input(1))) { \
4725 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4726 VectorLengthField::encode(VectorLength), \
4727 dst, g.UseRegister(op.input(0)), g.UseImmediate(op.input(1))); \
4728 } else { \
4729 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4730 VectorLengthField::encode(VectorLength), \
4731 dst, g.UseRegister(op.input(0)), g.UseRegister(op.input(1))); \
4732 } \
4733 }
4734SIMD_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES(
4735 VISIT_SIMD_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES)
4736
4737#undef VISIT_SIMD_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES
4738#undef SIMD_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES
4739
4740#define VISIT_SIMD_NARROW_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES( \
4741 Name, Opcode, LaneSize, VectorLength) \
4742 void InstructionSelectorT::Visit##Name(OpIndex node) { \
4743 X64OperandGeneratorT g(this); \
4744 const Operation& op = Get(node); \
4745 DCHECK_EQ(op.input_count, 2); \
4746 InstructionOperand output = \
4747 IsSupported(AVX) ? g.UseRegister(node) : g.DefineSameAsFirst(node); \
4748 if (g.CanBeImmediate(op.input(1))) { \
4749 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4750 VectorLengthField::encode(VectorLength), \
4751 output, g.UseRegister(op.input(0)), g.UseImmediate(op.input(1))); \
4752 } else { \
4753 InstructionOperand temps[] = {g.TempSimd128Register()}; \
4754 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4755 VectorLengthField::encode(VectorLength), \
4756 output, g.UseUniqueRegister(op.input(0)), \
4757 g.UseUniqueRegister(op.input(1)), arraysize(temps), temps); \
4758 } \
4759 }
4760SIMD_NARROW_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES(
4761 VISIT_SIMD_NARROW_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES)
4762#undef VISIT_SIMD_NARROW_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES
4763#undef SIMD_NARROW_SHIFT_LANE_SIZE_VECTOR_LENGTH_OPCODES
4764
4765#define VISIT_SIMD_UNOP(Opcode) \
4766 void InstructionSelectorT::Visit##Opcode(OpIndex node) { \
4767 X64OperandGeneratorT g(this); \
4768 const Operation& op = Get(node); \
4769 DCHECK_EQ(op.input_count, 1); \
4770 Emit(kX64##Opcode, g.DefineAsRegister(node), g.UseRegister(op.input(0))); \
4771 }
4772SIMD_UNOP_LIST(VISIT_SIMD_UNOP)
4773#undef VISIT_SIMD_UNOP
4774#undef SIMD_UNOP_LIST
4775
4776#define VISIT_SIMD_UNOP_LANE_SIZE_VECTOR_LENGTH(Name, Opcode, LaneSize, \
4777 VectorLength) \
4778 void InstructionSelectorT::Visit##Name(OpIndex node) { \
4779 X64OperandGeneratorT g(this); \
4780 const Operation& op = Get(node); \
4781 DCHECK_EQ(op.input_count, 1); \
4782 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4783 VectorLengthField::encode(VectorLength), \
4784 g.DefineAsRegister(node), g.UseRegister(op.input(0))); \
4785 }
4786
4787SIMD_UNOP_LANE_SIZE_VECTOR_LENGTH_LIST(VISIT_SIMD_UNOP_LANE_SIZE_VECTOR_LENGTH)
4788
4789#undef VISIT_SIMD_UNOP_LANE_SIZE_VECTOR_LENGTH
4790#undef SIMD_UNOP_LANE_SIZE_VECTOR_LENGTH_LIST
4791
4792#define VISIT_SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH(Name, Opcode, LaneSize, \
4793 VectorLength) \
4794 void InstructionSelectorT::Visit##Name(OpIndex node) { \
4795 X64OperandGeneratorT g(this); \
4796 const Operation& op = Get(node); \
4797 DCHECK_EQ(op.input_count, 2); \
4798 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4799 VectorLengthField::encode(VectorLength), \
4800 g.DefineSameAsFirst(node), g.UseRegister(op.input(0)), \
4801 g.UseRegister(op.input(1))); \
4802 }
4803
4804SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH_LIST(
4805 VISIT_SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH)
4806
4807#undef VISIT_SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH
4808#undef SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH_LIST
4809
4810#define VISIT_SIMD_BINOP(Opcode) \
4811 void InstructionSelectorT::Visit##Opcode(OpIndex node) { \
4812 X64OperandGeneratorT g(this); \
4813 const Operation& op = Get(node); \
4814 DCHECK_EQ(op.input_count, 2); \
4815 if (IsSupported(AVX)) { \
4816 Emit(kX64##Opcode, g.DefineAsRegister(node), g.UseRegister(op.input(0)), \
4817 g.UseRegister(op.input(1))); \
4818 } else { \
4819 Emit(kX64##Opcode, g.DefineSameAsFirst(node), \
4820 g.UseRegister(op.input(0)), g.UseRegister(op.input(1))); \
4821 } \
4822 }
4823
4824SIMD_BINOP_SSE_AVX_LIST(VISIT_SIMD_BINOP)
4825#undef VISIT_SIMD_BINOP
4826#undef SIMD_BINOP_SSE_AVX_LIST
4827
4828#define VISIT_SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH(Name, Opcode, LaneSize, \
4829 VectorLength) \
4830 void InstructionSelectorT::Visit##Name(OpIndex node) { \
4831 X64OperandGeneratorT g(this); \
4832 const Operation& op = Get(node); \
4833 DCHECK_EQ(op.input_count, 2); \
4834 if (IsSupported(AVX)) { \
4835 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4836 VectorLengthField::encode(VectorLength), \
4837 g.DefineAsRegister(node), g.UseRegister(op.input(0)), \
4838 g.UseRegister(op.input(1))); \
4839 } else { \
4840 Emit(kX64##Opcode | LaneSizeField::encode(LaneSize) | \
4841 VectorLengthField::encode(VectorLength), \
4842 g.DefineSameAsFirst(node), g.UseRegister(op.input(0)), \
4843 g.UseRegister(op.input(1))); \
4844 } \
4845 }
4846
4847SIMD_BINOP_SSE_AVX_LANE_SIZE_VECTOR_LENGTH_LIST(
4848 VISIT_SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH)
4849#undef VISIT_SIMD_BINOP_LANE_SIZE_VECTOR_LENGTH
4850#undef SIMD_BINOP_SSE_AVX_LANE_SIZE_VECTOR_LENGTH_LIST
4851
4852#define VISIT_SIMD_F16x8_BINOP(Name, Opcode) \
4853 void InstructionSelectorT::Visit##Name(OpIndex node) { \
4854 X64OperandGeneratorT g(this); \
4855 const Operation& op = Get(node); \
4856 DCHECK_EQ(op.input_count, 2); \
4857 InstructionOperand temps[] = {g.TempSimd256Register(), \
4858 g.TempSimd256Register()}; \
4859 size_t temp_count = arraysize(temps); \
4860 Emit(kX64##Opcode | LaneSizeField::encode(kL16) | \
4861 VectorLengthField::encode(kV128), \
4862 g.DefineAsRegister(node), g.UseUniqueRegister(op.input(0)), \
4863 g.UseUniqueRegister(op.input(1)), temp_count, temps); \
4864 }
4865
4866SIMD_F16x8_BINOP_LIST(VISIT_SIMD_F16x8_BINOP)
4867#undef VISIT_SIMD_F16x8_BINOP
4868#undef SIMD_F16x8_BINOP_LIST
4869
4870 void InstructionSelectorT::VisitV128AnyTrue(OpIndex node) {
4871 X64OperandGeneratorT g(this);
4872 const Simd128TestOp& op = Cast<Simd128TestOp>(node);
4873 DCHECK_EQ(op.input_count, 1);
4874 Emit(kX64V128AnyTrue, g.DefineAsRegister(node),
4875 g.UseUniqueRegister(op.input()));
4876}
4877
4878namespace {
4879
4880static bool IsV128ZeroConst(InstructionSelectorT* selector, OpIndex node) {
4881 const Operation& op = selector->Get(node);
4882 if (auto constant = op.TryCast<Simd128ConstantOp>()) {
4883 return constant->IsZero();
4884 }
4885 return false;
4886}
4887
4888static bool MatchSimd128Constant(InstructionSelectorT* selector, OpIndex node,
4889 std::array<uint8_t, kSimd128Size>* constant) {
4890 DCHECK_NOT_NULL(constant);
4891 const Operation& op = selector->Get(node);
4892 if (auto c = op.TryCast<Simd128ConstantOp>()) {
4893 std::memcpy(constant, c->value, kSimd128Size);
4894 return true;
4895 }
4896 return false;
4897}
4898
4899} // namespace
4900
4901void InstructionSelectorT::VisitS128Select(OpIndex node) {
4902 X64OperandGeneratorT g(this);
4903 const Simd128TernaryOp& op = Cast<Simd128TernaryOp>(node);
4904 DCHECK_EQ(op.input_count, 3);
4905
4906 InstructionOperand dst =
4907 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node);
4908 if (IsV128ZeroConst(this, op.input(2))) {
4909 // select(cond, input1, 0) -> and(cond, input1)
4910 Emit(kX64SAnd | VectorLengthField::encode(kV128), dst,
4911 g.UseRegister(op.input(0)), g.UseRegister(op.input(1)));
4912 } else if (IsV128ZeroConst(this, op.input(1))) {
4913 // select(cond, 0, input2) -> and(not(cond), input2)
4914 Emit(kX64SAndNot | VectorLengthField::encode(kV128), dst,
4915 g.UseRegister(op.input(0)), g.UseRegister(op.input(2)));
4916 } else {
4917 Emit(kX64SSelect | VectorLengthField::encode(kV128), dst,
4918 g.UseRegister(op.input(0)), g.UseRegister(op.input(1)),
4919 g.UseRegister(op.input(2)));
4920 }
4921}
4922
4923void InstructionSelectorT::VisitS256Select(OpIndex node) {
4924#ifdef V8_ENABLE_WASM_SIMD256_REVEC
4925 X64OperandGeneratorT g(this);
4926 const Simd256TernaryOp& op = Cast<Simd256TernaryOp>(node);
4927 Emit(kX64SSelect | VectorLengthField::encode(kV256), g.DefineAsRegister(node),
4928 g.UseRegister(op.input(0)), g.UseRegister(op.input(1)),
4929 g.UseRegister(op.input(2)));
4930#else
4931 UNREACHABLE();
4932#endif
4933}
4934
4935void InstructionSelectorT::VisitS128AndNot(OpIndex node) {
4936 X64OperandGeneratorT g(this);
4937 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
4938 DCHECK_EQ(op.input_count, 2);
4939 // andnps a b does ~a & b, but we want a & !b, so flip the input.
4940 Emit(kX64SAndNot | VectorLengthField::encode(kV128),
4941 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node),
4942 g.UseRegister(op.right()), g.UseRegister(op.left()));
4943}
4944
4945void InstructionSelectorT::VisitS256AndNot(OpIndex node) {
4946#ifdef V8_ENABLE_WASM_SIMD256_REVEC
4947 X64OperandGeneratorT g(this);
4948 const Simd256BinopOp& op = Cast<Simd256BinopOp>(node);
4949 DCHECK_EQ(op.input_count, 2);
4950 // andnps a b does ~a & b, but we want a & !b, so flip the input.
4951 Emit(kX64SAndNot | VectorLengthField::encode(kV256),
4952 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node),
4953 g.UseRegister(op.right()), g.UseRegister(op.left()));
4954#else
4955 UNREACHABLE();
4956#endif
4957}
4958
4959void InstructionSelectorT::VisitF64x2Abs(OpIndex node) {
4960 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
4961 DCHECK_EQ(op.input_count, 1);
4962 VisitFloatUnop(this, node, op.input(),
4963 kX64FAbs | LaneSizeField::encode(kL64) |
4964 VectorLengthField::encode(kV128));
4965}
4966
4967void InstructionSelectorT::VisitF64x2Neg(OpIndex node) {
4968 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
4969 DCHECK_EQ(op.input_count, 1);
4970 VisitFloatUnop(this, node, op.input(),
4971 kX64FNeg | LaneSizeField::encode(kL64) |
4972 VectorLengthField::encode(kV128));
4973}
4974
4975void InstructionSelectorT::VisitF32x4UConvertI32x4(OpIndex node) {
4976 X64OperandGeneratorT g(this);
4977 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
4978 DCHECK_EQ(op.input_count, 1);
4979 OpIndex value = op.input();
4980
4981 // F32x4SConvertI32x4 is more efficient than F32x4UConvertI32x4 on x64,
4982 // if the u32x4 input can fit into i32x4, we can use F32x4SConvertI32x4
4983 // instead. Input node with I32x4UConvertI16x8Low/I32x4UConvertI16x8High
4984 // opcode is one of this kinds.
4985 bool can_use_sign_convert = false;
4986 if (const Simd128UnaryOp* unop =
4987 this->Get(value).template TryCast<Simd128UnaryOp>()) {
4988 if (unop->kind == Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low ||
4989 unop->kind == Simd128UnaryOp::Kind::kI32x4UConvertI16x8High) {
4990 can_use_sign_convert = true;
4991 }
4992 }
4993
4994 if (can_use_sign_convert) {
4995 Emit(kX64F32x4SConvertI32x4, g.DefineAsRegister(node),
4996 g.UseRegister(op.input()));
4997 } else {
4998 Emit(kX64F32x4UConvertI32x4, g.DefineSameAsFirst(node),
4999 g.UseRegister(op.input()));
5000 }
5001}
5002
5003#define VISIT_SIMD_QFMOP(Opcode) \
5004 void InstructionSelectorT::Visit##Opcode(OpIndex node) { \
5005 X64OperandGeneratorT g(this); \
5006 const Operation& op = Get(node); \
5007 DCHECK_EQ(op.input_count, 3); \
5008 Emit(kX64##Opcode, g.UseRegister(node), g.UseRegister(op.input(0)), \
5009 g.UseRegister(op.input(1)), g.UseRegister(op.input(2))); \
5010 }
5011VISIT_SIMD_QFMOP(F64x2Qfma)
5012VISIT_SIMD_QFMOP(F64x2Qfms)
5013VISIT_SIMD_QFMOP(F32x4Qfma)
5014VISIT_SIMD_QFMOP(F32x4Qfms)
5015
5016#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5017VISIT_SIMD_QFMOP(F64x4Qfma)
5018VISIT_SIMD_QFMOP(F64x4Qfms)
5019VISIT_SIMD_QFMOP(F32x8Qfma)
5020VISIT_SIMD_QFMOP(F32x8Qfms)
5021#endif // V8_ENABLE_WASM_SIMD256_REVEC
5022#undef VISIT_SIMD_QFMOP
5023
5024#define VISIT_SIMD_F16x8_QFMOP(Opcode) \
5025 void InstructionSelectorT::Visit##Opcode(OpIndex node) { \
5026 X64OperandGeneratorT g(this); \
5027 const Operation& op = Get(node); \
5028 DCHECK_EQ(op.input_count, 3); \
5029 InstructionOperand temps[] = {g.TempSimd256Register(), \
5030 g.TempSimd256Register()}; \
5031 Emit(kX64##Opcode, g.UseRegister(node), g.UseUniqueRegister(op.input(0)), \
5032 g.UseUniqueRegister(op.input(1)), g.UseUniqueRegister(op.input(2)), \
5033 arraysize(temps), temps); \
5034 }
5035
5036VISIT_SIMD_F16x8_QFMOP(F16x8Qfma) VISIT_SIMD_F16x8_QFMOP(F16x8Qfms)
5037#undef VISIT_SIMD_F16x8_QFMOP
5038
5039 void InstructionSelectorT::VisitI64x2Neg(OpIndex node) {
5040 X64OperandGeneratorT g(this);
5041 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5042 DCHECK_EQ(op.input_count, 1);
5043 // If AVX unsupported, make sure dst != src to avoid a move.
5044 InstructionOperand operand0 = IsSupported(AVX)
5045 ? g.UseRegister(op.input())
5046 : g.UseUniqueRegister(op.input());
5047 Emit(
5048 kX64INeg | LaneSizeField::encode(kL64) | VectorLengthField::encode(kV128),
5049 g.DefineAsRegister(node), operand0);
5050}
5051
5052void InstructionSelectorT::VisitI64x2ShrS(OpIndex node) {
5053 X64OperandGeneratorT g(this);
5054 const Simd128ShiftOp& op = Cast<Simd128ShiftOp>(node);
5055 DCHECK_EQ(op.input_count, 2);
5056 InstructionOperand dst =
5057 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node);
5058
5059 if (g.CanBeImmediate(op.shift())) {
5060 Emit(kX64IShrS | LaneSizeField::encode(kL64) |
5061 VectorLengthField::encode(kV128),
5062 dst, g.UseRegister(op.input()), g.UseImmediate(op.shift()));
5063 } else {
5064 InstructionOperand temps[] = {g.TempSimd128Register()};
5065 Emit(kX64IShrS | LaneSizeField::encode(kL64) |
5066 VectorLengthField::encode(kV128),
5067 dst, g.UseUniqueRegister(op.input()), g.UseRegister(op.shift()),
5068 arraysize(temps), temps);
5069 }
5070}
5071
5072void InstructionSelectorT::VisitI64x2Mul(OpIndex node) {
5073 X64OperandGeneratorT g(this);
5074 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
5075 DCHECK_EQ(op.input_count, 2);
5076 InstructionOperand temps[] = {g.TempSimd128Register()};
5077 Emit(
5078 kX64IMul | LaneSizeField::encode(kL64) | VectorLengthField::encode(kV128),
5079 g.DefineAsRegister(node), g.UseUniqueRegister(op.left()),
5080 g.UseUniqueRegister(op.right()), arraysize(temps), temps);
5081}
5082
5083void InstructionSelectorT::VisitI64x4Mul(OpIndex node) {
5084#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5085 X64OperandGeneratorT g(this);
5086 const Simd256BinopOp& op = Cast<Simd256BinopOp>(node);
5087 DCHECK_EQ(op.input_count, 2);
5088 InstructionOperand temps[] = {g.TempSimd256Register()};
5089 Emit(
5090 kX64IMul | LaneSizeField::encode(kL64) | VectorLengthField::encode(kV256),
5091 g.DefineAsRegister(node), g.UseUniqueRegister(op.left()),
5092 g.UseUniqueRegister(op.right()), arraysize(temps), temps);
5093#else
5094 UNREACHABLE();
5095#endif
5096}
5097
5098void InstructionSelectorT::VisitI32x4SConvertF32x4(OpIndex node) {
5099 X64OperandGeneratorT g(this);
5100 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5101 DCHECK_EQ(op.input_count, 1);
5102 Emit(kX64I32x4SConvertF32x4,
5103 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node),
5104 g.UseRegister(op.input()));
5105}
5106
5107void InstructionSelectorT::VisitI32x8SConvertF32x8(OpIndex node) {
5108#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5109 X64OperandGeneratorT g(this);
5110 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5111 DCHECK_EQ(op.input_count, 1);
5112 Emit(kX64I32x8SConvertF32x8, g.DefineAsRegister(node),
5113 g.UseRegister(op.input()));
5114#else
5115 UNREACHABLE();
5116#endif
5117}
5118
5119void InstructionSelectorT::VisitI32x4UConvertF32x4(OpIndex node) {
5120 X64OperandGeneratorT g(this);
5121 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5122 DCHECK_EQ(op.input_count, 1);
5123 InstructionOperand temps[] = {g.TempSimd128Register(),
5124 g.TempSimd128Register()};
5125 Emit(kX64I32x4UConvertF32x4, g.DefineSameAsFirst(node),
5126 g.UseRegister(op.input()), arraysize(temps), temps);
5127}
5128
5129void InstructionSelectorT::VisitI32x8UConvertF32x8(OpIndex node) {
5130#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5131 X64OperandGeneratorT g(this);
5132 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5133 DCHECK_EQ(op.input_count, 1);
5134 InstructionOperand temps[] = {g.TempSimd256Register(),
5135 g.TempSimd256Register()};
5136 Emit(kX64I32x8UConvertF32x8, g.DefineSameAsFirst(node),
5137 g.UseRegister(op.input()), arraysize(temps), temps);
5138#else
5139 UNREACHABLE();
5140#endif
5141}
5142
5143#if V8_ENABLE_WASM_SIMD256_REVEC
5144void InstructionSelectorT::VisitF32x8UConvertI32x8(OpIndex node) {
5145 X64OperandGeneratorT g(this);
5146 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5147 DCHECK_EQ(op.input_count, 1);
5148
5149 OpIndex value = op.input();
5150
5151 // F32x8SConvertI32x8 is more efficient than F32x8UConvertI32x8 on x64.
5152 bool can_use_sign_convert = false;
5153 if (Is<Opmask::kSimd256I32x8UConvertI16x8>(value)) {
5154 can_use_sign_convert = true;
5155 }
5156
5157 if (can_use_sign_convert) {
5158 Emit(kX64F32x8SConvertI32x8, g.DefineAsRegister(node),
5159 g.UseRegister(op.input()));
5160 } else {
5161 Emit(kX64F32x8UConvertI32x8, g.DefineSameAsFirst(node),
5162 g.UseRegister(op.input()));
5163 }
5164}
5165
5166void InstructionSelectorT::VisitExtractF128(OpIndex node) {
5167 X64OperandGeneratorT g(this);
5168 const Simd256Extract128LaneOp& op = Cast<Simd256Extract128LaneOp>(node);
5169 if (op.lane == 0) {
5170 EmitIdentity(node);
5171 } else {
5172 Emit(kX64ExtractF128, g.DefineAsRegister(node), g.UseRegister(op.input()),
5173 g.UseImmediate(op.lane));
5174 }
5175}
5176
5177void InstructionSelectorT::VisitI8x32Shuffle(OpIndex node) { UNREACHABLE(); }
5178#endif // V8_ENABLE_WASM_SIMD256_REVEC
5179#endif
5180
5181void InstructionSelectorT::VisitInt32AbsWithOverflow(OpIndex node) {
5182 UNREACHABLE();
5183}
5184
5185void InstructionSelectorT::VisitInt64AbsWithOverflow(OpIndex node) {
5186 UNREACHABLE();
5187}
5188
5189#if V8_ENABLE_WEBASSEMBLY
5190namespace {
5191
5192// Returns true if shuffle can be decomposed into two 16x4 half shuffles
5193// followed by a 16x8 blend.
5194// E.g. [3 2 1 0 15 14 13 12].
5195bool TryMatch16x8HalfShuffle(uint8_t* shuffle16x8, uint8_t* blend_mask) {
5196 *blend_mask = 0;
5197 for (int i = 0; i < 8; i++) {
5198 if ((shuffle16x8[i] & 0x4) != (i & 0x4)) return false;
5199 *blend_mask |= (shuffle16x8[i] > 7 ? 1 : 0) << i;
5200 }
5201 return true;
5202}
5203
5204bool TryMatchShufps(const uint8_t* shuffle32x4) {
5205 DCHECK_GT(8, shuffle32x4[2]);
5206 DCHECK_GT(8, shuffle32x4[3]);
5207 // shufps can be used if the first 2 indices select the first input [0-3], and
5208 // the other 2 indices select the second input [4-7].
5209 return shuffle32x4[0] < 4 && shuffle32x4[1] < 4 && shuffle32x4[2] > 3 &&
5210 shuffle32x4[3] > 3;
5211}
5212
5213static bool TryMatchOneInputIsZeros(InstructionSelectorT* selector,
5214 TurboshaftAdapter::SimdShuffleView& view,
5215 uint8_t* shuffle, bool* needs_swap) {
5216 *needs_swap = false;
5217 bool input0_is_zero = IsV128ZeroConst(selector, view.input(0));
5218 bool input1_is_zero = IsV128ZeroConst(selector, view.input(1));
5219 if (!input0_is_zero && !input1_is_zero) {
5220 return false;
5221 }
5222
5223 if (input0_is_zero) {
5224 *needs_swap = true;
5225 }
5226 return true;
5227}
5228
5229} // namespace
5230
5231void InstructionSelectorT::VisitI8x16Shuffle(OpIndex node) {
5232 uint8_t shuffle[kSimd128Size];
5233 bool is_swizzle;
5234 auto view = this->simd_shuffle_view(node);
5235 CanonicalizeShuffle(view, shuffle, &is_swizzle);
5236
5237 int imm_count = 0;
5238 static const int kMaxImms = 6;
5239 uint32_t imms[kMaxImms];
5240 int temp_count = 0;
5241 static const int kMaxTemps = 2;
5242 InstructionOperand temps[kMaxTemps];
5243
5244 X64OperandGeneratorT g(this);
5245 // Swizzles don't generally need DefineSameAsFirst to avoid a move.
5246 bool no_same_as_first = is_swizzle;
5247 // We generally need UseRegister for input0, Use for input1.
5248 // TODO(v8:9198): We don't have 16-byte alignment for SIMD operands yet, but
5249 // we retain this logic (continue setting these in the various shuffle match
5250 // clauses), but ignore it when selecting registers or slots.
5251 bool src0_needs_reg = true;
5252 bool src1_needs_reg = false;
5253 ArchOpcode opcode = kX64I8x16Shuffle; // general shuffle is the default
5254
5255 uint8_t offset;
5256 uint8_t shuffle32x4[4];
5257 uint8_t shuffle16x8[8];
5258 int index;
5259 const wasm::ShuffleEntry<kSimd128Size>* arch_shuffle;
5260 bool needs_swap;
5261 if (wasm::SimdShuffle::TryMatchConcat(shuffle, &offset)) {
5262 if (wasm::SimdShuffle::TryMatch32x4Rotate(shuffle, shuffle32x4,
5263 is_swizzle)) {
5264 uint8_t shuffle_mask = wasm::SimdShuffle::PackShuffle4(shuffle32x4);
5265 opcode = kX64S32x4Rotate;
5266 imms[imm_count++] = shuffle_mask;
5267 } else {
5268 // Swap inputs from the normal order for (v)palignr.
5269 SwapShuffleInputs(view);
5270 is_swizzle = false; // It's simpler to just handle the general case.
5271 no_same_as_first = CpuFeatures::IsSupported(AVX);
5272 // TODO(v8:9608): also see v8:9083
5273 src1_needs_reg = true;
5274 opcode = kX64S8x16Alignr;
5275 // palignr takes a single imm8 offset.
5276 imms[imm_count++] = offset;
5277 }
5278 } else if (wasm::SimdShuffle::TryMatchArchShuffle(shuffle, is_swizzle,
5279 &arch_shuffle)) {
5280 opcode = arch_shuffle->opcode;
5281 src0_needs_reg = arch_shuffle->src0_needs_reg;
5282 // SSE can't take advantage of both operands in registers and needs
5283 // same-as-first.
5284 src1_needs_reg = arch_shuffle->src1_needs_reg;
5285 no_same_as_first =
5286 IsSupported(AVX) && arch_shuffle->no_same_as_first_if_avx;
5287 } else if (wasm::SimdShuffle::TryMatch32x4Shuffle(shuffle, shuffle32x4)) {
5288 uint8_t shuffle_mask = wasm::SimdShuffle::PackShuffle4(shuffle32x4);
5289 if (is_swizzle) {
5290 if (wasm::SimdShuffle::TryMatchIdentity(shuffle)) {
5291 // Bypass normal shuffle code generation in this case.
5292 OpIndex input = view.input(0);
5293 // EmitIdentity
5294 MarkAsUsed(input);
5295 MarkAsDefined(node);
5296 SetRename(node, input);
5297 return;
5298 } else {
5299 // pshufd takes a single imm8 shuffle mask.
5300 opcode = kX64S32x4Swizzle;
5301 no_same_as_first = true;
5302 // TODO(v8:9083): This doesn't strictly require a register, forcing the
5303 // swizzles to always use registers until generation of incorrect memory
5304 // operands can be fixed.
5305 src0_needs_reg = true;
5306 imms[imm_count++] = shuffle_mask;
5307 }
5308 } else {
5309 // 2 operand shuffle
5310 // A blend is more efficient than a general 32x4 shuffle; try it first.
5311 if (wasm::SimdShuffle::TryMatchBlend(shuffle)) {
5312 opcode = kX64S16x8Blend;
5313 uint8_t blend_mask = wasm::SimdShuffle::PackBlend4(shuffle32x4);
5314 imms[imm_count++] = blend_mask;
5315 no_same_as_first = CpuFeatures::IsSupported(AVX);
5316 } else if (TryMatchShufps(shuffle32x4)) {
5317 opcode = kX64Shufps;
5318 uint8_t mask = wasm::SimdShuffle::PackShuffle4(shuffle32x4);
5319 imms[imm_count++] = mask;
5320 src1_needs_reg = true;
5321 no_same_as_first = IsSupported(AVX);
5322 } else {
5323 opcode = kX64S32x4Shuffle;
5324 no_same_as_first = true;
5325 // TODO(v8:9083): src0 and src1 is used by pshufd in codegen, which
5326 // requires memory to be 16-byte aligned, since we cannot guarantee that
5327 // yet, force using a register here.
5328 src0_needs_reg = true;
5329 src1_needs_reg = true;
5330 imms[imm_count++] = shuffle_mask;
5331 uint8_t blend_mask = wasm::SimdShuffle::PackBlend4(shuffle32x4);
5332 imms[imm_count++] = blend_mask;
5333 }
5334 }
5335 } else if (wasm::SimdShuffle::TryMatch16x8Shuffle(shuffle, shuffle16x8)) {
5336 uint8_t blend_mask;
5337 if (wasm::SimdShuffle::TryMatchBlend(shuffle)) {
5338 opcode = kX64S16x8Blend;
5339 blend_mask = wasm::SimdShuffle::PackBlend8(shuffle16x8);
5340 imms[imm_count++] = blend_mask;
5341 no_same_as_first = CpuFeatures::IsSupported(AVX);
5342 } else if (wasm::SimdShuffle::TryMatchSplat<8>(shuffle, &index)) {
5343 opcode = kX64S16x8Dup;
5344 src0_needs_reg = false;
5345 imms[imm_count++] = index;
5346 } else if (TryMatch16x8HalfShuffle(shuffle16x8, &blend_mask)) {
5347 opcode = is_swizzle ? kX64S16x8HalfShuffle1 : kX64S16x8HalfShuffle2;
5348 // Half-shuffles don't need DefineSameAsFirst or UseRegister(src0).
5349 no_same_as_first = true;
5350 src0_needs_reg = false;
5351 uint8_t mask_lo = wasm::SimdShuffle::PackShuffle4(shuffle16x8);
5352 uint8_t mask_hi = wasm::SimdShuffle::PackShuffle4(shuffle16x8 + 4);
5353 imms[imm_count++] = mask_lo;
5354 imms[imm_count++] = mask_hi;
5355 if (!is_swizzle) imms[imm_count++] = blend_mask;
5356 }
5357 } else if (wasm::SimdShuffle::TryMatchSplat<16>(shuffle, &index)) {
5358 opcode = kX64S8x16Dup;
5359 no_same_as_first = false;
5360 src0_needs_reg = true;
5361 imms[imm_count++] = index;
5362 } else if (TryMatchOneInputIsZeros(this, view, shuffle, &needs_swap)) {
5363 is_swizzle = true;
5364 // Swap zeros to input1
5365 if (needs_swap) {
5366 SwapShuffleInputs(view);
5367 for (int i = 0; i < kSimd128Size; ++i) {
5368 shuffle[i] ^= kSimd128Size;
5369 }
5370 }
5371 if (wasm::SimdShuffle::TryMatchByteToDwordZeroExtend(shuffle)) {
5372 opcode = kX64I32X4ShiftZeroExtendI8x16;
5373 no_same_as_first = true;
5374 src0_needs_reg = true;
5375 imms[imm_count++] = shuffle[0];
5376 } else {
5377 // If the most significant bit (bit 7) of each byte of the shuffle control
5378 // mask is set, then constant zero is written in the result byte. Input1
5379 // is zeros now, we can avoid using input1 by setting bit 7 of shuffle[i]
5380 // to 1.
5381 for (int i = 0; i < kSimd128Size; ++i) {
5382 if (shuffle[i] >= kSimd128Size) {
5383 shuffle[i] = 0x80;
5384 }
5385 }
5386 }
5387 }
5388 if (opcode == kX64I8x16Shuffle) {
5389 // Use same-as-first for general swizzle, but not shuffle.
5390 no_same_as_first = !is_swizzle;
5391 src0_needs_reg = !no_same_as_first;
5392 imms[imm_count++] = wasm::SimdShuffle::Pack4Lanes(shuffle);
5393 imms[imm_count++] = wasm::SimdShuffle::Pack4Lanes(shuffle + 4);
5394 imms[imm_count++] = wasm::SimdShuffle::Pack4Lanes(shuffle + 8);
5395 imms[imm_count++] = wasm::SimdShuffle::Pack4Lanes(shuffle + 12);
5396 temps[temp_count++] = g.TempSimd128Register();
5397 }
5398
5399 // Use DefineAsRegister(node) and Use(src0) if we can without forcing an extra
5400 // move instruction in the CodeGenerator.
5401 OpIndex input0 = view.input(0);
5402 InstructionOperand dst =
5403 no_same_as_first ? g.DefineAsRegister(view) : g.DefineSameAsFirst(view);
5404 // TODO(v8:9198): Use src0_needs_reg when we have memory alignment for SIMD.
5405 // We only need a unique register for input0 if we use temp registers.
5406 InstructionOperand src0 =
5407 temp_count ? g.UseUniqueRegister(input0) : g.UseRegister(input0);
5408 USE(src0_needs_reg);
5409
5410 int input_count = 0;
5411 InstructionOperand inputs[2 + kMaxImms + kMaxTemps];
5412 inputs[input_count++] = src0;
5413 if (!is_swizzle) {
5414 OpIndex input1 = view.input(1);
5415 // TODO(v8:9198): Use src1_needs_reg when we have memory alignment for SIMD.
5416 // We only need a unique register for input1 if we use temp registers.
5417 inputs[input_count++] =
5418 temp_count ? g.UseUniqueRegister(input1) : g.UseRegister(input1);
5419 USE(src1_needs_reg);
5420 }
5421 for (int i = 0; i < imm_count; ++i) {
5422 inputs[input_count++] = g.UseImmediate(imms[i]);
5423 }
5424 Emit(opcode, 1, &dst, input_count, inputs, temp_count, temps);
5425}
5426
5427void InstructionSelectorT::VisitI8x16Swizzle(OpIndex node) {
5428 InstructionCode opcode = kX64I8x16Swizzle;
5429 const Simd128BinopOp& binop = Cast<Simd128BinopOp>(node);
5430 DCHECK_EQ(binop.input_count, 2);
5431 DCHECK(binop.kind == any_of(Simd128BinopOp::Kind::kI8x16Swizzle,
5432 Simd128BinopOp::Kind::kI8x16RelaxedSwizzle));
5433 bool relaxed = binop.kind == Simd128BinopOp::Kind::kI8x16RelaxedSwizzle;
5434 OpIndex left = binop.left();
5435 OpIndex right = binop.right();
5436
5437 if (relaxed) {
5438 opcode |= MiscField::encode(true);
5439 } else {
5440 std::array<uint8_t, kSimd128Size> imms;
5441 if (MatchSimd128Constant(this, right, &imms)) {
5442 // If the indices vector is a const, check if they are in range, or if the
5443 // top bit is set, then we can avoid the paddusb in the codegen and simply
5444 // emit a pshufb.
5445 opcode |=
5446 MiscField::encode(wasm::SimdSwizzle::AllInRangeOrTopBitSet(imms));
5447 }
5448 }
5449
5450 X64OperandGeneratorT g(this);
5451 Emit(opcode,
5452 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node),
5453 g.UseRegister(left), g.UseRegister(right));
5454}
5455
5456namespace {
5457void VisitRelaxedLaneSelect(InstructionSelectorT* selector, OpIndex node,
5458 InstructionCode code) {
5459 X64OperandGeneratorT g(selector);
5460 const Operation& op = selector->Get(node);
5461 DCHECK_EQ(op.input_count, 3);
5462 // pblendvb/blendvps/blendvpd copies src2 when mask is set, opposite from Wasm
5463 // semantics. Node's inputs are: mask, lhs, rhs (determined in
5464 // wasm-compiler.cc).
5465 if (selector->IsSupported(AVX)) {
5466 selector->Emit(code, g.DefineAsRegister(node), g.UseRegister(op.input(2)),
5467 g.UseRegister(op.input(1)), g.UseRegister(op.input(0)));
5468 } else {
5469 // SSE4.1 pblendvb/blendvps/blendvpd requires xmm0 to hold the mask as an
5470 // implicit operand.
5471 selector->Emit(code, g.DefineSameAsFirst(node), g.UseRegister(op.input(2)),
5472 g.UseRegister(op.input(1)), g.UseFixed(op.input(0), xmm0));
5473 }
5474}
5475} // namespace
5476
5477void InstructionSelectorT::VisitI8x16RelaxedLaneSelect(OpIndex node) {
5478 VisitRelaxedLaneSelect(this, node,
5479 kX64Pblendvb | VectorLengthField::encode(kV128));
5480}
5481void InstructionSelectorT::VisitI16x8RelaxedLaneSelect(OpIndex node) {
5482 VisitRelaxedLaneSelect(this, node,
5483 kX64Pblendvb | VectorLengthField::encode(kV128));
5484}
5485void InstructionSelectorT::VisitI32x4RelaxedLaneSelect(OpIndex node) {
5486 VisitRelaxedLaneSelect(this, node,
5487 kX64Blendvps | VectorLengthField::encode(kV128));
5488}
5489
5490void InstructionSelectorT::VisitI64x2RelaxedLaneSelect(OpIndex node) {
5491 VisitRelaxedLaneSelect(this, node,
5492 kX64Blendvpd | VectorLengthField::encode(kV128));
5493}
5494
5495#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5496void InstructionSelectorT::VisitI8x32RelaxedLaneSelect(OpIndex node) {
5497 VisitRelaxedLaneSelect(this, node,
5498 kX64Pblendvb | VectorLengthField::encode(kV256));
5499}
5500void InstructionSelectorT::VisitI16x16RelaxedLaneSelect(OpIndex node) {
5501 VisitRelaxedLaneSelect(this, node,
5502 kX64Pblendvb | VectorLengthField::encode(kV256));
5503}
5504
5505void InstructionSelectorT::VisitI32x8RelaxedLaneSelect(OpIndex node) {
5506 VisitRelaxedLaneSelect(this, node,
5507 kX64Blendvps | VectorLengthField::encode(kV256));
5508}
5509
5510void InstructionSelectorT::VisitI64x4RelaxedLaneSelect(OpIndex node) {
5511 VisitRelaxedLaneSelect(this, node,
5512 kX64Blendvpd | VectorLengthField::encode(kV256));
5513}
5514#endif // V8_ENABLE_WASM_SIMD256_REVEC
5515
5516void InstructionSelectorT::VisitF16x8Pmin(OpIndex node) {
5517 X64OperandGeneratorT g(this);
5518 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
5519 DCHECK_EQ(op.input_count, 2);
5520 InstructionOperand dst = g.DefineAsRegister(node);
5521 InstructionCode instr_code = kX64Minph | VectorLengthField::encode(kV128);
5522 InstructionOperand temps[] = {g.TempSimd256Register(),
5523 g.TempSimd256Register()};
5524 size_t temp_count = arraysize(temps);
5525
5526 Emit(instr_code, dst, g.UseUniqueRegister(op.right()),
5527 g.UseUniqueRegister(op.left()), temp_count, temps);
5528}
5529
5530void InstructionSelectorT::VisitF16x8Pmax(OpIndex node) {
5531 X64OperandGeneratorT g(this);
5532 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
5533 DCHECK_EQ(op.input_count, 2);
5534 InstructionOperand dst = g.DefineAsRegister(node);
5535 InstructionCode instr_code = kX64Maxph | VectorLengthField::encode(kV128);
5536 InstructionOperand temps[] = {g.TempSimd256Register(),
5537 g.TempSimd256Register()};
5538 size_t temp_count = arraysize(temps);
5539
5540 Emit(instr_code, dst, g.UseUniqueRegister(op.right()),
5541 g.UseUniqueRegister(op.left()), temp_count, temps);
5542}
5543
5544void InstructionSelectorT::VisitF16x8DemoteF64x2Zero(OpIndex node) {
5545 X64OperandGeneratorT g(this);
5546 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5547 DCHECK_EQ(op.input_count, 1);
5548 InstructionOperand temps[] = {g.TempRegister(), g.TempSimd128Register(),
5549 g.TempSimd128Register()};
5550 size_t temp_count = arraysize(temps);
5551
5552 Emit(kX64F16x8DemoteF64x2Zero, g.DefineAsRegister(node),
5553 g.UseUniqueRegister(op.input()), temp_count, temps);
5554}
5555
5556void InstructionSelectorT::VisitF32x4Pmin(OpIndex node) {
5557 VisitMinOrMax<kV128>(this, node, kX64Minps, true);
5558}
5559
5560void InstructionSelectorT::VisitF32x4Pmax(OpIndex node) {
5561 VisitMinOrMax<kV128>(this, node, kX64Maxps, true);
5562}
5563
5564void InstructionSelectorT::VisitF64x2Pmin(OpIndex node) {
5565 VisitMinOrMax<kV128>(this, node, kX64Minpd, true);
5566}
5567
5568void InstructionSelectorT::VisitF64x2Pmax(OpIndex node) {
5569 VisitMinOrMax<kV128>(this, node, kX64Maxpd, true);
5570}
5571
5572void InstructionSelectorT::VisitF32x8Pmin(OpIndex node) {
5573 VisitMinOrMax<kV256>(this, node, kX64F32x8Pmin, true);
5574}
5575
5576void InstructionSelectorT::VisitF32x8Pmax(OpIndex node) {
5577 VisitMinOrMax<kV256>(this, node, kX64F32x8Pmax, true);
5578}
5579
5580void InstructionSelectorT::VisitF64x4Pmin(OpIndex node) {
5581 VisitMinOrMax<kV256>(this, node, kX64F64x4Pmin, true);
5582}
5583
5584void InstructionSelectorT::VisitF64x4Pmax(OpIndex node) {
5585 VisitMinOrMax<kV256>(this, node, kX64F64x4Pmax, true);
5586}
5587
5588void InstructionSelectorT::VisitF32x4RelaxedMin(OpIndex node) {
5589 VisitMinOrMax<kV128>(this, node, kX64Minps, false);
5590}
5591
5592void InstructionSelectorT::VisitF32x4RelaxedMax(OpIndex node) {
5593 VisitMinOrMax<kV128>(this, node, kX64Maxps, false);
5594}
5595
5596void InstructionSelectorT::VisitF64x2RelaxedMin(OpIndex node) {
5597 VisitMinOrMax<kV128>(this, node, kX64Minpd, false);
5598}
5599
5600void InstructionSelectorT::VisitF64x2RelaxedMax(OpIndex node) {
5601 VisitMinOrMax<kV128>(this, node, kX64Maxpd, false);
5602}
5603
5604void InstructionSelectorT::VisitI32x4ExtAddPairwiseI16x8S(OpIndex node) {
5605 X64OperandGeneratorT g(this);
5606 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5607 DCHECK_EQ(op.input_count, 1);
5608 InstructionOperand dst = CpuFeatures::IsSupported(AVX)
5609 ? g.DefineAsRegister(node)
5610 : g.DefineSameAsFirst(node);
5611 Emit(kX64I32x4ExtAddPairwiseI16x8S, dst, g.UseRegister(op.input()));
5612}
5613
5614void InstructionSelectorT::VisitI32x8ExtAddPairwiseI16x16S(OpIndex node) {
5615#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5616 X64OperandGeneratorT g(this);
5617 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5618 DCHECK_EQ(op.input_count, 1);
5619 Emit(kX64I32x8ExtAddPairwiseI16x16S, g.DefineAsRegister(node),
5620 g.UseRegister(op.input()));
5621#else
5622 UNREACHABLE();
5623#endif
5624}
5625
5626void InstructionSelectorT::VisitI32x4ExtAddPairwiseI16x8U(OpIndex node) {
5627 X64OperandGeneratorT g(this);
5628 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5629 DCHECK_EQ(op.input_count, 1);
5630 InstructionOperand dst = CpuFeatures::IsSupported(AVX)
5631 ? g.DefineAsRegister(node)
5632 : g.DefineSameAsFirst(node);
5633 Emit(kX64I32x4ExtAddPairwiseI16x8U, dst, g.UseRegister(op.input()));
5634}
5635
5636void InstructionSelectorT::VisitI32x8ExtAddPairwiseI16x16U(OpIndex node) {
5637#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5638 X64OperandGeneratorT g(this);
5639 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5640 DCHECK_EQ(op.input_count, 1);
5641 Emit(kX64I32x8ExtAddPairwiseI16x16U, g.DefineAsRegister(node),
5642 g.UseRegister(op.input()));
5643#else
5644 UNREACHABLE();
5645#endif
5646}
5647
5648void InstructionSelectorT::VisitI16x8ExtAddPairwiseI8x16S(OpIndex node) {
5649 X64OperandGeneratorT g(this);
5650 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5651 DCHECK_EQ(op.input_count, 1);
5652 // Codegen depends on dst != src.
5653 Emit(kX64I16x8ExtAddPairwiseI8x16S, g.DefineAsRegister(node),
5654 g.UseUniqueRegister(op.input()));
5655}
5656
5657void InstructionSelectorT::VisitI16x16ExtAddPairwiseI8x32S(OpIndex node) {
5658#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5659 X64OperandGeneratorT g(this);
5660 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5661 DCHECK_EQ(op.input_count, 1);
5662 Emit(kX64I16x16ExtAddPairwiseI8x32S, g.DefineAsRegister(node),
5663 g.UseUniqueRegister(op.input()));
5664#else
5665 UNREACHABLE();
5666#endif
5667}
5668
5669void InstructionSelectorT::VisitI16x8ExtAddPairwiseI8x16U(OpIndex node) {
5670 X64OperandGeneratorT g(this);
5671 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5672 DCHECK_EQ(op.input_count, 1);
5673 InstructionOperand dst = CpuFeatures::IsSupported(AVX)
5674 ? g.DefineAsRegister(node)
5675 : g.DefineSameAsFirst(node);
5676 Emit(kX64I16x8ExtAddPairwiseI8x16U, dst, g.UseRegister(op.input()));
5677}
5678
5679void InstructionSelectorT::VisitI16x16ExtAddPairwiseI8x32U(OpIndex node) {
5680#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5681 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5682 DCHECK_EQ(op.input_count, 1);
5683 X64OperandGeneratorT g(this);
5684 Emit(kX64I16x16ExtAddPairwiseI8x32U, g.DefineAsRegister(node),
5685 g.UseUniqueRegister(op.input()));
5686#else
5687 UNREACHABLE();
5688#endif
5689}
5690
5691void InstructionSelectorT::VisitI8x16Popcnt(OpIndex node) {
5692 X64OperandGeneratorT g(this);
5693 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5694 DCHECK_EQ(op.input_count, 1);
5695 InstructionOperand temps[] = {g.TempSimd128Register()};
5696 Emit(kX64I8x16Popcnt, g.DefineAsRegister(node),
5697 g.UseUniqueRegister(op.input()), arraysize(temps), temps);
5698}
5699
5700void InstructionSelectorT::VisitF64x2ConvertLowI32x4U(OpIndex node) {
5701 X64OperandGeneratorT g(this);
5702 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5703 DCHECK_EQ(op.input_count, 1);
5704 InstructionOperand dst =
5705 IsSupported(AVX) ? g.DefineAsRegister(node) : g.DefineSameAsFirst(node);
5706 Emit(kX64F64x2ConvertLowI32x4U, dst, g.UseRegister(op.input()));
5707}
5708
5709void InstructionSelectorT::VisitI32x4TruncSatF64x2SZero(OpIndex node) {
5710 X64OperandGeneratorT g(this);
5711 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5712 DCHECK_EQ(op.input_count, 1);
5713 if (CpuFeatures::IsSupported(AVX)) {
5714 // Requires dst != src.
5715 Emit(kX64I32x4TruncSatF64x2SZero, g.DefineAsRegister(node),
5716 g.UseUniqueRegister(op.input()));
5717 } else {
5718 Emit(kX64I32x4TruncSatF64x2SZero, g.DefineSameAsFirst(node),
5719 g.UseRegister(op.input()));
5720 }
5721}
5722
5723void InstructionSelectorT::VisitI32x4TruncSatF64x2UZero(OpIndex node) {
5724 X64OperandGeneratorT g(this);
5725 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5726 DCHECK_EQ(op.input_count, 1);
5727 InstructionOperand dst = CpuFeatures::IsSupported(AVX)
5728 ? g.DefineAsRegister(node)
5729 : g.DefineSameAsFirst(node);
5730 Emit(kX64I32x4TruncSatF64x2UZero, dst, g.UseRegister(op.input()));
5731}
5732
5733void InstructionSelectorT::VisitI32x4RelaxedTruncF64x2SZero(OpIndex node) {
5734 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5735 DCHECK_EQ(op.input_count, 1);
5736 VisitFloatUnop(this, node, op.input(), kX64Cvttpd2dq);
5737}
5738
5739void InstructionSelectorT::VisitI32x4RelaxedTruncF64x2UZero(OpIndex node) {
5740 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5741 DCHECK_EQ(op.input_count, 1);
5742 VisitFloatUnop(this, node, op.input(), kX64I32x4TruncF64x2UZero);
5743}
5744
5745void InstructionSelectorT::VisitI32x4RelaxedTruncF32x4S(OpIndex node) {
5746 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5747 DCHECK_EQ(op.input_count, 1);
5748 VisitFloatUnop(this, node, op.input(), kX64Cvttps2dq);
5749}
5750
5751void InstructionSelectorT::VisitI32x4RelaxedTruncF32x4U(OpIndex node) {
5752 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5753 DCHECK_EQ(op.input_count, 1);
5754 X64OperandGeneratorT g(this);
5755 InstructionOperand temps[] = {g.TempSimd128Register()};
5756 if (IsSupported(AVX)) {
5757 Emit(kX64I32x4TruncF32x4U, g.DefineAsRegister(node),
5758 g.UseRegister(op.input()), arraysize(temps), temps);
5759 } else {
5760 Emit(kX64I32x4TruncF32x4U, g.DefineSameAsFirst(node),
5761 g.UseRegister(op.input()), arraysize(temps), temps);
5762 }
5763}
5764
5765void InstructionSelectorT::VisitI32x8RelaxedTruncF32x8S(OpIndex node) {
5766#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5767 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5768 DCHECK_EQ(op.input_count, 1);
5769 VisitFloatUnop(this, node, op.input(),
5770 kX64Cvttps2dq | VectorLengthField::encode(kV256));
5771#else
5772 UNREACHABLE();
5773#endif
5774}
5775
5776void InstructionSelectorT::VisitI32x8RelaxedTruncF32x8U(OpIndex node) {
5777#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5778 const Simd256UnaryOp& op = Cast<Simd256UnaryOp>(node);
5779 DCHECK_EQ(op.input_count, 1);
5780 DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
5781 X64OperandGeneratorT g(this);
5782 InstructionOperand temps[] = {g.TempSimd256Register()};
5783 Emit(kX64I32x8TruncF32x8U, g.DefineAsRegister(node),
5784 g.UseRegister(op.input()), arraysize(temps), temps);
5785#else
5786 UNREACHABLE();
5787#endif
5788}
5789
5790void InstructionSelectorT::VisitI64x2GtS(OpIndex node) {
5791 X64OperandGeneratorT g(this);
5792 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
5793 DCHECK_EQ(op.input_count, 2);
5794 if (CpuFeatures::IsSupported(AVX)) {
5795 Emit(kX64IGtS | LaneSizeField::encode(kL64) |
5796 VectorLengthField::encode(kV128),
5797 g.DefineAsRegister(node), g.UseRegister(op.left()),
5798 g.UseRegister(op.right()));
5799 } else if (CpuFeatures::IsSupported(SSE4_2)) {
5800 Emit(kX64IGtS | LaneSizeField::encode(kL64) |
5801 VectorLengthField::encode(kV128),
5802 g.DefineSameAsFirst(node), g.UseRegister(op.left()),
5803 g.UseRegister(op.right()));
5804 } else {
5805 Emit(kX64IGtS | LaneSizeField::encode(kL64) |
5806 VectorLengthField::encode(kV128),
5807 g.DefineAsRegister(node), g.UseUniqueRegister(op.left()),
5808 g.UseUniqueRegister(op.right()));
5809 }
5810}
5811
5812void InstructionSelectorT::VisitI64x2GeS(OpIndex node) {
5813 X64OperandGeneratorT g(this);
5814 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
5815 DCHECK_EQ(op.input_count, 2);
5816 if (CpuFeatures::IsSupported(AVX)) {
5817 Emit(kX64IGeS | LaneSizeField::encode(kL64) |
5818 VectorLengthField::encode(kV128),
5819 g.DefineAsRegister(node), g.UseRegister(op.left()),
5820 g.UseRegister(op.right()));
5821 } else if (CpuFeatures::IsSupported(SSE4_2)) {
5822 Emit(kX64IGeS | LaneSizeField::encode(kL64) |
5823 VectorLengthField::encode(kV128),
5824 g.DefineAsRegister(node), g.UseUniqueRegister(op.left()),
5825 g.UseRegister(op.right()));
5826 } else {
5827 Emit(kX64IGeS | LaneSizeField::encode(kL64) |
5828 VectorLengthField::encode(kV128),
5829 g.DefineAsRegister(node), g.UseUniqueRegister(op.left()),
5830 g.UseUniqueRegister(op.right()));
5831 }
5832}
5833
5834void InstructionSelectorT::VisitI64x4GeS(OpIndex node) {
5835#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5836 X64OperandGeneratorT g(this);
5837 const Simd256BinopOp& op = Cast<Simd256BinopOp>(node);
5838 DCHECK_EQ(op.input_count, 2);
5839 DCHECK(CpuFeatures::IsSupported(AVX2));
5840 Emit(
5841 kX64IGeS | LaneSizeField::encode(kL64) | VectorLengthField::encode(kV256),
5842 g.DefineAsRegister(node), g.UseRegister(op.left()),
5843 g.UseRegister(op.right()));
5844#else
5845 UNREACHABLE();
5846#endif
5847}
5848
5849void InstructionSelectorT::VisitI64x2Abs(OpIndex node) {
5850 X64OperandGeneratorT g(this);
5851 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5852 DCHECK_EQ(op.input_count, 1);
5853 if (CpuFeatures::IsSupported(AVX)) {
5854 Emit(kX64IAbs | LaneSizeField::encode(kL64) |
5855 VectorLengthField::encode(kV128),
5856 g.DefineAsRegister(node), g.UseUniqueRegister(op.input()));
5857 } else {
5858 Emit(kX64IAbs | LaneSizeField::encode(kL64) |
5859 VectorLengthField::encode(kV128),
5860 g.DefineSameAsFirst(node), g.UseRegister(op.input()));
5861 }
5862}
5863
5864bool InstructionSelectorT::CanOptimizeF64x2PromoteLowF32x4(OpIndex node) {
5865 const Simd128UnaryOp& op = Cast<Opmask::kSimd128F64x2PromoteLowF32x4>(node);
5866 V<Simd128> input = op.input();
5867 return Is<Opmask::kSimd128LoadTransform64Zero>(input) &&
5868 CanCover(node, input);
5869}
5870
5871void InstructionSelectorT::VisitF64x2PromoteLowF32x4(OpIndex node) {
5872 X64OperandGeneratorT g(this);
5873 const Simd128UnaryOp& op = Cast<Simd128UnaryOp>(node);
5874 DCHECK_EQ(op.input_count, 1);
5875 InstructionCode code = kX64F64x2PromoteLowF32x4;
5876 if (CanOptimizeF64x2PromoteLowF32x4(node)) {
5877 V<Simd128> input = op.input();
5878 const Simd128LoadTransformOp& load_transform =
5879 Cast<Simd128LoadTransformOp>(input);
5880 if (load_transform.load_kind.with_trap_handler) {
5881 code |= AccessModeField::encode(kMemoryAccessProtectedMemOutOfBounds);
5882 }
5883 // LoadTransforms cannot be eliminated, so they are visited even if
5884 // unused. Mark it as defined so that we don't visit it.
5885 MarkAsDefined(input);
5886 VisitLoad(node, input, code);
5887 return;
5888 }
5889
5890 VisitRR(this, node, code);
5891}
5892
5893void InstructionSelectorT::VisitI16x8DotI8x16I7x16S(OpIndex node) {
5894 X64OperandGeneratorT g(this);
5895 const Simd128BinopOp& op = Cast<Simd128BinopOp>(node);
5896 DCHECK_EQ(op.input_count, 2);
5897 Emit(kX64I16x8DotI8x16I7x16S, g.DefineAsRegister(node),
5898 g.UseUniqueRegister(op.left()), g.UseRegister(op.right()));
5899}
5900
5901void InstructionSelectorT::VisitI32x4DotI8x16I7x16AddS(OpIndex node) {
5902 X64OperandGeneratorT g(this);
5903 const Simd128TernaryOp& op = Cast<Simd128TernaryOp>(node);
5904 DCHECK_EQ(op.input_count, 3);
5905 if (CpuFeatures::IsSupported(AVX_VNNI)) {
5906 Emit(kX64I32x4DotI8x16I7x16AddS, g.DefineSameAsInput(node, 2),
5907 g.UseRegister(op.input(0)), g.UseRegister(op.input(1)),
5908 g.UseRegister(op.input(2)));
5909 } else {
5910 InstructionOperand temps[] = {g.TempSimd128Register()};
5911 Emit(kX64I32x4DotI8x16I7x16AddS, g.DefineSameAsInput(node, 2),
5912 g.UseUniqueRegister(op.input(0)), g.UseUniqueRegister(op.input(1)),
5913 g.UseUniqueRegister(op.input(2)), arraysize(temps), temps);
5914 }
5915}
5916
5917#ifdef V8_ENABLE_WASM_SIMD256_REVEC
5918void InstructionSelectorT::VisitI16x16DotI8x32I7x32S(OpIndex node) {
5919 X64OperandGeneratorT g(this);
5920 const Simd256BinopOp& op = Cast<Simd256BinopOp>(node);
5921 DCHECK_EQ(op.input_count, 2);
5922 Emit(kX64I16x16DotI8x32I7x32S, g.DefineAsRegister(node),
5923 g.UseUniqueRegister(op.left()), g.UseRegister(op.right()));
5924}
5925
5926void InstructionSelectorT::VisitI32x8DotI8x32I7x32AddS(OpIndex node) {
5927 X64OperandGeneratorT g(this);
5928 const Simd256TernaryOp& op = Cast<Simd256TernaryOp>(node);
5929 DCHECK_EQ(op.input_count, 3);
5930 if (CpuFeatures::IsSupported(AVX_VNNI)) {
5931 Emit(kX64I32x8DotI8x32I7x32AddS, g.DefineSameAsInput(node, 2),
5932 g.UseRegister(op.input(0)), g.UseRegister(op.input(1)),
5933 g.UseRegister(op.input(2)));
5934 } else {
5935 InstructionOperand temps[] = {g.TempSimd256Register()};
5936 Emit(kX64I32x8DotI8x32I7x32AddS, g.DefineSameAsInput(node, 2),
5937 g.UseUniqueRegister(op.input(0)), g.UseUniqueRegister(op.input(1)),
5938 g.UseUniqueRegister(op.input(2)), arraysize(temps), temps);
5939 }
5940}
5941#endif
5942
5943void InstructionSelectorT::VisitSetStackPointer(OpIndex node) {
5944 X64OperandGeneratorT g(this);
5945 const SetStackPointerOp& op = Cast<SetStackPointerOp>(node);
5946 DCHECK_EQ(op.input_count, 1);
5947 auto input = g.UseAny(op.value());
5948 Emit(kArchSetStackPointer, 0, nullptr, 1, &input);
5949}
5950
5951#endif // V8_ENABLE_WEBASSEMBLY
5952
5953#ifndef V8_ENABLE_WEBASSEMBLY
5954#define VISIT_UNSUPPORTED_OP(op) \
5955 void InstructionSelectorT::Visit##op(OpIndex) { UNREACHABLE(); }
5958#endif
5959
5960void InstructionSelectorT::AddOutputToSelectContinuation(OperandGenerator* g,
5961 int first_input_index,
5962 OpIndex node) {
5963 continuation_outputs_.push_back(
5964 g->DefineSameAsInput(node, first_input_index));
5965}
5966
5967// static
5968MachineOperatorBuilder::Flags
5969InstructionSelector::SupportedMachineOperatorFlags() {
5970 MachineOperatorBuilder::Flags flags =
5971 MachineOperatorBuilder::kWord32ShiftIsSafe |
5972 MachineOperatorBuilder::kWord32Ctz | MachineOperatorBuilder::kWord64Ctz |
5973 MachineOperatorBuilder::kWord32Rol | MachineOperatorBuilder::kWord64Rol |
5974 MachineOperatorBuilder::kWord32Select |
5975 MachineOperatorBuilder::kWord64Select;
5976 if (CpuFeatures::IsSupported(POPCNT)) {
5977 flags |= MachineOperatorBuilder::kWord32Popcnt |
5978 MachineOperatorBuilder::kWord64Popcnt;
5979 }
5980 if (CpuFeatures::IsSupported(SSE4_1)) {
5981 flags |= MachineOperatorBuilder::kFloat32RoundDown |
5982 MachineOperatorBuilder::kFloat64RoundDown |
5983 MachineOperatorBuilder::kFloat32RoundUp |
5984 MachineOperatorBuilder::kFloat64RoundUp |
5985 MachineOperatorBuilder::kFloat32RoundTruncate |
5986 MachineOperatorBuilder::kFloat64RoundTruncate |
5987 MachineOperatorBuilder::kFloat32RoundTiesEven |
5988 MachineOperatorBuilder::kFloat64RoundTiesEven;
5989 }
5990 if (CpuFeatures::IsSupported(F16C)) {
5991 flags |= MachineOperatorBuilder::kFloat16;
5992 if (CpuFeatures::IsSupported(AVX)) {
5993 flags |= MachineOperatorBuilder::kFloat16RawBitsConversion;
5994 }
5995 }
5996 return flags;
5997}
5998
5999// static
6000MachineOperatorBuilder::AlignmentRequirements
6001InstructionSelector::AlignmentRequirements() {
6002 return MachineOperatorBuilder::AlignmentRequirements::
6003 FullUnalignedAccessSupport();
6004}
6005
6006} // namespace compiler
6007} // namespace internal
6008} // namespace v8
#define V(Name)
#define F(name, str)
interpreter::OperandScale scale
Definition builtins.cc:44
Builtins::Kind kind
Definition builtins.cc:40
static constexpr U encode(T value)
Definition bit-field.h:55
Bootstrapper * bootstrapper()
Definition isolate.h:1178
RootsTable & roots_table()
Definition isolate.h:1250
static constexpr MachineType Float64()
static constexpr MachineType Uint8()
constexpr MachineRepresentation representation() const
static constexpr MachineType Int32()
static constexpr MachineType Simd128()
static constexpr MachineType Uint32()
static constexpr MachineType Uint16()
static constexpr MachineType Int16()
static constexpr MachineType Float32()
static constexpr MachineType None()
static constexpr MachineType Int8()
static int32_t RootRegisterOffsetForRootIndex(RootIndex root_index)
Tagged_t ReadOnlyRootPtr(RootIndex index)
static intptr_t RootRegisterOffsetForExternalReference(Isolate *isolate, const ExternalReference &reference)
bool IsRootHandle(IndirectHandle< T > handle, RootIndex *index) const
Definition roots-inl.h:65
static constexpr bool IsReadOnly(RootIndex root_index)
Definition roots.h:623
static FlagsContinuationT ForSet(FlagsCondition condition, turboshaft::OpIndex result)
int AllocateSpillSlot(int width, int alignment=0, bool is_tagged=false)
Definition frame.h:138
void VisitWordCompareZero(turboshaft::OpIndex user, turboshaft::OpIndex value, FlagsContinuation *cont)
void EmitPrepareArguments(ZoneVector< PushParameter > *arguments, const CallDescriptor *call_descriptor, turboshaft::OpIndex node)
Instruction * Emit(InstructionCode opcode, InstructionOperand output, size_t temp_count=0, InstructionOperand *temps=nullptr)
bool CanCover(turboshaft::OpIndex user, turboshaft::OpIndex node) const
void EmitMoveFPRToParam(InstructionOperand *op, LinkageLocation location)
Instruction * EmitWithContinuation(InstructionCode opcode, FlagsContinuation *cont)
void VisitLoadTransform(Node *node, Node *value, InstructionCode opcode)
void VisitStackPointerGreaterThan(turboshaft::OpIndex node, FlagsContinuation *cont)
bool IsReallyLive(turboshaft::OpIndex node) const
void ConsumeEqualZero(turboshaft::OpIndex *user, turboshaft::OpIndex *value, FlagsContinuation *cont)
turboshaft::OptionalOpIndex FindProjection(turboshaft::OpIndex node, size_t projection_index)
void VisitLoad(turboshaft::OpIndex node, turboshaft::OpIndex value, InstructionCode opcode)
bool IsCommutative(turboshaft::OpIndex node) const
int GetEffectLevel(turboshaft::OpIndex node) const
void VisitFloat64Ieee754Binop(turboshaft::OpIndex, InstructionCode code)
void EmitMoveParamToFPR(turboshaft::OpIndex node, int index)
void VisitFloat64Ieee754Unop(turboshaft::OpIndex, InstructionCode code)
void EmitPrepareResults(ZoneVector< PushParameter > *results, const CallDescriptor *call_descriptor, turboshaft::OpIndex node)
InstructionOperand Use(turboshaft::OpIndex node)
InstructionOperand DefineSameAsFirst(turboshaft::OpIndex node)
InstructionOperand UseUniqueRegister(turboshaft::OpIndex node)
InstructionOperand UseImmediate64(int64_t immediate)
InstructionOperand UseRegister(turboshaft::OpIndex node)
InstructionOperand GetEffectiveIndexOperand(OpIndex index, AddressingMode *mode)
bool CanBeMemoryOperand(InstructionCode opcode, OpIndex node, OpIndex input, int effect_level)
AddressingMode GetEffectiveAddressMemoryOperand(OpIndex operand, InstructionOperand inputs[], size_t *input_count, RegisterUseKind reg_kind=RegisterUseKind::kUseRegister)
AddressingMode GenerateMemoryOperandInputs(OptionalOpIndex index, int scale_exponent, OpIndex base, int64_t displacement, DisplacementMode displacement_mode, InstructionOperand inputs[], size_t *input_count, RegisterUseKind reg_kind=RegisterUseKind::kUseRegister)
static constexpr MemoryRepresentation AnyTagged()
static constexpr MemoryRepresentation Float16()
static constexpr MemoryRepresentation AnyUncompressedTagged()
static constexpr MemoryRepresentation UncompressedTaggedPointer()
static constexpr MemoryRepresentation TaggedSigned()
static constexpr MemoryRepresentation Int32()
static constexpr MemoryRepresentation Int64()
static constexpr MemoryRepresentation Simd128()
static constexpr MemoryRepresentation SandboxedPointer()
static constexpr MemoryRepresentation Uint16()
static constexpr MemoryRepresentation ProtectedPointer()
static constexpr MemoryRepresentation Uint8()
static constexpr MemoryRepresentation Simd256()
static constexpr MemoryRepresentation Int16()
static constexpr MemoryRepresentation IndirectPointer()
static constexpr MemoryRepresentation Float32()
static constexpr MemoryRepresentation Float64()
const Operation & Get(V< AnyOrNone > op_idx) const
bool MatchWordBinop(V< Any > matched, VMatch< T > left, VMatch< T > right, OMatch< WordBinopOp::Kind > kind={}, OMatch< WordRepresentation > rep={}) const
const underlying_operation_t< Op > * TryCast(V< AnyOrNone > op_idx) const
bool MatchExternalConstant(V< Any > matched, ExternalReference *reference) const
bool MatchTruncateWord64ToWord32(V< Any > matched, VMatch< Word64 > input) const
const underlying_operation_t< Op > & Cast(V< AnyOrNone > op_idx) const
bool MatchSignedIntegralConstant(V< Any > matched, int64_t *constant) const
bool MatchIntegralWord32Constant(V< Any > matched, uint32_t *constant) const
bool MatchUnsignedIntegralConstant(V< Any > matched, uint64_t *constant) const
static constexpr RegisterRepresentation Word32()
static constexpr RegisterRepresentation Word64()
static constexpr RegisterRepresentation Tagged()
Handle< Code > code
#define COMPRESS_POINTERS_BOOL
Definition globals.h:99
#define V8_ENABLE_SANDBOX_BOOL
Definition globals.h:160
other heap size flags(e.g. initial_heap_size) take precedence") DEFINE_SIZE_T( max_shared_heap_size
Isolate * isolate
#define RR_OP_T_LIST(V)
#define RR_VISITOR(Name, opcode)
#define RO_VISITOR(Name, opcode)
int32_t displacement
#define RO_OP_T_LIST(V)
int32_t offset
DisplacementMode displacement_mode
#define SIMD_VISIT_EXTRACT_LANE(Type, Sign)
#define VISIT_SIMD_QFMOP(Name, instruction)
OptionalOpIndex index
int32_t offset
#define VISIT_ATOMIC_BINOP(op)
#define VISIT_UNSUPPORTED_OP(op)
Node * node
Instruction * instr
ZoneVector< RpoNumber > & result
uint32_t const mask
#define SIMD_UNOP_LIST(V)
int m
Definition mul-fft.cc:294
int n
Definition mul-fft.cc:296
int int32_t
Definition unicode.cc:40
bool any_of(const C &container, const P &predicate)
signed_type NegateWithWraparound(signed_type a)
auto Reversed(T &t)
Definition iterator.h:105
constexpr bool IsInRange(T value, U lower_limit, U higher_limit)
Definition bounds.h:20
ShiftMask::For< ShiftOp::Kind::kShiftRightArithmetic, WordRepresentation::Word64()> kWord64ShiftRightArithmetic
Definition opmasks.h:229
ShiftMask::For< ShiftOp::Kind::kShiftRightLogical, WordRepresentation::Word64()> kWord64ShiftRightLogical
Definition opmasks.h:232
V8_INLINE const Operation & Get(const Graph &graph, OpIndex index)
Definition graph.h:1231
WordWithBits< 128 > Simd128
Definition index.h:236
WordWithBits< 256 > Simd256
Definition index.h:237
void VisitAtomicBinop(InstructionSelectorT *selector, OpIndex node, ArchOpcode opcode, AtomicWidth width)
int32_t GetImmediateIntegerValue(InstructionSelectorT *selector, OpIndex node)
void VisitAtomicExchange(InstructionSelectorT *selector, OpIndex node, ArchOpcode opcode)
BinopMatcher< Int32Matcher, Int32Matcher, MachineRepresentation::kWord32 > Int32BinopMatcher
static void VisitRRO(InstructionSelectorT *selector, ArchOpcode opcode, OpIndex node)
static void VisitRR(InstructionSelectorT *selector, ArchOpcode opcode, OpIndex node)
std::optional< uint64_t > TryGetRightWordConstant(InstructionSelectorT *selector, OpIndex node)
BinopMatcher< Int64Matcher, Int64Matcher, MachineRepresentation::kWord64 > Int64BinopMatcher
std::optional< ScaledIndexMatch > TryMatchScaledIndex(InstructionSelectorT *selector, OpIndex node, bool allow_power_of_two_plus_one)
std::optional< ScaledIndexMatch > TryMatchScaledIndex32(InstructionSelectorT *selector, OpIndex node, bool allow_power_of_two_plus_one)
static Instruction * VisitCompare(InstructionSelectorT *selector, InstructionCode opcode, InstructionOperand left, InstructionOperand right, FlagsContinuationT *cont)
std::optional< int32_t > GetWord32Constant(InstructionSelectorT *selector, OpIndex node, bool allow_implicit_int64_truncation=TurboshaftAdapter::AllowsImplicitWord64ToWord32Truncation)
static void VisitBinop(InstructionSelectorT *selector, OpIndex node, InstructionCode opcode, FlagsContinuationT *cont)
void VisitFloat32Compare(InstructionSelectorT *selector, OpIndex node, FlagsContinuationT *cont)
std::optional< BaseWithScaledIndexAndDisplacementMatch > TryMatchBaseWithScaledIndexAndDisplacement64(InstructionSelectorT *selector, OpIndex node)
void VisitStoreCommon(InstructionSelectorT *selector, OpIndex node, StoreRepresentation store_rep, std::optional< AtomicMemoryOrder > atomic_order)
static void VisitBinop(InstructionSelectorT *selector, turboshaft::OpIndex node, InstructionCode opcode, bool has_reverse_opcode, InstructionCode reverse_opcode, FlagsContinuationT *cont)
bool MatchScaledIndex(InstructionSelectorT *selector, OpIndex node, OpIndex *index, int *scale, bool *power_of_two_plus_one)
bool ValueFitsIntoImmediate(int64_t value)
Instruction * VisitWordCompare(InstructionSelectorT *selector, OpIndex node, InstructionCode opcode, FlagsContinuationT *cont, bool commutative)
void VisitFloat64Compare(InstructionSelectorT *selector, OpIndex node, FlagsContinuationT *cont)
RecordWriteMode WriteBarrierKindToRecordWriteMode(WriteBarrierKind write_barrier_kind)
bool CanBeImmediate(InstructionSelectorT *selector, OpIndex node)
std::optional< ScaledIndexMatch > TryMatchScaledIndex64(InstructionSelectorT *selector, OpIndex node, bool allow_power_of_two_plus_one)
void VisitRR(InstructionSelectorT *selector, InstructionCode opcode, OpIndex node)
void VisitAtomicCompareExchange(InstructionSelectorT *selector, OpIndex node, ArchOpcode opcode)
std::optional< BaseWithScaledIndexAndDisplacementMatch > TryMatchBaseWithScaledIndexAndDisplacement32(InstructionSelectorT *selector, OpIndex node)
std::optional< BaseWithScaledIndexAndDisplacementMatch > TryMatchBaseWithScaledIndexAndDisplacement64ForWordBinop(InstructionSelectorT *selector, OpIndex left, OpIndex right, bool is_commutative)
constexpr int kSimd128Size
Definition globals.h:706
constexpr bool CanBeTaggedOrCompressedOrIndirectPointer(MachineRepresentation rep)
Tagged(T object) -> Tagged< T >
constexpr bool CanBeTaggedOrCompressedPointer(MachineRepresentation rep)
constexpr int kSystemPointerSizeLog2
Definition globals.h:494
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 allocation gc speed threshold for starting incremental marking via a task in percent of available threshold for starting incremental marking immediately in percent of available Use a single schedule for determining a marking schedule between JS and C objects schedules the minor GC task with kUserVisible priority max worker number of concurrent for NumberOfWorkerThreads start background threads that allocate memory concurrent_array_buffer_sweeping use parallel threads to clear weak refs in the atomic pause trace progress of the incremental marking trace object counts and memory usage report a tick only when allocated zone memory changes by this amount TracingFlags::gc_stats TracingFlags::gc_stats track native contexts that are expected to be garbage collected verify heap pointers before and after GC memory reducer runs GC with ReduceMemoryFootprint flag Maximum number of memory reducer GCs scheduled Old gen GC speed is computed directly from gc tracer counters Perform compaction on full GCs based on V8 s default heuristics Perform compaction on every full GC Perform code space compaction when finalizing a full GC with stack Stress GC compaction to flush out bugs with moving objects flush of baseline code when it has not been executed recently Use time base code flushing instead of age Use a progress bar to scan large objects in increments when incremental marking is active force incremental marking for small heaps and run it more often force marking at random points between and force scavenge at random points between and reclaim otherwise unreachable unmodified wrapper objects when possible less compaction in non memory reducing mode use high priority threads for concurrent Marking Test mode only flag It allows an unit test to select evacuation candidates use incremental marking for CppHeap cppheap_concurrent_marking c value for membalancer A special constant to balance between memory and space tradeoff The smaller the more memory it uses enable use of SSE4 instructions if available enable use of AVX VNNI instructions if available enable use of POPCNT instruction if available force all emitted branches to be in long mode(MIPS/PPC only)") DEFINE_BOOL(partial_constant_pool
constexpr bool IsAnyTagged(MachineRepresentation rep)
constexpr bool IsAnyCompressed(MachineRepresentation rep)
constexpr int kSimd256Size
Definition globals.h:709
constexpr int kSystemPointerSize
Definition globals.h:410
constexpr bool SmiValuesAre31Bits()
const int kHeapObjectTag
Definition v8-internal.h:72
V8_EXPORT_PRIVATE FlagValues v8_flags
V8_EXPORT_PRIVATE constexpr int ElementSizeLog2Of(MachineRepresentation)
return value
Definition map-inl.h:893
constexpr bool Is64()
constexpr bool CanBeCompressedPointer(MachineRepresentation rep)
constexpr bool IsIntegral(MachineRepresentation rep)
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
#define MACHINE_SIMD256_OP_LIST(V)
Definition opcodes.h:1159
#define MACHINE_SIMD128_OP_LIST(V)
Definition opcodes.h:879
Operation
Definition operation.h:43
#define I(name, number_of_args, result_size)
Definition runtime.cc:36
#define UNREACHABLE()
Definition logging.h:67
#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 DCHECK_GT(v1, v2)
Definition logging.h:487
#define USE(...)
Definition macros.h:293
#define arraysize(array)
Definition macros.h:67
base::Vector< const turboshaft::OpIndex > inputs(turboshaft::OpIndex node) const
bool IsLoadOrLoadImmutable(turboshaft::OpIndex node) const
turboshaft::Opcode opcode(turboshaft::OpIndex node) const
V8_INLINE OpIndex input(size_t i) const
Definition operations.h:959
const underlying_operation_t< Op > * TryCast() const
Definition operations.h:990
underlying_operation_t< Op > & Cast()
Definition operations.h:980
#define V8_STATIC_ROOTS_BOOL
Definition v8config.h:1001
#define V8_UNLIKELY(condition)
Definition v8config.h:660