no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / js / src / wasm / WasmBuiltinModule.cpp
blob044591224e26ccf8772960f14b30f1bdfe84db7f
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:
4 * Copyright 2021 Mozilla Foundation
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include "wasm/WasmBuiltinModule.h"
21 #include "util/Text.h"
22 #include "vm/GlobalObject.h"
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"
33 using namespace js;
34 using namespace js::wasm;
36 BuiltinModuleFuncs* BuiltinModuleFuncs::singleton_ = nullptr;
38 [[nodiscard]] bool BuiltinModuleFunc::init(const RefPtr<TypeContext>& types,
39 mozilla::Span<const ValType> params,
40 Maybe<ValType> result,
41 bool usesMemory,
42 const SymbolicAddressSignature* sig,
43 const char* exportName) {
44 // This builtin must not have been initialized yet.
45 MOZ_ASSERT(!recGroup_);
47 // Initialize the basic fields
48 exportName_ = exportName;
49 sig_ = sig;
50 usesMemory_ = usesMemory;
52 // Create a function type for the given params and result
53 ValTypeVector paramVec;
54 if (!paramVec.append(params.data(), params.data() + params.size())) {
55 return false;
57 ValTypeVector resultVec;
58 if (result.isSome() && !resultVec.append(*result)) {
59 return false;
61 const TypeDef* typeDef =
62 types->addType(FuncType(std::move(paramVec), std::move(resultVec)));
63 if (!typeDef) {
64 return false;
66 recGroup_ = &typeDef->recGroup();
67 return true;
70 bool BuiltinModuleFuncs::init() {
71 singleton_ = js_new<BuiltinModuleFuncs>();
72 if (!singleton_) {
73 return false;
76 RefPtr<TypeContext> types = js_new<TypeContext>();
77 if (!types) {
78 return false;
81 #define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, uses_memory, \
82 ...) \
83 const ValType op##Params[] = \
84 DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_##op; \
85 Maybe<ValType> op##Result = DECLARE_BUILTIN_MODULE_FUNC_RESULT_VALTYPE_##op; \
86 if (!singleton_->funcs_[BuiltinModuleFuncId::op].init( \
87 types, mozilla::Span<const ValType>(op##Params), op##Result, \
88 uses_memory, &SASig##sa_name, export)) { \
89 return false; \
91 FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
92 #undef VISIT_BUILTIN_FUNC
94 return true;
97 void BuiltinModuleFuncs::destroy() {
98 if (!singleton_) {
99 return;
101 js_delete(singleton_);
102 singleton_ = nullptr;
105 bool EncodeFuncBody(const BuiltinModuleFunc& builtinModuleFunc,
106 BuiltinModuleFuncId id, Bytes* body) {
107 Encoder encoder(*body);
108 if (!EncodeLocalEntries(encoder, ValTypeVector())) {
109 return false;
111 const FuncType* funcType = builtinModuleFunc.funcType();
112 for (uint32_t i = 0; i < funcType->args().length(); i++) {
113 if (!encoder.writeOp(Op::LocalGet) || !encoder.writeVarU32(i)) {
114 return false;
117 if (!encoder.writeOp(MozOp::CallBuiltinModuleFunc)) {
118 return false;
120 if (!encoder.writeVarU32(uint32_t(id))) {
121 return false;
123 return encoder.writeOp(Op::End);
126 bool CompileBuiltinModule(JSContext* cx,
127 const mozilla::Span<BuiltinModuleFuncId> ids,
128 mozilla::Maybe<Shareable> memory,
129 MutableHandle<WasmModuleObject*> result) {
130 // Create the options manually, enabling intrinsics
131 FeatureOptions featureOptions;
132 featureOptions.isBuiltinModule = true;
134 // Initialize the compiler environment, choosing the best tier possible
135 SharedCompileArgs compileArgs = CompileArgs::buildAndReport(
136 cx, ScriptedCaller(), featureOptions, /* reportOOM */ true);
137 if (!compileArgs) {
138 return false;
140 CompilerEnvironment compilerEnv(
141 CompileMode::Once, IonAvailable(cx) ? Tier::Optimized : Tier::Baseline,
142 DebugEnabled::False);
143 compilerEnv.computeParameters();
145 // Build a module environment
146 ModuleEnvironment moduleEnv(compileArgs->features);
147 if (!moduleEnv.init()) {
148 ReportOutOfMemory(cx);
149 return false;
152 if (memory.isSome()) {
153 // Add (import (memory 0))
154 CacheableName emptyString;
155 CacheableName memoryString;
156 if (!CacheableName::fromUTF8Chars("memory", &memoryString)) {
157 ReportOutOfMemory(cx);
158 return false;
160 if (!moduleEnv.imports.append(Import(std::move(emptyString),
161 std::move(memoryString),
162 DefinitionKind::Memory))) {
163 ReportOutOfMemory(cx);
164 return false;
166 if (!moduleEnv.memories.append(MemoryDesc(Limits(0, Nothing(), *memory)))) {
167 ReportOutOfMemory(cx);
168 return false;
172 // Add (type (func (params ...))) for each func. The function types will
173 // be deduplicated by the runtime
174 for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
175 const BuiltinModuleFuncId& id = ids[funcIndex];
176 const BuiltinModuleFunc& builtinModuleFunc =
177 BuiltinModuleFuncs::getFromId(id);
179 SharedRecGroup recGroup = builtinModuleFunc.recGroup();
180 MOZ_ASSERT(recGroup->numTypes() == 1);
181 if (!moduleEnv.types->addRecGroup(recGroup)) {
182 ReportOutOfMemory(cx);
183 return false;
187 // Add (func (type $i)) declarations. Do this after all types have been added
188 // as the function declaration metadata uses pointers into the type vectors
189 // that must be stable.
190 for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
191 FuncDesc decl(&(*moduleEnv.types)[funcIndex].funcType(), funcIndex);
192 if (!moduleEnv.funcs.append(decl)) {
193 ReportOutOfMemory(cx);
194 return false;
196 moduleEnv.declareFuncExported(funcIndex, true, false);
199 // Add (export "$name" (func $i)) declarations.
200 for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
201 const BuiltinModuleFunc& builtinModuleFunc =
202 BuiltinModuleFuncs::getFromId(ids[funcIndex]);
204 CacheableName exportName;
205 if (!CacheableName::fromUTF8Chars(builtinModuleFunc.exportName(),
206 &exportName) ||
207 !moduleEnv.exports.append(Export(std::move(exportName), funcIndex,
208 DefinitionKind::Function))) {
209 ReportOutOfMemory(cx);
210 return false;
214 // Compile the module functions
215 UniqueChars error;
216 ModuleGenerator mg(*compileArgs, &moduleEnv, &compilerEnv, nullptr, &error,
217 nullptr);
218 if (!mg.init(nullptr)) {
219 ReportOutOfMemory(cx);
220 return false;
223 // Prepare and compile function bodies
224 Vector<Bytes, 1, SystemAllocPolicy> bodies;
225 if (!bodies.reserve(ids.size())) {
226 ReportOutOfMemory(cx);
227 return false;
229 for (uint32_t funcIndex = 0; funcIndex < ids.size(); funcIndex++) {
230 BuiltinModuleFuncId id = ids[funcIndex];
231 const BuiltinModuleFunc& builtinModuleFunc =
232 BuiltinModuleFuncs::getFromId(ids[funcIndex]);
234 // Compilation may be done using other threads, ModuleGenerator requires
235 // that function bodies live until after finishFuncDefs().
236 bodies.infallibleAppend(Bytes());
237 Bytes& bytecode = bodies.back();
239 // Encode function body that will call the builtinModuleFunc using our
240 // builtin opcode, and launch a compile task
241 if (!EncodeFuncBody(builtinModuleFunc, id, &bytecode) ||
242 !mg.compileFuncDef(funcIndex, 0, bytecode.begin(),
243 bytecode.begin() + bytecode.length())) {
244 // This must be an OOM and will be reported by the caller
245 MOZ_ASSERT(!error);
246 ReportOutOfMemory(cx);
247 return false;
251 // Finish and block on function compilation
252 if (!mg.finishFuncDefs()) {
253 // This must be an OOM and will be reported by the caller
254 MOZ_ASSERT(!error);
255 ReportOutOfMemory(cx);
256 return false;
259 // Create a dummy bytecode vector, that will not be used
260 SharedBytes bytecode = js_new<ShareableBytes>();
261 if (!bytecode) {
262 ReportOutOfMemory(cx);
263 return false;
266 // Finish the module
267 SharedModule module = mg.finishModule(*bytecode, nullptr);
268 if (!module) {
269 ReportOutOfMemory(cx);
270 return false;
273 // Create a WasmModuleObject for the module, and return it
274 RootedObject proto(
275 cx, GlobalObject::getOrCreatePrototype(cx, JSProto_WasmModule));
276 if (!proto) {
277 ReportOutOfMemory(cx);
278 return false;
280 result.set(WasmModuleObject::create(cx, *module, proto));
281 return !!result;
284 static BuiltinModuleFuncId SelfTestFuncs[] = {BuiltinModuleFuncId::I8VecMul};
286 #ifdef ENABLE_WASM_MOZ_INTGEMM
287 static BuiltinModuleFuncId IntGemmFuncs[] = {
288 BuiltinModuleFuncId::I8PrepareB,
289 BuiltinModuleFuncId::I8PrepareBFromTransposed,
290 BuiltinModuleFuncId::I8PrepareBFromQuantizedTransposed,
291 BuiltinModuleFuncId::I8PrepareA,
292 BuiltinModuleFuncId::I8PrepareBias,
293 BuiltinModuleFuncId::I8MultiplyAndAddBias,
294 BuiltinModuleFuncId::I8SelectColumnsOfB};
295 #endif // ENABLE_WASM_MOZ_INTGEMM
297 #ifdef ENABLE_WASM_JS_STRING_BUILTINS
298 static BuiltinModuleFuncId JSStringFuncs[] = {
299 BuiltinModuleFuncId::StringTest,
300 BuiltinModuleFuncId::StringCast,
301 BuiltinModuleFuncId::StringFromCharCodeArray,
302 BuiltinModuleFuncId::StringIntoCharCodeArray,
303 BuiltinModuleFuncId::StringFromCharCode,
304 BuiltinModuleFuncId::StringFromCodePoint,
305 BuiltinModuleFuncId::StringCharCodeAt,
306 BuiltinModuleFuncId::StringCodePointAt,
307 BuiltinModuleFuncId::StringLength,
308 BuiltinModuleFuncId::StringConcat,
309 BuiltinModuleFuncId::StringSubstring,
310 BuiltinModuleFuncId::StringEquals,
311 BuiltinModuleFuncId::StringCompare};
312 static const char* JSStringModuleName = "wasm:js-string";
313 #endif // ENABLE_WASM_JS_STRING_BUILTINS
315 Maybe<BuiltinModuleId> wasm::ImportMatchesBuiltinModule(
316 Span<const char> importName, BuiltinModuleIds enabledBuiltins) {
317 #ifdef ENABLE_WASM_JS_STRING_BUILTINS
318 if (enabledBuiltins.jsString &&
319 importName == mozilla::MakeStringSpan(JSStringModuleName)) {
320 return Some(BuiltinModuleId::JSString);
322 #endif // ENABLE_WASM_JS_STRING_BUILTINS
323 // Not supported for implicit instantiation yet
324 MOZ_RELEASE_ASSERT(!enabledBuiltins.selfTest && !enabledBuiltins.intGemm);
325 return Nothing();
328 Maybe<const BuiltinModuleFunc*> wasm::ImportMatchesBuiltinModuleFunc(
329 mozilla::Span<const char> importName, BuiltinModuleId module) {
330 #ifdef ENABLE_WASM_JS_STRING_BUILTINS
331 // Not supported for implicit instantiation yet
332 MOZ_RELEASE_ASSERT(module == BuiltinModuleId::JSString);
333 for (BuiltinModuleFuncId funcId : JSStringFuncs) {
334 const BuiltinModuleFunc& func = BuiltinModuleFuncs::getFromId(funcId);
335 if (importName == mozilla::MakeStringSpan(func.exportName())) {
336 return Some(&func);
339 #endif // ENABLE_WASM_JS_STRING_BUILTINS
340 return Nothing();
343 bool wasm::CompileBuiltinModule(JSContext* cx, BuiltinModuleId module,
344 MutableHandle<WasmModuleObject*> result) {
345 switch (module) {
346 case BuiltinModuleId::SelfTest:
347 return CompileBuiltinModule(cx, SelfTestFuncs, Some(Shareable::False),
348 result);
349 #ifdef ENABLE_WASM_MOZ_INTGEMM
350 case BuiltinModuleId::IntGemm:
351 return CompileBuiltinModule(cx, IntGemmFuncs, Some(Shareable::False),
352 result);
353 #endif // ENABLE_WASM_MOZ_INTGEMM
354 #ifdef ENABLE_WASM_JS_STRING_BUILTINS
355 case BuiltinModuleId::JSString:
356 return CompileBuiltinModule(cx, JSStringFuncs, Nothing(), result);
357 #endif // ENABLE_WASM_JS_STRING_BUILTINS
358 default:
359 MOZ_CRASH();
363 bool wasm::InstantiateBuiltinModule(JSContext* cx, BuiltinModuleId module,
364 MutableHandleObject result) {
365 Rooted<WasmModuleObject*> moduleObj(cx);
366 if (!CompileBuiltinModule(cx, module, &moduleObj)) {
367 ReportOutOfMemory(cx);
368 return false;
370 ImportValues imports;
371 Rooted<WasmInstanceObject*> instanceObj(cx);
372 RootedObject instanceProto(cx);
373 if (!moduleObj->module().instantiate(cx, imports, instanceProto,
374 &instanceObj)) {
375 MOZ_RELEASE_ASSERT(cx->isThrowingOutOfMemory());
376 return false;
378 result.set(&instanceObj->exportsObj());
379 return true;