tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WasmBuiltinModule.cpp (16204B)


      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/WasmBuiltinModule.h"
     20 
     21 #include "util/Text.h"
     22 #include "vm/GlobalObject.h"
     23 
     24 #include "wasm/WasmBuiltinModuleGenerated.h"
     25 #include "wasm/WasmFeatures.h"
     26 #include "wasm/WasmGenerator.h"
     27 #include "wasm/WasmJS.h"
     28 #include "wasm/WasmModule.h"
     29 #include "wasm/WasmOpIter.h"
     30 #include "wasm/WasmStaticTypeDefs.h"
     31 #include "wasm/WasmValidate.h"
     32 
     33 using namespace js;
     34 using namespace js::wasm;
     35 
     36 using mozilla::Maybe;
     37 using mozilla::Nothing;
     38 using mozilla::Some;
     39 
     40 BuiltinModuleFuncs* BuiltinModuleFuncs::singleton_ = nullptr;
     41 
     42 [[nodiscard]] bool BuiltinModuleFunc::init(
     43    const RefPtr<TypeContext>& types, mozilla::Span<const ValType> params,
     44    Maybe<ValType> result, bool usesMemory, const SymbolicAddressSignature* sig,
     45    BuiltinInlineOp inlineOp, const char* exportName) {
     46  // This builtin must not have been initialized yet.
     47  MOZ_ASSERT(!recGroup_);
     48 
     49  // Initialize the basic fields
     50  exportName_ = exportName;
     51  sig_ = sig;
     52  usesMemory_ = usesMemory;
     53  inlineOp_ = inlineOp;
     54 
     55  // Create a function type for the given params and result
     56  ValTypeVector paramVec;
     57  if (!paramVec.append(params.data(), params.data() + params.size())) {
     58    return false;
     59  }
     60  ValTypeVector resultVec;
     61  if (result.isSome() && !resultVec.append(*result)) {
     62    return false;
     63  }
     64  const TypeDef* typeDef =
     65      types->addType(FuncType(std::move(paramVec), std::move(resultVec)));
     66  if (!typeDef) {
     67    return false;
     68  }
     69  recGroup_ = &typeDef->recGroup();
     70  return true;
     71 }
     72 
     73 bool BuiltinModuleFuncs::init() {
     74  singleton_ = js_new<BuiltinModuleFuncs>();
     75  if (!singleton_) {
     76    return false;
     77  }
     78 
     79  RefPtr<TypeContext> types = js_new<TypeContext>();
     80  if (!types) {
     81    return false;
     82  }
     83 
     84 #define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, needs_thunk, entry,   \
     85                           uses_memory, inline_op, ...)                        \
     86  const ValType op##Params[] =                                                 \
     87      DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_##op;                         \
     88  Maybe<ValType> op##Result = DECLARE_BUILTIN_MODULE_FUNC_RESULT_VALTYPE_##op; \
     89  if (!singleton_->funcs_[BuiltinModuleFuncId::op].init(                       \
     90          types, mozilla::Span<const ValType>(op##Params), op##Result,         \
     91          uses_memory, &SASig##sa_name, inline_op, export)) {                  \
     92    return false;                                                              \
     93  }
     94  FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
     95 #undef VISIT_BUILTIN_FUNC
     96 
     97  return true;
     98 }
     99 
    100 void BuiltinModuleFuncs::destroy() {
    101  if (!singleton_) {
    102    return;
    103  }
    104  js_delete(singleton_);
    105  singleton_ = nullptr;
    106 }
    107 
    108 bool EncodeFuncBody(const BuiltinModuleFunc& builtinModuleFunc,
    109                    BuiltinModuleFuncId id, Bytes* body) {
    110  Encoder encoder(*body);
    111  if (!EncodeLocalEntries(encoder, ValTypeVector())) {
    112    return false;
    113  }
    114  const FuncType* funcType = builtinModuleFunc.funcType();
    115  for (uint32_t i = 0; i < funcType->args().length(); i++) {
    116    if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(i)) {
    117      return false;
    118    }
    119  }
    120  if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc)) {
    121    return false;
    122  }
    123  if (!encoder.writeVarU32(uint32_t(id))) {
    124    return false;
    125  }
    126  return encoder.writeOp(Op::End);
    127 }
    128 
    129 // Descriptor of how a builtin should use memory.
    130 struct BuiltinMemory {
    131  // Whether the memory is shared or not.
    132  Shareable shared;
    133  // Optional import name for the memory. If not provided, will fall back to
    134  // "" "memory" as the import name.
    135  const Import* import;
    136 
    137  BuiltinMemory(Shareable shared, const Import* import)
    138      : shared(shared), import(import) {}
    139 };
    140 
    141 bool CompileBuiltinModule(JSContext* cx,
    142                          const mozilla::Span<BuiltinModuleFuncId> ids,
    143                          mozilla::Maybe<BuiltinMemory> memory,
    144                          MutableHandle<WasmModuleObject*> result) {
    145  // Create the options manually, enabling intrinsics
    146  FeatureOptions featureOptions;
    147  featureOptions.isBuiltinModule = true;
    148 
    149  // Initialize the compiler environment, choosing the best tier possible
    150  SharedCompileArgs compileArgs = CompileArgs::buildAndReport(
    151      cx, ScriptedCaller(), featureOptions, /* reportOOM */ true);
    152  if (!compileArgs) {
    153    return false;
    154  }
    155  CompilerEnvironment compilerEnv(
    156      CompileMode::Once, IonAvailable(cx) ? Tier::Optimized : Tier::Baseline,
    157      DebugEnabled::False);
    158  compilerEnv.computeParameters();
    159 
    160  // Build a module metadata struct
    161  MutableModuleMetadata moduleMeta = js_new<ModuleMetadata>();
    162  if (!moduleMeta || !moduleMeta->init(*compileArgs)) {
    163    ReportOutOfMemory(cx);
    164    return false;
    165  }
    166  MutableCodeMetadata codeMeta = moduleMeta->codeMeta;
    167 
    168  if (memory.isSome()) {
    169    // Add (import (memory 0)) using the specified import name, or else fall
    170    // back to "" "memory" if no import was specified.
    171    CacheableName moduleString;
    172    CacheableName fieldString;
    173    if (!memory->import) {
    174      // Keep moduleString empty, using "memory" for the fieldString
    175      if (!CacheableName::fromUTF8Chars("memory", &fieldString)) {
    176        ReportOutOfMemory(cx);
    177        return false;
    178      }
    179    } else {
    180      // The provided import name must be a memory import.
    181      MOZ_ASSERT(memory->import->kind == DefinitionKind::Memory);
    182      if (!memory->import->module.clone(&moduleString) ||
    183          !memory->import->field.clone(&fieldString)) {
    184        ReportOutOfMemory(cx);
    185        return false;
    186      }
    187    }
    188 
    189    if (!moduleMeta->imports.append(Import(std::move(moduleString),
    190                                           std::move(fieldString),
    191                                           DefinitionKind::Memory))) {
    192      ReportOutOfMemory(cx);
    193      return false;
    194    }
    195    if (!codeMeta->memories.append(MemoryDesc(
    196            Limits(0, Nothing(), memory->shared, PageSize::Standard)))) {
    197      ReportOutOfMemory(cx);
    198      return false;
    199    }
    200  }
    201 
    202  // Add (type (func (params ...))) for each func. The function types will
    203  // be deduplicated by the runtime.
    204  for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
    205    const BuiltinModuleFuncId& id = ids[funcIndex];
    206    const BuiltinModuleFunc& builtinModuleFunc =
    207        BuiltinModuleFuncs::getFromId(id);
    208 
    209    SharedRecGroup recGroup = builtinModuleFunc.recGroup();
    210    MOZ_ASSERT(recGroup->numTypes() == 1);
    211    if (!codeMeta->types->addRecGroup(recGroup)) {
    212      ReportOutOfMemory(cx);
    213      return false;
    214    }
    215  }
    216 
    217  // Add all static type defs to the type context so that we can always look up
    218  // their index. This must come after the func types so as not to interfere
    219  // with funcIndex.
    220  if (!StaticTypeDefs::addAllToTypeContext(codeMeta->types)) {
    221    ReportOutOfMemory(cx);
    222    return false;
    223  }
    224 
    225  // Add (func (type $i)) declarations. Do this after all types have been added
    226  // as the function declaration metadata uses pointers into the type vectors
    227  // that must be stable.
    228  for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
    229    FuncDesc decl(funcIndex);
    230    if (!codeMeta->funcs.append(decl)) {
    231      ReportOutOfMemory(cx);
    232      return false;
    233    }
    234    codeMeta->funcs[funcIndex].declareFuncExported(/* eager */ true,
    235                                                   /* canRefFunc */ true);
    236  }
    237 
    238  // Add (export "$name" (func $i)) declarations.
    239  for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
    240    const BuiltinModuleFunc& builtinModuleFunc =
    241        BuiltinModuleFuncs::getFromId(ids[funcIndex]);
    242 
    243    CacheableName exportName;
    244    if (!CacheableName::fromUTF8Chars(builtinModuleFunc.exportName(),
    245                                      &exportName) ||
    246        !moduleMeta->exports.append(Export(std::move(exportName), funcIndex,
    247                                           DefinitionKind::Function))) {
    248      ReportOutOfMemory(cx);
    249      return false;
    250    }
    251  }
    252 
    253  if (!moduleMeta->prepareForCompile(compilerEnv.mode())) {
    254    return false;
    255  }
    256 
    257  // Compile the module functions
    258  UniqueChars error;
    259  ModuleGenerator mg(*codeMeta, compilerEnv, compilerEnv.initialState(),
    260                     nullptr, &error, nullptr);
    261  if (!mg.initializeCompleteTier()) {
    262    ReportOutOfMemory(cx);
    263    return false;
    264  }
    265 
    266  // Prepare and compile function bodies
    267  Vector<Bytes, 1, SystemAllocPolicy> bodies;
    268  if (!bodies.reserve(ids.size())) {
    269    ReportOutOfMemory(cx);
    270    return false;
    271  }
    272  uint32_t funcBytecodeOffset = CallSite::FIRST_VALID_BYTECODE_OFFSET;
    273  for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
    274    BuiltinModuleFuncId id = ids[funcIndex];
    275    const BuiltinModuleFunc& builtinModuleFunc =
    276        BuiltinModuleFuncs::getFromId(ids[funcIndex]);
    277 
    278    // Compilation may be done using other threads, ModuleGenerator requires
    279    // that function bodies live until after finishFuncDefs().
    280    bodies.infallibleAppend(Bytes());
    281    Bytes& bytecode = bodies.back();
    282 
    283    // Encode function body that will call the builtinModuleFunc using our
    284    // builtin opcode, and launch a compile task
    285    if (!EncodeFuncBody(builtinModuleFunc, id, &bytecode) ||
    286        !mg.compileFuncDef(funcIndex, funcBytecodeOffset, bytecode.begin(),
    287                           bytecode.begin() + bytecode.length())) {
    288      // This must be an OOM and will be reported by the caller
    289      MOZ_ASSERT(!error);
    290      ReportOutOfMemory(cx);
    291      return false;
    292    }
    293    funcBytecodeOffset += bytecode.length();
    294  }
    295 
    296  // Finish and block on function compilation
    297  if (!mg.finishFuncDefs()) {
    298    // This must be an OOM and will be reported by the caller
    299    MOZ_ASSERT(!error);
    300    ReportOutOfMemory(cx);
    301    return false;
    302  }
    303 
    304  // Finish the module
    305  SharedModule module = mg.finishModule(BytecodeBufferOrSource(), *moduleMeta,
    306                                        /*maybeCompleteTier2Listener=*/nullptr);
    307  if (!module) {
    308    ReportOutOfMemory(cx);
    309    return false;
    310  }
    311 
    312  // Create a WasmModuleObject for the module, and return it
    313  RootedObject proto(
    314      cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmModule));
    315  if (!proto) {
    316    ReportOutOfMemory(cx);
    317    return false;
    318  }
    319  result.set(WasmModuleObject::create(cx, *module, proto));
    320  return !!result;
    321 }
    322 
    323 static BuiltinModuleFuncId SelfTestFuncs[] = {BuiltinModuleFuncId::I8VecMul};
    324 
    325 #ifdef ENABLE_WASM_MOZ_INTGEMM
    326 static BuiltinModuleFuncId IntGemmFuncs[] = {
    327    BuiltinModuleFuncId::I8PrepareB,
    328    BuiltinModuleFuncId::I8PrepareBFromTransposed,
    329    BuiltinModuleFuncId::I8PrepareBFromQuantizedTransposed,
    330    BuiltinModuleFuncId::I8PrepareA,
    331    BuiltinModuleFuncId::I8PrepareBias,
    332    BuiltinModuleFuncId::I8MultiplyAndAddBias,
    333    BuiltinModuleFuncId::I8SelectColumnsOfB};
    334 // Name chosen to maintain compatibility with existing wasm files, so nothing
    335 // needs to be rebuilt.
    336 static const char* IntGemmModuleName = "wasm_gemm";
    337 #endif  // ENABLE_WASM_MOZ_INTGEMM
    338 
    339 static BuiltinModuleFuncId JSStringFuncs[] = {
    340    BuiltinModuleFuncId::StringTest,
    341    BuiltinModuleFuncId::StringCast,
    342    BuiltinModuleFuncId::StringFromCharCodeArray,
    343    BuiltinModuleFuncId::StringIntoCharCodeArray,
    344    BuiltinModuleFuncId::StringFromCharCode,
    345    BuiltinModuleFuncId::StringFromCodePoint,
    346    BuiltinModuleFuncId::StringCharCodeAt,
    347    BuiltinModuleFuncId::StringCodePointAt,
    348    BuiltinModuleFuncId::StringLength,
    349    BuiltinModuleFuncId::StringConcat,
    350    BuiltinModuleFuncId::StringSubstring,
    351    BuiltinModuleFuncId::StringEquals,
    352    BuiltinModuleFuncId::StringCompare};
    353 static const char* JSStringModuleName = "wasm:js-string";
    354 
    355 Maybe<BuiltinModuleId> wasm::ImportMatchesBuiltinModule(
    356    mozilla::Span<const char> importName,
    357    const BuiltinModuleIds& enabledBuiltins) {
    358  if (enabledBuiltins.jsString &&
    359      importName == mozilla::MakeStringSpan(JSStringModuleName)) {
    360    return Some(BuiltinModuleId::JSString);
    361  }
    362  if (enabledBuiltins.jsStringConstants &&
    363      importName ==
    364          mozilla::MakeStringSpan(
    365              enabledBuiltins.jsStringConstantsNamespace->chars.get())) {
    366    return Some(BuiltinModuleId::JSStringConstants);
    367  }
    368 #ifdef ENABLE_WASM_MOZ_INTGEMM
    369  if (enabledBuiltins.intGemm &&
    370      importName == mozilla::MakeStringSpan(IntGemmModuleName)) {
    371    return Some(BuiltinModuleId::IntGemm);
    372  }
    373 #endif  // ENABLE_WASM_MOZ_INTGEMM
    374  // Not supported for implicit instantiation yet
    375  MOZ_RELEASE_ASSERT(!enabledBuiltins.selfTest);
    376  return Nothing();
    377 }
    378 
    379 bool wasm::ImportMatchesBuiltinModuleFunc(mozilla::Span<const char> importName,
    380                                          BuiltinModuleId module,
    381                                          const BuiltinModuleFunc** matchedFunc,
    382                                          BuiltinModuleFuncId* matchedFuncId) {
    383  // Imported string constants don't define any functions
    384  if (module == BuiltinModuleId::JSStringConstants) {
    385    return false;
    386  }
    387 
    388 #ifdef ENABLE_WASM_MOZ_INTGEMM
    389  if (module == BuiltinModuleId::IntGemm) {
    390    for (BuiltinModuleFuncId funcId : IntGemmFuncs) {
    391      const BuiltinModuleFunc& func = BuiltinModuleFuncs::getFromId(funcId);
    392      if (importName == mozilla::MakeStringSpan(func.exportName())) {
    393        *matchedFunc = &func;
    394        *matchedFuncId = funcId;
    395        return true;
    396      }
    397    }
    398  }
    399 #endif
    400 
    401  // That leaves only the wasm:js-string module that defines functions at this
    402  // point, and is supported by implicit instantiation.
    403  MOZ_RELEASE_ASSERT(module == BuiltinModuleId::JSString);
    404  for (BuiltinModuleFuncId funcId : JSStringFuncs) {
    405    const BuiltinModuleFunc& func = BuiltinModuleFuncs::getFromId(funcId);
    406    if (importName == mozilla::MakeStringSpan(func.exportName())) {
    407      *matchedFunc = &func;
    408      *matchedFuncId = funcId;
    409      return true;
    410    }
    411  }
    412  return false;
    413 }
    414 
    415 bool wasm::CompileBuiltinModule(JSContext* cx, BuiltinModuleId module,
    416                                const Import* moduleMemoryImport,
    417                                MutableHandle<WasmModuleObject*> result) {
    418  switch (module) {
    419    case BuiltinModuleId::SelfTest:
    420      return CompileBuiltinModule(
    421          cx, SelfTestFuncs, Some(BuiltinMemory(Shareable::False, nullptr)),
    422          result);
    423 #ifdef ENABLE_WASM_MOZ_INTGEMM
    424    case BuiltinModuleId::IntGemm:
    425      return CompileBuiltinModule(
    426          cx, IntGemmFuncs,
    427          Some(BuiltinMemory(Shareable::False, moduleMemoryImport)), result);
    428 #endif  // ENABLE_WASM_MOZ_INTGEMM
    429    case BuiltinModuleId::JSString:
    430      return CompileBuiltinModule(cx, JSStringFuncs, Nothing(), result);
    431    case BuiltinModuleId::JSStringConstants:
    432      MOZ_CRASH();
    433    default:
    434      MOZ_CRASH();
    435  }
    436 }
    437 
    438 bool wasm::InstantiateBuiltinModule(JSContext* cx, BuiltinModuleId module,
    439                                    const Import* moduleMemoryImport,
    440                                    HandleObject importObj,
    441                                    MutableHandleObject result) {
    442  Rooted<WasmModuleObject*> moduleObj(cx);
    443  if (!CompileBuiltinModule(cx, module, moduleMemoryImport, &moduleObj)) {
    444    ReportOutOfMemory(cx);
    445    return false;
    446  }
    447  Rooted<ImportValues> imports(cx);
    448  if (!wasm::GetImports(cx, moduleObj->module(), importObj,
    449                        imports.address())) {
    450    return false;
    451  }
    452 
    453  Rooted<WasmInstanceObject*> instanceObj(cx);
    454  RootedObject instanceProto(cx);
    455  if (!moduleObj->module().instantiate(cx, *imports.address(), instanceProto,
    456                                       &instanceObj)) {
    457    MOZ_RELEASE_ASSERT(cx->isThrowingOutOfMemory());
    458    return false;
    459  }
    460  result.set(&instanceObj->exportsObj());
    461  return true;
    462 }