1 // Copyright 2022 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/compiler/simplified-lowering-verifier.h"
6
7 #include "src/compiler/operation-typer.h"
8 #include "src/compiler/type-cache.h"
9
10 namespace v8 {
11 namespace internal {
12 namespace compiler {
13
LeastGeneralTruncation(const Truncation & t1,const Truncation & t2)14 Truncation LeastGeneralTruncation(const Truncation& t1, const Truncation& t2) {
15 if (t1.IsLessGeneralThan(t2)) return t1;
16 CHECK(t2.IsLessGeneralThan(t1));
17 return t2;
18 }
19
LeastGeneralTruncation(const Truncation & t1,const Truncation & t2,const Truncation & t3)20 Truncation LeastGeneralTruncation(const Truncation& t1, const Truncation& t2,
21 const Truncation& t3) {
22 return LeastGeneralTruncation(LeastGeneralTruncation(t1, t2), t3);
23 }
24
CheckType(Node * node,const Type & type)25 void SimplifiedLoweringVerifier::CheckType(Node* node, const Type& type) {
26 CHECK(NodeProperties::IsTyped(node));
27 Type node_type = NodeProperties::GetType(node);
28 if (!type.Is(node_type)) {
29 std::ostringstream type_str;
30 type.PrintTo(type_str);
31 std::ostringstream node_type_str;
32 node_type.PrintTo(node_type_str);
33
34 FATAL(
35 "SimplifiedLoweringVerifierError: verified type %s of node #%d:%s "
36 "does not match with type %s assigned during lowering",
37 type_str.str().c_str(), node->id(), node->op()->mnemonic(),
38 node_type_str.str().c_str());
39 }
40 }
41
CheckAndSet(Node * node,const Type & type,const Truncation & trunc)42 void SimplifiedLoweringVerifier::CheckAndSet(Node* node, const Type& type,
43 const Truncation& trunc) {
44 DCHECK(!type.IsInvalid());
45
46 if (NodeProperties::IsTyped(node)) {
47 CheckType(node, type);
48 } else {
49 // We store the type inferred by the verification pass. We do not update
50 // the node's type directly, because following phases might encounter
51 // unsound types as long as the verification is not complete.
52 SetType(node, type);
53 }
54 SetTruncation(node, GeneralizeTruncation(trunc, type));
55 }
56
IsModuloTruncation(const Truncation & truncation)57 bool IsModuloTruncation(const Truncation& truncation) {
58 return truncation.IsUsedAsWord32() || truncation.IsUsedAsWord64() ||
59 Truncation::Any().IsLessGeneralThan(truncation);
60 }
61
GeneralizeTruncation(const Truncation & truncation,const Type & type) const62 Truncation SimplifiedLoweringVerifier::GeneralizeTruncation(
63 const Truncation& truncation, const Type& type) const {
64 IdentifyZeros identify_zeros = truncation.identify_zeros();
65 if (!type.Maybe(Type::MinusZero())) {
66 identify_zeros = IdentifyZeros::kDistinguishZeros;
67 }
68
69 switch (truncation.kind()) {
70 case Truncation::TruncationKind::kAny: {
71 return Truncation::Any(identify_zeros);
72 }
73 case Truncation::TruncationKind::kWord32: {
74 if (type.Is(Type::Signed32OrMinusZero()) ||
75 type.Is(Type::Unsigned32OrMinusZero())) {
76 return Truncation::Any(identify_zeros);
77 }
78 return Truncation(Truncation::TruncationKind::kWord32, identify_zeros);
79 }
80 case Truncation::TruncationKind::kWord64: {
81 if (type.Is(Type::BigInt())) {
82 DCHECK_EQ(identify_zeros, IdentifyZeros::kDistinguishZeros);
83 if (type.Is(Type::SignedBigInt64()) ||
84 type.Is(Type::UnsignedBigInt64())) {
85 return Truncation::Any(IdentifyZeros::kDistinguishZeros);
86 }
87 } else if (type.Is(TypeCache::Get()->kSafeIntegerOrMinusZero)) {
88 return Truncation::Any(identify_zeros);
89 }
90 return Truncation(Truncation::TruncationKind::kWord64, identify_zeros);
91 }
92
93 default:
94 // TODO(nicohartmann): Support remaining truncations.
95 UNREACHABLE();
96 }
97 }
98
VisitNode(Node * node,OperationTyper & op_typer)99 void SimplifiedLoweringVerifier::VisitNode(Node* node,
100 OperationTyper& op_typer) {
101 switch (node->opcode()) {
102 case IrOpcode::kInt64Constant: {
103 // Constants might be untyped, because they are cached in the graph and
104 // used in different contexts such that no single type can be assigned.
105 // Their type is provided by an introduced TypeGuard where necessary.
106 break;
107 }
108 case IrOpcode::kCheckedFloat64ToInt32: {
109 Type input_type = InputType(node, 0);
110 DCHECK(input_type.Is(Type::Number()));
111
112 const auto& p = CheckMinusZeroParametersOf(node->op());
113 if (p.mode() == CheckForMinusZeroMode::kCheckForMinusZero) {
114 // Remove -0 from input_type.
115 input_type =
116 Type::Intersect(input_type, Type::Signed32(), graph_zone());
117 } else {
118 input_type = Type::Intersect(input_type, Type::Signed32OrMinusZero(),
119 graph_zone());
120 }
121 CheckAndSet(node, input_type, Truncation::Word32());
122 break;
123 }
124 case IrOpcode::kInt32Add: {
125 Type output_type =
126 op_typer.NumberAdd(InputType(node, 0), InputType(node, 1));
127 Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
128 InputTruncation(node, 1),
129 Truncation::Word32());
130 CHECK(IsModuloTruncation(output_trunc));
131 CheckAndSet(node, output_type, output_trunc);
132 break;
133 }
134 case IrOpcode::kInt32Sub: {
135 Type output_type =
136 op_typer.NumberSubtract(InputType(node, 0), InputType(node, 1));
137 Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
138 InputTruncation(node, 1),
139 Truncation::Word32());
140 CHECK(IsModuloTruncation(output_trunc));
141 CheckAndSet(node, output_type, output_trunc);
142 break;
143 }
144 case IrOpcode::kChangeInt31ToTaggedSigned: {
145 // ChangeInt31ToTaggedSigned is not truncating any values, so we can
146 // simply forward input.
147 CheckAndSet(node, InputType(node, 0), InputTruncation(node, 0));
148 break;
149 }
150 case IrOpcode::kChangeInt32ToTagged: {
151 // ChangeInt32ToTagged is not truncating any values, so we can simply
152 // forward input.
153 CheckAndSet(node, InputType(node, 0), InputTruncation(node, 0));
154 break;
155 }
156 case IrOpcode::kInt64Add: {
157 Type left_type = InputType(node, 0);
158 Type right_type = InputType(node, 1);
159
160 Type output_type;
161 if (left_type.Is(Type::BigInt()) && right_type.Is(Type::BigInt())) {
162 // BigInt x BigInt -> BigInt
163 output_type = op_typer.BigIntAdd(left_type, right_type);
164 } else if (left_type.Is(Type::Number()) &&
165 right_type.Is(Type::Number())) {
166 // Number x Number -> Number
167 output_type = op_typer.NumberAdd(left_type, right_type);
168 } else {
169 // Invalid type combination.
170 std::ostringstream left_str, right_str;
171 left_type.PrintTo(left_str);
172 right_type.PrintTo(right_str);
173 FATAL(
174 "SimplifiedLoweringVerifierError: invalid combination of input "
175 "types "
176 "%s and %s for node #%d:%s",
177 left_str.str().c_str(), right_str.str().c_str(), node->id(),
178 node->op()->mnemonic());
179 }
180
181 Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
182 InputTruncation(node, 1),
183 Truncation::Word64());
184 CHECK(IsModuloTruncation(output_trunc));
185 CheckAndSet(node, output_type, output_trunc);
186 break;
187 }
188 case IrOpcode::kChangeInt32ToInt64: {
189 // ChangeInt32ToInt64 is not truncating any values, so we can simply
190 // forward input.
191 CheckAndSet(node, InputType(node, 0), InputTruncation(node, 0));
192 break;
193 }
194 case IrOpcode::kDeadValue: {
195 CheckAndSet(node, Type::None(), Truncation::Any());
196 break;
197 }
198 case IrOpcode::kTypeGuard: {
199 Type output_type = op_typer.TypeTypeGuard(node->op(), InputType(node, 0));
200 // TypeGuard has no effect on trunction, but the restricted type may help
201 // generalize it.
202 CheckAndSet(node, output_type, InputTruncation(node, 0));
203 break;
204 }
205 case IrOpcode::kTruncateBigIntToWord64: {
206 Type input_type = InputType(node, 0);
207 CHECK(input_type.Is(Type::BigInt()));
208 CHECK(Truncation::Word64().IsLessGeneralThan(InputTruncation(node, 0)));
209 CheckAndSet(node, input_type, Truncation::Word64());
210 break;
211 }
212 case IrOpcode::kChangeTaggedSignedToInt64: {
213 Type input_type = InputType(node, 0);
214 CHECK(input_type.Is(Type::Number()));
215 Truncation output_trunc = LeastGeneralTruncation(InputTruncation(node, 0),
216 Truncation::Word64());
217 CheckAndSet(node, input_type, output_trunc);
218 break;
219 }
220 case IrOpcode::kCheckBigInt: {
221 Type input_type = InputType(node, 0);
222 input_type = Type::Intersect(input_type, Type::BigInt(), graph_zone());
223 CheckAndSet(node, input_type, InputTruncation(node, 0));
224 break;
225 }
226 case IrOpcode::kReturn: {
227 const int return_value_count = ValueInputCountOfReturn(node->op());
228 for (int i = 0; i < return_value_count; ++i) {
229 Type input_type = InputType(node, 1 + i);
230 Truncation input_trunc = InputTruncation(node, 1 + i);
231 input_trunc = GeneralizeTruncation(input_trunc, input_type);
232 // No values must be lost due to truncation.
233 CHECK_EQ(input_trunc, Truncation::Any());
234 }
235 break;
236 }
237 case IrOpcode::kSLVerifierHint: {
238 Type output_type = InputType(node, 0);
239 Truncation output_trunc = InputTruncation(node, 0);
240 const auto& p = SLVerifierHintParametersOf(node->op());
241
242 if (const Operator* semantics = p.semantics()) {
243 switch (semantics->opcode()) {
244 case IrOpcode::kPlainPrimitiveToNumber:
245 output_type = op_typer.ToNumber(output_type);
246 break;
247 default:
248 UNREACHABLE();
249 }
250 CheckType(node, output_type);
251 }
252
253 if (p.override_output_type()) {
254 output_type = *p.override_output_type();
255 }
256
257 SetType(node, output_type);
258 SetTruncation(node, GeneralizeTruncation(output_trunc, output_type));
259 break;
260 }
261
262 default:
263 // TODO(nicohartmann): Support remaining operators.
264 break;
265 }
266 }
267
268 } // namespace compiler
269 } // namespace internal
270 } // namespace v8
271