1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 *
4 * Copyright 2021 Mozilla Foundation
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "wasm/WasmInitExpr.h"
20
21 #include "mozilla/Maybe.h"
22
23 #include "wasm/WasmInstance.h"
24 #include "wasm/WasmOpIter.h"
25 #include "wasm/WasmSerialize.h"
26 #include "wasm/WasmValidate.h"
27
28 using namespace js;
29 using namespace js::wasm;
30
ValidateInitExpr(Decoder & d,ModuleEnvironment * env,ValType expected,Maybe<LitVal> * literal)31 static bool ValidateInitExpr(Decoder& d, ModuleEnvironment* env,
32 ValType expected, Maybe<LitVal>* literal) {
33 ValidatingOpIter iter(*env, d, ValidatingOpIter::InitExpr);
34
35 if (!iter.startInitExpr(expected)) {
36 return false;
37 }
38
39 // Perform trivial constant recovery, this is done so that codegen may
40 // generate optimal code for global.get on immutable globals with simple
41 // initializers.
42 //
43 // We simply update the last seen literal value while validating an
44 // instruction with a literal value, and clear the literal value when
45 // validating an instruction with a dynamic value. The last value is the
46 // literal for this init expressions, if any. This is correct because there
47 // are no drops or control flow allowed in init expressions.
48 *literal = Nothing();
49
50 while (true) {
51 OpBytes op;
52 if (!iter.readOp(&op)) {
53 return false;
54 }
55
56 #ifdef ENABLE_WASM_EXTENDED_CONST
57 Nothing nothing;
58 #endif
59 NothingVector nothings{};
60 ResultType unusedType;
61
62 switch (op.b0) {
63 case uint16_t(Op::End): {
64 LabelKind kind;
65 if (!iter.readEnd(&kind, &unusedType, ¬hings, ¬hings)) {
66 return false;
67 }
68 MOZ_ASSERT(kind == LabelKind::Body);
69 iter.popEnd();
70 if (iter.controlStackEmpty()) {
71 return iter.endInitExpr();
72 }
73 break;
74 }
75 case uint16_t(Op::GetGlobal): {
76 uint32_t index;
77 if (!iter.readGetGlobal(&index)) {
78 return false;
79 }
80 *literal = Nothing();
81 break;
82 }
83 case uint16_t(Op::I32Const): {
84 int32_t c;
85 if (!iter.readI32Const(&c)) {
86 return false;
87 }
88 *literal = Some(LitVal(uint32_t(c)));
89 break;
90 }
91 case uint16_t(Op::I64Const): {
92 int64_t c;
93 if (!iter.readI64Const(&c)) {
94 return false;
95 }
96 *literal = Some(LitVal(uint64_t(c)));
97 break;
98 }
99 case uint16_t(Op::F32Const): {
100 float c;
101 if (!iter.readF32Const(&c)) {
102 return false;
103 }
104 *literal = Some(LitVal(c));
105 break;
106 }
107 case uint16_t(Op::F64Const): {
108 double c;
109 if (!iter.readF64Const(&c)) {
110 return false;
111 }
112 *literal = Some(LitVal(c));
113 break;
114 }
115 #ifdef ENABLE_WASM_SIMD
116 case uint16_t(Op::SimdPrefix): {
117 if (!env->v128Enabled()) {
118 return d.fail("v128 not enabled");
119 }
120 if (op.b1 != uint32_t(SimdOp::V128Const)) {
121 return d.fail("unexpected initializer opcode");
122 }
123 V128 c;
124 if (!iter.readV128Const(&c)) {
125 return false;
126 }
127 *literal = Some(LitVal(c));
128 break;
129 }
130 #endif
131 case uint16_t(Op::RefFunc): {
132 uint32_t funcIndex;
133 if (!iter.readRefFunc(&funcIndex)) {
134 return false;
135 }
136 env->declareFuncExported(funcIndex, /* eager */ false,
137 /* canRefFunc */ true);
138 *literal = Nothing();
139 break;
140 }
141 case uint16_t(Op::RefNull): {
142 RefType type;
143 if (!iter.readRefNull(&type)) {
144 return false;
145 }
146 *literal = Some(LitVal(ValType(type)));
147 break;
148 }
149 #ifdef ENABLE_WASM_EXTENDED_CONST
150 case uint16_t(Op::I32Add):
151 case uint16_t(Op::I32Sub):
152 case uint16_t(Op::I32Mul): {
153 if (!env->extendedConstEnabled()) {
154 return d.fail("unexpected initializer opcode");
155 }
156 if (!iter.readBinary(ValType::I32, ¬hing, ¬hing)) {
157 return false;
158 }
159 *literal = Nothing();
160 break;
161 }
162 case uint16_t(Op::I64Add):
163 case uint16_t(Op::I64Sub):
164 case uint16_t(Op::I64Mul): {
165 if (!env->extendedConstEnabled()) {
166 return d.fail("unexpected initializer opcode");
167 }
168 if (!iter.readBinary(ValType::I64, ¬hing, ¬hing)) {
169 return false;
170 }
171 *literal = Nothing();
172 break;
173 }
174 #endif
175 default: {
176 return d.fail("unexpected initializer opcode");
177 }
178 }
179 }
180 }
181
182 class MOZ_STACK_CLASS InitExprInterpreter {
183 public:
InitExprInterpreter(JSContext * cx,const ValVector & globalImportValues,HandleWasmInstanceObject instanceObj)184 explicit InitExprInterpreter(JSContext* cx,
185 const ValVector& globalImportValues,
186 HandleWasmInstanceObject instanceObj)
187 : features(FeatureArgs::build(cx, FeatureOptions())),
188 stack(cx),
189 globalImportValues(globalImportValues),
190 instanceObj(cx, instanceObj) {}
191
192 bool evaluate(Decoder& d);
193
result()194 Val result() {
195 MOZ_ASSERT(stack.length() == 1);
196 return stack.popCopy();
197 }
198
199 private:
200 FeatureArgs features;
201 RootedValVector stack;
202 const ValVector& globalImportValues;
203 RootedWasmInstanceObject instanceObj;
204
pushI32(int32_t c)205 bool pushI32(int32_t c) { return stack.append(Val(uint32_t(c))); }
pushI64(int64_t c)206 bool pushI64(int64_t c) { return stack.append(Val(uint64_t(c))); }
pushF32(float c)207 bool pushF32(float c) { return stack.append(Val(c)); }
pushF64(double c)208 bool pushF64(double c) { return stack.append(Val(c)); }
pushV128(V128 c)209 bool pushV128(V128 c) { return stack.append(Val(c)); }
pushRef(ValType type,AnyRef ref)210 bool pushRef(ValType type, AnyRef ref) {
211 return stack.append(Val(type, ref));
212 }
pushFuncRef(FuncRef ref)213 bool pushFuncRef(FuncRef ref) {
214 return stack.append(Val(RefType::func(), ref));
215 }
216
217 #ifdef ENABLE_WASM_EXTENDED_CONST
popI32()218 int32_t popI32() {
219 uint32_t result = stack.back().i32();
220 stack.popBack();
221 return int32_t(result);
222 }
popI64()223 int64_t popI64() {
224 uint64_t result = stack.back().i64();
225 stack.popBack();
226 return int64_t(result);
227 }
228 #endif
229
evalGetGlobal(uint32_t index)230 bool evalGetGlobal(uint32_t index) {
231 return stack.append(globalImportValues[index]);
232 }
evalI32Const(int32_t c)233 bool evalI32Const(int32_t c) { return pushI32(c); }
evalI64Const(int64_t c)234 bool evalI64Const(int64_t c) { return pushI64(c); }
evalF32Const(float c)235 bool evalF32Const(float c) { return pushF32(c); }
evalF64Const(double c)236 bool evalF64Const(double c) { return pushF64(c); }
evalV128Const(V128 c)237 bool evalV128Const(V128 c) { return pushV128(c); }
evalRefFunc(uint32_t funcIndex)238 bool evalRefFunc(uint32_t funcIndex) {
239 void* fnref = Instance::refFunc(&instanceObj->instance(), funcIndex);
240 if (fnref == AnyRef::invalid().forCompiledCode()) {
241 return false; // OOM, which has already been reported.
242 }
243 return pushFuncRef(FuncRef::fromCompiledCode(fnref));
244 }
evalRefNull(RefType type)245 bool evalRefNull(RefType type) { return pushRef(type, AnyRef::null()); }
246 #ifdef ENABLE_WASM_EXTENDED_CONST
evalI32Add()247 bool evalI32Add() {
248 uint32_t a = popI32();
249 uint32_t b = popI32();
250 pushI32(a + b);
251 return true;
252 }
evalI32Sub()253 bool evalI32Sub() {
254 uint32_t a = popI32();
255 uint32_t b = popI32();
256 pushI32(a - b);
257 return true;
258 }
evalI32Mul()259 bool evalI32Mul() {
260 uint32_t a = popI32();
261 uint32_t b = popI32();
262 pushI32(a * b);
263 return true;
264 }
evalI64Add()265 bool evalI64Add() {
266 uint64_t a = popI64();
267 uint64_t b = popI64();
268 pushI64(a + b);
269 return true;
270 }
evalI64Sub()271 bool evalI64Sub() {
272 uint64_t a = popI64();
273 uint64_t b = popI64();
274 pushI64(a - b);
275 return true;
276 }
evalI64Mul()277 bool evalI64Mul() {
278 uint64_t a = popI64();
279 uint64_t b = popI64();
280 pushI64(a * b);
281 return true;
282 }
283 #endif
284 };
285
evaluate(Decoder & d)286 bool InitExprInterpreter::evaluate(Decoder& d) {
287 #define CHECK(c) \
288 if (!(c)) return false; \
289 break
290
291 while (true) {
292 OpBytes op;
293 if (!d.readOp(&op)) {
294 return false;
295 }
296
297 switch (op.b0) {
298 case uint16_t(Op::End): {
299 return true;
300 }
301 case uint16_t(Op::GetGlobal): {
302 uint32_t index;
303 if (!d.readGetGlobal(&index)) {
304 return false;
305 }
306 CHECK(evalGetGlobal(index));
307 }
308 case uint16_t(Op::I32Const): {
309 int32_t c;
310 if (!d.readI32Const(&c)) {
311 return false;
312 }
313 CHECK(evalI32Const(c));
314 }
315 case uint16_t(Op::I64Const): {
316 int64_t c;
317 if (!d.readI64Const(&c)) {
318 return false;
319 }
320 CHECK(evalI64Const(c));
321 }
322 case uint16_t(Op::F32Const): {
323 float c;
324 if (!d.readF32Const(&c)) {
325 return false;
326 }
327 CHECK(evalF32Const(c));
328 }
329 case uint16_t(Op::F64Const): {
330 double c;
331 if (!d.readF64Const(&c)) {
332 return false;
333 }
334 CHECK(evalF64Const(c));
335 }
336 #ifdef ENABLE_WASM_SIMD
337 case uint16_t(Op::SimdPrefix): {
338 MOZ_RELEASE_ASSERT(op.b1 == uint32_t(SimdOp::V128Const));
339 V128 c;
340 if (!d.readV128Const(&c)) {
341 return false;
342 }
343 CHECK(evalV128Const(c));
344 }
345 #endif
346 case uint16_t(Op::RefFunc): {
347 uint32_t funcIndex;
348 if (!d.readRefFunc(&funcIndex)) {
349 return false;
350 }
351 CHECK(evalRefFunc(funcIndex));
352 }
353 case uint16_t(Op::RefNull): {
354 RefType type;
355 if (!d.readRefNull(features, &type)) {
356 return false;
357 }
358 CHECK(evalRefNull(type));
359 }
360 #ifdef ENABLE_WASM_EXTENDED_CONST
361 case uint16_t(Op::I32Add): {
362 if (!d.readBinary()) {
363 return false;
364 }
365 CHECK(evalI32Add());
366 }
367 case uint16_t(Op::I32Sub): {
368 if (!d.readBinary()) {
369 return false;
370 }
371 CHECK(evalI32Sub());
372 }
373 case uint16_t(Op::I32Mul): {
374 if (!d.readBinary()) {
375 return false;
376 }
377 CHECK(evalI32Mul());
378 }
379 case uint16_t(Op::I64Add): {
380 if (!d.readBinary()) {
381 return false;
382 }
383 CHECK(evalI64Add());
384 }
385 case uint16_t(Op::I64Sub): {
386 if (!d.readBinary()) {
387 return false;
388 }
389 CHECK(evalI64Sub());
390 }
391 case uint16_t(Op::I64Mul): {
392 if (!d.readBinary()) {
393 return false;
394 }
395 CHECK(evalI64Mul());
396 }
397 #endif
398 default: {
399 MOZ_CRASH();
400 }
401 }
402 }
403
404 #undef CHECK
405 }
406
decodeAndValidate(Decoder & d,ModuleEnvironment * env,ValType expected,InitExpr * expr)407 bool InitExpr::decodeAndValidate(Decoder& d, ModuleEnvironment* env,
408 ValType expected, InitExpr* expr) {
409 Maybe<LitVal> literal = Nothing();
410 const uint8_t* exprStart = d.currentPosition();
411 if (!ValidateInitExpr(d, env, expected, &literal)) {
412 return false;
413 }
414 const uint8_t* exprEnd = d.currentPosition();
415 size_t exprSize = exprEnd - exprStart;
416
417 MOZ_ASSERT(expr->kind_ == InitExprKind::None);
418 expr->type_ = expected;
419
420 if (literal) {
421 expr->kind_ = InitExprKind::Literal;
422 expr->literal_ = *literal;
423 return true;
424 }
425
426 expr->kind_ = InitExprKind::Variable;
427 return expr->bytecode_.reserve(exprSize) &&
428 expr->bytecode_.append(exprStart, exprEnd);
429 }
430
evaluate(JSContext * cx,const ValVector & globalImportValues,HandleWasmInstanceObject instanceObj,MutableHandleVal result) const431 bool InitExpr::evaluate(JSContext* cx, const ValVector& globalImportValues,
432 HandleWasmInstanceObject instanceObj,
433 MutableHandleVal result) const {
434 MOZ_ASSERT(kind_ != InitExprKind::None);
435
436 if (isLiteral()) {
437 result.set(Val(literal()));
438 return true;
439 }
440
441 UniqueChars error;
442 Decoder d(bytecode_.begin(), bytecode_.end(), 0, &error);
443 InitExprInterpreter interp(cx, globalImportValues, instanceObj);
444 if (!interp.evaluate(d)) {
445 // This expression should have been validated already. So we should only be
446 // able to OOM, which is reported by having no error message.
447 MOZ_RELEASE_ASSERT(!error);
448 return false;
449 }
450
451 result.set(interp.result());
452 return true;
453 }
454
clone(const InitExpr & src)455 bool InitExpr::clone(const InitExpr& src) {
456 kind_ = src.kind_;
457 MOZ_ASSERT(bytecode_.empty());
458 if (!bytecode_.appendAll(src.bytecode_)) {
459 return false;
460 }
461 literal_ = src.literal_;
462 type_ = src.type_;
463 return true;
464 }
465
serializedSize() const466 size_t InitExpr::serializedSize() const {
467 size_t size = sizeof(kind_) + sizeof(type_);
468 switch (kind_) {
469 case InitExprKind::Literal:
470 size += sizeof(literal_);
471 break;
472 case InitExprKind::Variable:
473 size += SerializedPodVectorSize(bytecode_);
474 break;
475 default:
476 MOZ_CRASH();
477 }
478 return size;
479 }
480
serialize(uint8_t * cursor) const481 uint8_t* InitExpr::serialize(uint8_t* cursor) const {
482 cursor = WriteBytes(cursor, &kind_, sizeof(kind_));
483 cursor = WriteBytes(cursor, &type_, sizeof(type_));
484 switch (kind_) {
485 case InitExprKind::Literal:
486 cursor = WriteBytes(cursor, &literal_, sizeof(literal_));
487 break;
488 case InitExprKind::Variable:
489 cursor = SerializePodVector(cursor, bytecode_);
490 break;
491 default:
492 MOZ_CRASH();
493 }
494 return cursor;
495 }
496
deserialize(const uint8_t * cursor)497 const uint8_t* InitExpr::deserialize(const uint8_t* cursor) {
498 if (!(cursor = ReadBytes(cursor, &kind_, sizeof(kind_))) ||
499 !(cursor = ReadBytes(cursor, &type_, sizeof(type_)))) {
500 return nullptr;
501 }
502 switch (kind_) {
503 case InitExprKind::Literal:
504 cursor = ReadBytes(cursor, &literal_, sizeof(literal_));
505 break;
506 case InitExprKind::Variable:
507 cursor = DeserializePodVector(cursor, &bytecode_);
508 break;
509 default:
510 MOZ_CRASH();
511 }
512 return cursor;
513 }
514
sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const515 size_t InitExpr::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
516 return bytecode_.sizeOfExcludingThis(mallocSizeOf);
517 }
518