/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * * Copyright 2021 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wasm/WasmInitExpr.h" #include "mozilla/Maybe.h" #include "js/Value.h" #include "wasm/WasmGcObject.h" #include "wasm/WasmInstance.h" #include "wasm/WasmOpIter.h" #include "wasm/WasmSerialize.h" #include "wasm/WasmUtility.h" #include "wasm/WasmValidate.h" #include "wasm/WasmInstance-inl.h" using namespace js; using namespace js::wasm; static bool ValidateInitExpr(Decoder& d, ModuleEnvironment* env, ValType expected, uint32_t maxInitializedGlobalsIndexPlus1, Maybe* literal) { ValidatingOpIter iter(*env, d, ValidatingOpIter::InitExpr); if (!iter.startInitExpr(expected, maxInitializedGlobalsIndexPlus1)) { return false; } // Perform trivial constant recovery, this is done so that codegen may // generate optimal code for global.get on immutable globals with simple // initializers. // // We simply update the last seen literal value while validating an // instruction with a literal value, and clear the literal value when // validating an instruction with a dynamic value. The last value is the // literal for this init expressions, if any. This is correct because there // are no drops or control flow allowed in init expressions. *literal = Nothing(); while (true) { OpBytes op; if (!iter.readOp(&op)) { return false; } #if defined(ENABLE_WASM_EXTENDED_CONST) || defined(ENABLE_WASM_GC) Nothing nothing; #endif NothingVector nothings{}; ResultType unusedType; switch (op.b0) { case uint16_t(Op::End): { LabelKind kind; if (!iter.readEnd(&kind, &unusedType, ¬hings, ¬hings)) { return false; } MOZ_ASSERT(kind == LabelKind::Body); iter.popEnd(); if (iter.controlStackEmpty()) { return iter.endInitExpr(); } break; } case uint16_t(Op::GlobalGet): { uint32_t index; if (!iter.readGetGlobal(&index)) { return false; } *literal = Nothing(); break; } case uint16_t(Op::I32Const): { int32_t c; if (!iter.readI32Const(&c)) { return false; } *literal = Some(LitVal(uint32_t(c))); break; } case uint16_t(Op::I64Const): { int64_t c; if (!iter.readI64Const(&c)) { return false; } *literal = Some(LitVal(uint64_t(c))); break; } case uint16_t(Op::F32Const): { float c; if (!iter.readF32Const(&c)) { return false; } *literal = Some(LitVal(c)); break; } case uint16_t(Op::F64Const): { double c; if (!iter.readF64Const(&c)) { return false; } *literal = Some(LitVal(c)); break; } #ifdef ENABLE_WASM_SIMD case uint16_t(Op::SimdPrefix): { if (!env->simdAvailable()) { return d.fail("v128 not enabled"); } if (op.b1 != uint32_t(SimdOp::V128Const)) { return iter.unrecognizedOpcode(&op); } V128 c; if (!iter.readV128Const(&c)) { return false; } *literal = Some(LitVal(c)); break; } #endif case uint16_t(Op::RefFunc): { uint32_t funcIndex; if (!iter.readRefFunc(&funcIndex)) { return false; } env->declareFuncExported(funcIndex, /* eager */ false, /* canRefFunc */ true); *literal = Nothing(); break; } case uint16_t(Op::RefNull): { RefType type; if (!iter.readRefNull(&type)) { return false; } *literal = Some(LitVal(ValType(type))); break; } #ifdef ENABLE_WASM_EXTENDED_CONST case uint16_t(Op::I32Add): case uint16_t(Op::I32Sub): case uint16_t(Op::I32Mul): { if (!env->extendedConstEnabled()) { return iter.unrecognizedOpcode(&op); } if (!iter.readBinary(ValType::I32, ¬hing, ¬hing)) { return false; } *literal = Nothing(); break; } case uint16_t(Op::I64Add): case uint16_t(Op::I64Sub): case uint16_t(Op::I64Mul): { if (!env->extendedConstEnabled()) { return iter.unrecognizedOpcode(&op); } if (!iter.readBinary(ValType::I64, ¬hing, ¬hing)) { return false; } *literal = Nothing(); break; } #endif #ifdef ENABLE_WASM_GC case uint16_t(Op::GcPrefix): { if (!env->gcEnabled()) { return iter.unrecognizedOpcode(&op); } switch (op.b1) { case uint32_t(GcOp::StructNew): { uint32_t typeIndex; if (!iter.readStructNew(&typeIndex, ¬hings)) { return false; } break; } case uint32_t(GcOp::StructNewDefault): { uint32_t typeIndex; if (!iter.readStructNewDefault(&typeIndex)) { return false; } break; } case uint32_t(GcOp::ArrayNew): { uint32_t typeIndex; if (!iter.readArrayNew(&typeIndex, ¬hing, ¬hing)) { return false; } break; } case uint32_t(GcOp::ArrayNewFixed): { uint32_t typeIndex, len; if (!iter.readArrayNewFixed(&typeIndex, &len, ¬hings)) { return false; } break; } case uint32_t(GcOp::ArrayNewDefault): { uint32_t typeIndex; if (!iter.readArrayNewDefault(&typeIndex, ¬hing)) { return false; } break; } default: { return iter.unrecognizedOpcode(&op); } } *literal = Nothing(); break; } #endif default: { return iter.unrecognizedOpcode(&op); } } } } class MOZ_STACK_CLASS InitExprInterpreter { public: explicit InitExprInterpreter(JSContext* cx, Handle instanceObj) : features(FeatureArgs::build(cx, FeatureOptions())), stack(cx), instanceObj(cx, instanceObj), types(instanceObj->instance().metadata().types) {} bool evaluate(JSContext* cx, Decoder& d); Val result() { MOZ_ASSERT(stack.length() == 1); return stack.popCopy(); } private: FeatureArgs features; RootedValVectorN<48> stack; Rooted instanceObj; SharedTypeContext types; Instance& instance() { return instanceObj->instance(); } [[nodiscard]] bool pushI32(int32_t c) { return stack.append(Val(uint32_t(c))); } [[nodiscard]] bool pushI64(int64_t c) { return stack.append(Val(uint64_t(c))); } [[nodiscard]] bool pushF32(float c) { return stack.append(Val(c)); } [[nodiscard]] bool pushF64(double c) { return stack.append(Val(c)); } [[nodiscard]] bool pushV128(V128 c) { return stack.append(Val(c)); } [[nodiscard]] bool pushRef(ValType type, AnyRef ref) { return stack.append(Val(type, ref)); } [[nodiscard]] bool pushFuncRef(HandleFuncRef ref) { return stack.append(Val(RefType::func(), ref)); } #if defined(ENABLE_WASM_EXTENDED_CONST) || defined(ENABLE_WASM_GC) int32_t popI32() { uint32_t result = stack.back().i32(); stack.popBack(); return int32_t(result); } #endif #ifdef ENABLE_WASM_EXTENDED_CONST int64_t popI64() { uint64_t result = stack.back().i64(); stack.popBack(); return int64_t(result); } #endif bool evalGlobalGet(JSContext* cx, uint32_t index) { RootedVal val(cx); instance().constantGlobalGet(index, &val); return stack.append(val); } bool evalI32Const(int32_t c) { return pushI32(c); } bool evalI64Const(int64_t c) { return pushI64(c); } bool evalF32Const(float c) { return pushF32(c); } bool evalF64Const(double c) { return pushF64(c); } bool evalV128Const(V128 c) { return pushV128(c); } bool evalRefFunc(JSContext* cx, uint32_t funcIndex) { RootedFuncRef func(cx, FuncRef::fromJSFunction(nullptr)); if (!instance().constantRefFunc(funcIndex, &func)) { return false; } return pushFuncRef(func); } bool evalRefNull(RefType type) { return pushRef(type, AnyRef::null()); } #ifdef ENABLE_WASM_EXTENDED_CONST bool evalI32Add() { uint32_t b = popI32(); uint32_t a = popI32(); return pushI32(a + b); } bool evalI32Sub() { uint32_t b = popI32(); uint32_t a = popI32(); return pushI32(a - b); } bool evalI32Mul() { uint32_t b = popI32(); uint32_t a = popI32(); return pushI32(a * b); } bool evalI64Add() { uint64_t b = popI64(); uint64_t a = popI64(); return pushI64(a + b); } bool evalI64Sub() { uint64_t b = popI64(); uint64_t a = popI64(); return pushI64(a - b); } bool evalI64Mul() { uint64_t b = popI64(); uint64_t a = popI64(); return pushI64(a * b); } #endif // ENABLE_WASM_EXTENDED_CONST #ifdef ENABLE_WASM_GC bool evalStructNew(JSContext* cx, uint32_t typeIndex) { const TypeDef& typeDef = instance().metadata().types->type(typeIndex); const StructType& structType = typeDef.structType(); Rooted structObj( cx, instance().constantStructNewDefault(cx, typeIndex)); if (!structObj) { return false; } uint32_t numFields = structType.fields_.length(); for (uint32_t forwardIndex = 0; forwardIndex < numFields; forwardIndex++) { uint32_t reverseIndex = numFields - forwardIndex - 1; const Val& val = stack.back(); structObj->storeVal(val, reverseIndex); stack.popBack(); } return pushRef(RefType::fromTypeDef(&typeDef, false), AnyRef::fromJSObject(structObj)); } bool evalStructNewDefault(JSContext* cx, uint32_t typeIndex) { Rooted structObj( cx, instance().constantStructNewDefault(cx, typeIndex)); if (!structObj) { return false; } const TypeDef& typeDef = instance().metadata().types->type(typeIndex); return pushRef(RefType::fromTypeDef(&typeDef, false), AnyRef::fromJSObject(structObj)); } bool evalArrayNew(JSContext* cx, uint32_t typeIndex) { uint32_t numElements = popI32(); Rooted arrayObj( cx, instance().constantArrayNewDefault(cx, typeIndex, numElements)); if (!arrayObj) { return false; } const Val& val = stack.back(); arrayObj->fillVal(val, 0, numElements); stack.popBack(); const TypeDef& typeDef = instance().metadata().types->type(typeIndex); return pushRef(RefType::fromTypeDef(&typeDef, false), AnyRef::fromJSObject(arrayObj)); } bool evalArrayNewDefault(JSContext* cx, uint32_t typeIndex) { uint32_t numElements = popI32(); Rooted arrayObj( cx, instance().constantArrayNewDefault(cx, typeIndex, numElements)); if (!arrayObj) { return false; } const TypeDef& typeDef = instance().metadata().types->type(typeIndex); return pushRef(RefType::fromTypeDef(&typeDef, false), AnyRef::fromJSObject(arrayObj)); } bool evalArrayNewFixed(JSContext* cx, uint32_t typeIndex, uint32_t numElements) { Rooted arrayObj( cx, instance().constantArrayNewDefault(cx, typeIndex, numElements)); if (!arrayObj) { return false; } for (uint32_t forwardIndex = 0; forwardIndex < numElements; forwardIndex++) { uint32_t reverseIndex = numElements - forwardIndex - 1; const Val& val = stack.back(); arrayObj->storeVal(val, reverseIndex); stack.popBack(); } const TypeDef& typeDef = instance().metadata().types->type(typeIndex); return pushRef(RefType::fromTypeDef(&typeDef, false), AnyRef::fromJSObject(arrayObj)); } #endif // ENABLE_WASM_GC }; bool InitExprInterpreter::evaluate(JSContext* cx, Decoder& d) { #define CHECK(c) \ if (!(c)) return false; \ break while (true) { OpBytes op; if (!d.readOp(&op)) { return false; } switch (op.b0) { case uint16_t(Op::End): { return true; } case uint16_t(Op::GlobalGet): { uint32_t index; if (!d.readGlobalIndex(&index)) { return false; } CHECK(evalGlobalGet(cx, index)); } case uint16_t(Op::I32Const): { int32_t c; if (!d.readI32Const(&c)) { return false; } CHECK(evalI32Const(c)); } case uint16_t(Op::I64Const): { int64_t c; if (!d.readI64Const(&c)) { return false; } CHECK(evalI64Const(c)); } case uint16_t(Op::F32Const): { float c; if (!d.readF32Const(&c)) { return false; } CHECK(evalF32Const(c)); } case uint16_t(Op::F64Const): { double c; if (!d.readF64Const(&c)) { return false; } CHECK(evalF64Const(c)); } #ifdef ENABLE_WASM_SIMD case uint16_t(Op::SimdPrefix): { MOZ_RELEASE_ASSERT(op.b1 == uint32_t(SimdOp::V128Const)); V128 c; if (!d.readV128Const(&c)) { return false; } CHECK(evalV128Const(c)); } #endif case uint16_t(Op::RefFunc): { uint32_t funcIndex; if (!d.readFuncIndex(&funcIndex)) { return false; } CHECK(evalRefFunc(cx, funcIndex)); } case uint16_t(Op::RefNull): { RefType type; if (!d.readRefNull(*types, features, &type)) { return false; } CHECK(evalRefNull(type)); } #ifdef ENABLE_WASM_EXTENDED_CONST case uint16_t(Op::I32Add): { if (!d.readBinary()) { return false; } CHECK(evalI32Add()); } case uint16_t(Op::I32Sub): { if (!d.readBinary()) { return false; } CHECK(evalI32Sub()); } case uint16_t(Op::I32Mul): { if (!d.readBinary()) { return false; } CHECK(evalI32Mul()); } case uint16_t(Op::I64Add): { if (!d.readBinary()) { return false; } CHECK(evalI64Add()); } case uint16_t(Op::I64Sub): { if (!d.readBinary()) { return false; } CHECK(evalI64Sub()); } case uint16_t(Op::I64Mul): { if (!d.readBinary()) { return false; } CHECK(evalI64Mul()); } #endif #ifdef ENABLE_WASM_GC case uint16_t(Op::GcPrefix): { switch (op.b1) { case uint32_t(GcOp::StructNew): { uint32_t typeIndex; if (!d.readTypeIndex(&typeIndex)) { return false; } CHECK(evalStructNew(cx, typeIndex)); } case uint32_t(GcOp::StructNewDefault): { uint32_t typeIndex; if (!d.readTypeIndex(&typeIndex)) { return false; } CHECK(evalStructNewDefault(cx, typeIndex)); } case uint32_t(GcOp::ArrayNew): { uint32_t typeIndex; if (!d.readTypeIndex(&typeIndex)) { return false; } CHECK(evalArrayNew(cx, typeIndex)); } case uint32_t(GcOp::ArrayNewFixed): { uint32_t typeIndex, len; if (!d.readTypeIndex(&typeIndex)) { return false; } if (!d.readVarU32(&len)) { return false; } CHECK(evalArrayNewFixed(cx, typeIndex, len)); } case uint32_t(GcOp::ArrayNewDefault): { uint32_t typeIndex; if (!d.readTypeIndex(&typeIndex)) { return false; } CHECK(evalArrayNewDefault(cx, typeIndex)); } default: { MOZ_CRASH(); } } break; } #endif default: { MOZ_CRASH(); } } } #undef CHECK } bool InitExpr::decodeAndValidate(Decoder& d, ModuleEnvironment* env, ValType expected, uint32_t maxInitializedGlobalsIndexPlus1, InitExpr* expr) { Maybe literal = Nothing(); const uint8_t* exprStart = d.currentPosition(); if (!ValidateInitExpr(d, env, expected, maxInitializedGlobalsIndexPlus1, &literal)) { return false; } const uint8_t* exprEnd = d.currentPosition(); size_t exprSize = exprEnd - exprStart; MOZ_ASSERT(expr->kind_ == InitExprKind::None); expr->type_ = expected; if (literal) { expr->kind_ = InitExprKind::Literal; expr->literal_ = *literal; return true; } expr->kind_ = InitExprKind::Variable; return expr->bytecode_.reserve(exprSize) && expr->bytecode_.append(exprStart, exprEnd); } bool InitExpr::evaluate(JSContext* cx, Handle instanceObj, MutableHandleVal result) const { MOZ_ASSERT(kind_ != InitExprKind::None); if (isLiteral()) { result.set(Val(literal())); return true; } UniqueChars error; Decoder d(bytecode_.begin(), bytecode_.end(), 0, &error); InitExprInterpreter interp(cx, instanceObj); if (!interp.evaluate(cx, d)) { // This expression should have been validated already. So we should only be // able to OOM, which is reported by having no error message. MOZ_RELEASE_ASSERT(!error); return false; } result.set(interp.result()); return true; } bool InitExpr::clone(const InitExpr& src) { kind_ = src.kind_; MOZ_ASSERT(bytecode_.empty()); if (!bytecode_.appendAll(src.bytecode_)) { return false; } literal_ = src.literal_; type_ = src.type_; return true; } size_t InitExpr::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return bytecode_.sizeOfExcludingThis(mallocSizeOf); }