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 2016 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/WasmJS.h"
20
21 #include "mozilla/CheckedInt.h"
22 #include "mozilla/EndianUtils.h"
23 #include "mozilla/Maybe.h"
24 #include "mozilla/RangedPtr.h"
25
26 #include <algorithm>
27
28 #include "ds/IdValuePair.h" // js::IdValuePair
29 #include "gc/FreeOp.h"
30 #include "jit/AtomicOperations.h"
31 #include "jit/JitOptions.h"
32 #include "jit/JitRuntime.h"
33 #include "jit/Simulator.h"
34 #if defined(JS_CODEGEN_X64) // Assembler::HasSSE41
35 # include "jit/x64/Assembler-x64.h"
36 # include "jit/x86-shared/Architecture-x86-shared.h"
37 # include "jit/x86-shared/Assembler-x86-shared.h"
38 #endif
39 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
40 #include "js/Printf.h"
41 #include "js/PropertySpec.h" // JS_{PS,FN}{,_END}
42 #include "util/StringBuffer.h"
43 #include "util/Text.h"
44 #include "vm/ErrorObject.h"
45 #include "vm/FunctionFlags.h" // js::FunctionFlags
46 #include "vm/GlobalObject.h" // js::GlobalObject
47 #include "vm/HelperThreadState.h" // js::PromiseHelperTask
48 #include "vm/Interpreter.h"
49 #include "vm/PlainObject.h" // js::PlainObject
50 #include "vm/PromiseObject.h" // js::PromiseObject
51 #include "vm/StringType.h"
52 #include "vm/Warnings.h" // js::WarnNumberASCII
53 #include "vm/WellKnownAtom.h" // js_*_str
54 #include "wasm/WasmBaselineCompile.h"
55 #include "wasm/WasmBuiltins.h"
56 #include "wasm/WasmCompile.h"
57 #include "wasm/WasmCraneliftCompile.h"
58 #include "wasm/WasmInstance.h"
59 #include "wasm/WasmIonCompile.h"
60 #include "wasm/WasmModule.h"
61 #include "wasm/WasmProcess.h"
62 #include "wasm/WasmSignalHandlers.h"
63 #include "wasm/WasmStubs.h"
64 #include "wasm/WasmValidate.h"
65
66 #include "vm/ArrayBufferObject-inl.h"
67 #include "vm/JSObject-inl.h"
68 #include "vm/NativeObject-inl.h"
69
70 using namespace js;
71 using namespace js::jit;
72 using namespace js::wasm;
73
74 using mozilla::CheckedInt;
75 using mozilla::Nothing;
76 using mozilla::RangedPtr;
77 using mozilla::Span;
78
79 // About the fuzzer intercession points: If fuzzing has been selected and only a
80 // single compiler has been selected then we will disable features that are not
81 // supported by that single compiler. This is strictly a concession to the
82 // fuzzer infrastructure.
83
IsFuzzingIon(JSContext * cx)84 static inline bool IsFuzzingIon(JSContext* cx) {
85 return IsFuzzing() && !cx->options().wasmBaseline() &&
86 cx->options().wasmIon() && !cx->options().wasmCranelift();
87 }
88
IsFuzzingCranelift(JSContext * cx)89 static inline bool IsFuzzingCranelift(JSContext* cx) {
90 return IsFuzzing() && !cx->options().wasmBaseline() &&
91 !cx->options().wasmIon() && cx->options().wasmCranelift();
92 }
93
94 // These functions read flags and apply fuzzing intercession policies. Never go
95 // directly to the flags in code below, always go via these accessors.
96
WasmSimdWormholeFlag(JSContext * cx)97 static inline bool WasmSimdWormholeFlag(JSContext* cx) {
98 #ifdef ENABLE_WASM_SIMD_WORMHOLE
99 return cx->options().wasmSimdWormhole();
100 #else
101 return false;
102 #endif
103 }
104
WasmThreadsFlag(JSContext * cx)105 static inline bool WasmThreadsFlag(JSContext* cx) {
106 return cx->realm() &&
107 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled();
108 }
109
110 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
111 ...) \
112 static inline bool Wasm##NAME##Flag(JSContext* cx) { \
113 return (COMPILE_PRED) && (FLAG_PRED) && cx->options().wasm##NAME(); \
114 }
115 JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE);
116 #undef WASM_FEATURE
117
WasmDebuggerActive(JSContext * cx)118 static inline bool WasmDebuggerActive(JSContext* cx) {
119 if (IsFuzzingIon(cx) || IsFuzzingCranelift(cx)) {
120 return false;
121 }
122 return cx->realm() && cx->realm()->debuggerObservesAsmJS();
123 }
124
125 /*
126 * [SMDOC] Compiler and feature selection; compiler and feature availability.
127 *
128 * In order to make the computation of whether a wasm feature or wasm compiler
129 * is available predictable, we have established some rules, and implemented
130 * those rules.
131 *
132 * Code elsewhere should use the predicates below to test for features and
133 * compilers, it should never try to compute feature and compiler availability
134 * in other ways.
135 *
136 * At the outset, there is a set of selected compilers C containing at most one
137 * baseline compiler [*] and at most one optimizing compiler [**], and a set of
138 * selected features F. These selections come from defaults and from overrides
139 * by command line switches in the shell and javascript.option.wasm_X in the
140 * browser. Defaults for both features and compilers may be platform specific,
141 * for example, some compilers may not be available on some platforms because
142 * they do not support the architecture at all or they do not support features
143 * that must be enabled by default on the platform.
144 *
145 * [*] Currently we have only one, "baseline" aka "Rabaldr", but other
146 * implementations have additional baseline translators, eg from wasm
147 * bytecode to an internal code processed by an interpreter.
148 *
149 * [**] Currently we have two, "ion" aka "Baldr", and "Cranelift".
150 *
151 *
152 * Compiler availability:
153 *
154 * The set of features F induces a set of available compilers A: these are the
155 * compilers that all support all the features in F. (Some of these compilers
156 * may not be in the set C.)
157 *
158 * The sets C and A are intersected, yielding a set of enabled compilers E.
159 * Notably, the set E may be empty, in which case wasm is effectively disabled
160 * (though the WebAssembly object is still present in the global environment).
161 *
162 * An important consequence is that selecting a feature that is not supported by
163 * a particular compiler disables that compiler completely -- there is no notion
164 * of a compiler being available but suddenly failing when an unsupported
165 * feature is used by a program. If a compiler is available, it supports all
166 * the features that have been selected.
167 *
168 * Equally important, a feature cannot be enabled by default on a platform if
169 * the feature is not supported by all the compilers we wish to have enabled by
170 * default on the platform. We MUST by-default disable features on a platform
171 * that are not supported by all the compilers on the platform.
172 *
173 * As an example:
174 *
175 * On ARM64 the default compilers are Baseline and Cranelift. Say Cranelift
176 * does not support feature X. Thus X cannot be enabled by default on ARM64.
177 * However, X support can be compiled-in to SpiderMonkey, and the user can opt
178 * to enable X. Doing so will disable Cranelift.
179 *
180 * In contrast, X can be enabled by default on x64, where the default
181 * compilers are Baseline and Ion, both of which support X.
182 *
183 * A subtlety is worth noting: on x64, enabling Cranelift (thus disabling Ion)
184 * will not disable X. Instead, the presence of X in the selected feature set
185 * will disable Cranelift, leaving only Baseline. This follows from the logic
186 * described above.
187 *
188 * In a shell build, the testing functions wasmCompilersPresent,
189 * wasmCompileMode, wasmCraneliftDisabledByFeatures, and
190 * wasmIonDisabledByFeatures can be used to probe compiler availability and the
191 * reasons for a compiler being unavailable.
192 *
193 *
194 * Feature availability:
195 *
196 * A feature is available if it is selected and there is at least one available
197 * compiler that implements it.
198 *
199 * For example, --wasm-gc selects the GC feature, and if Baseline is available
200 * then the feature is available.
201 *
202 * In a shell build, there are per-feature testing functions (of the form
203 * wasmFeatureEnabled) to probe whether specific features are available.
204 */
205
206 // Compiler availability predicates. These must be kept in sync with the
207 // feature predicates in the next section below.
208 //
209 // These can't call the feature predicates since the feature predicates call
210 // back to these predicates. So there will be a small amount of duplicated
211 // logic here, but as compilers reach feature parity that duplication will go
212 // away.
213 //
214 // There's a static precedence order between the optimizing compilers. This
215 // order currently ranks Cranelift over Ion on all platforms because Cranelift
216 // is disabled by default on all platforms: anyone who has enabled Cranelift
217 // will wish to use it instead of Ion.
218 //
219 // The precedence order is implemented by guards in IonAvailable() and
220 // CraneliftAvailable(). We expect that it will become more complex as the
221 // default settings change. But it should remain static.
222
BaselineAvailable(JSContext * cx)223 bool wasm::BaselineAvailable(JSContext* cx) {
224 // Baseline supports every feature supported by any compiler.
225 return cx->options().wasmBaseline() && BaselinePlatformSupport();
226 }
227
IonAvailable(JSContext * cx)228 bool wasm::IonAvailable(JSContext* cx) {
229 if (!cx->options().wasmIon() || !IonPlatformSupport()) {
230 return false;
231 }
232 bool isDisabled = false;
233 MOZ_ALWAYS_TRUE(IonDisabledByFeatures(cx, &isDisabled));
234 return !isDisabled && !CraneliftAvailable(cx);
235 }
236
WasmCompilerForAsmJSAvailable(JSContext * cx)237 bool wasm::WasmCompilerForAsmJSAvailable(JSContext* cx) {
238 // For now, restrict this to Ion - we have not tested Cranelift properly.
239 return IonAvailable(cx);
240 }
241
242 template <size_t ArrayLength>
Append(JSStringBuilder * reason,const char (& s)[ArrayLength],char * sep)243 static inline bool Append(JSStringBuilder* reason, const char (&s)[ArrayLength],
244 char* sep) {
245 if ((*sep && !reason->append(*sep)) || !reason->append(s)) {
246 return false;
247 }
248 *sep = ',';
249 return true;
250 }
251
IonDisabledByFeatures(JSContext * cx,bool * isDisabled,JSStringBuilder * reason)252 bool wasm::IonDisabledByFeatures(JSContext* cx, bool* isDisabled,
253 JSStringBuilder* reason) {
254 // Ion has no debugging support, no gc support.
255 bool debug = WasmDebuggerActive(cx);
256 bool functionReferences = WasmFunctionReferencesFlag(cx);
257 bool gc = WasmGcFlag(cx);
258 bool exn = WasmExceptionsFlag(cx);
259 if (reason) {
260 char sep = 0;
261 if (debug && !Append(reason, "debug", &sep)) {
262 return false;
263 }
264 if (functionReferences && !Append(reason, "function-references", &sep)) {
265 return false;
266 }
267 if (gc && !Append(reason, "gc", &sep)) {
268 return false;
269 }
270 if (exn && !Append(reason, "exceptions", &sep)) {
271 return false;
272 }
273 }
274 *isDisabled = debug || functionReferences || gc || exn;
275 return true;
276 }
277
CraneliftAvailable(JSContext * cx)278 bool wasm::CraneliftAvailable(JSContext* cx) {
279 if (!cx->options().wasmCranelift() || !CraneliftPlatformSupport()) {
280 return false;
281 }
282 bool isDisabled = false;
283 MOZ_ALWAYS_TRUE(CraneliftDisabledByFeatures(cx, &isDisabled));
284 return !isDisabled;
285 }
286
CraneliftDisabledByFeatures(JSContext * cx,bool * isDisabled,JSStringBuilder * reason)287 bool wasm::CraneliftDisabledByFeatures(JSContext* cx, bool* isDisabled,
288 JSStringBuilder* reason) {
289 // Cranelift has no debugging support, no gc support, no simd, and
290 // no exceptions support.
291 bool debug = WasmDebuggerActive(cx);
292 bool functionReferences = WasmFunctionReferencesFlag(cx);
293 bool gc = WasmGcFlag(cx);
294 #ifdef JS_CODEGEN_ARM64
295 // Cranelift aarch64 has full SIMD support.
296 bool simdOnNonAarch64 = false;
297 #else
298 bool simdOnNonAarch64 = WasmSimdFlag(cx);
299 #endif
300 bool exn = WasmExceptionsFlag(cx);
301 if (reason) {
302 char sep = 0;
303 if (debug && !Append(reason, "debug", &sep)) {
304 return false;
305 }
306 if (functionReferences && !Append(reason, "function-references", &sep)) {
307 return false;
308 }
309 if (gc && !Append(reason, "gc", &sep)) {
310 return false;
311 }
312 if (simdOnNonAarch64 && !Append(reason, "simd", &sep)) {
313 return false;
314 }
315 if (exn && !Append(reason, "exceptions", &sep)) {
316 return false;
317 }
318 }
319 *isDisabled = debug || functionReferences || gc || simdOnNonAarch64 || exn;
320 return true;
321 }
322
AnyCompilerAvailable(JSContext * cx)323 bool wasm::AnyCompilerAvailable(JSContext* cx) {
324 return wasm::BaselineAvailable(cx) || wasm::IonAvailable(cx) ||
325 wasm::CraneliftAvailable(cx);
326 }
327
328 // Feature predicates. These must be kept in sync with the predicates in the
329 // section above.
330 //
331 // The meaning of these predicates is tricky: A predicate is true for a feature
332 // if the feature is enabled and/or compiled-in *and* we have *at least one*
333 // compiler that can support the feature. Subsequent compiler selection must
334 // ensure that only compilers that actually support the feature are used.
335
336 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
337 ...) \
338 bool wasm::NAME##Available(JSContext* cx) { \
339 return Wasm##NAME##Flag(cx) && (COMPILER_PRED); \
340 }
JS_FOR_WASM_FEATURES(WASM_FEATURE,WASM_FEATURE)341 JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE)
342 #undef WASM_FEATURE
343
344 #ifdef ENABLE_WASM_SIMD_WORMHOLE
345 static bool IsSimdPrivilegedContext(JSContext* cx) {
346 // This may be slightly more lenient than we want in an ideal world, but it
347 // remains safe.
348 return cx->realm() && cx->realm()->principals() &&
349 cx->realm()->principals()->isSystemOrAddonPrincipal();
350 }
351 #endif
352
SimdWormholeAvailable(JSContext * cx)353 bool wasm::SimdWormholeAvailable(JSContext* cx) {
354 #ifdef ENABLE_WASM_SIMD_WORMHOLE
355 // The #ifdef ensures that we only enable the wormhole on hardware that
356 // supports it and if SIMD support is compiled in.
357 //
358 // Next we must check that the CPU supports SIMD; it might not, even if SIMD
359 // is available. Do this directly, not via WasmSimdFlag().
360 //
361 // Do not go via WasmSimdFlag() because we do not want to gate on
362 // j.o.wasm_simd. If the wormhole is available, requesting it will
363 // force-enable SIMD.
364 return js::jit::JitSupportsWasmSimd() &&
365 (WasmSimdWormholeFlag(cx) || IsSimdPrivilegedContext(cx)) &&
366 (IonAvailable(cx) || BaselineAvailable(cx)) && !CraneliftAvailable(cx);
367 #else
368 return false;
369 #endif
370 }
371
ThreadsAvailable(JSContext * cx)372 bool wasm::ThreadsAvailable(JSContext* cx) {
373 return WasmThreadsFlag(cx) && AnyCompilerAvailable(cx);
374 }
375
HasPlatformSupport(JSContext * cx)376 bool wasm::HasPlatformSupport(JSContext* cx) {
377 #if !MOZ_LITTLE_ENDIAN() || defined(JS_CODEGEN_NONE) || defined(__wasi__)
378 return false;
379 #endif
380
381 if (gc::SystemPageSize() > wasm::PageSize) {
382 return false;
383 }
384
385 if (!JitOptions.supportsFloatingPoint) {
386 return false;
387 }
388
389 if (!JitOptions.supportsUnalignedAccesses) {
390 return false;
391 }
392
393 if (!wasm::EnsureFullSignalHandlers(cx)) {
394 return false;
395 }
396
397 if (!jit::JitSupportsAtomics()) {
398 return false;
399 }
400
401 // Wasm threads require 8-byte lock-free atomics.
402 if (!jit::AtomicOperations::isLockfree8()) {
403 return false;
404 }
405
406 // Lazily initialize the global type context
407 if (!cx->wasm().ensureTypeContext(cx)) {
408 return false;
409 }
410
411 // Test only whether the compilers are supported on the hardware, not whether
412 // they are enabled.
413 return BaselinePlatformSupport() || IonPlatformSupport() ||
414 CraneliftPlatformSupport();
415 }
416
HasSupport(JSContext * cx)417 bool wasm::HasSupport(JSContext* cx) {
418 // If the general wasm pref is on, it's on for everything.
419 bool prefEnabled = cx->options().wasm();
420 // If the general pref is off, check trusted principals.
421 if (MOZ_UNLIKELY(!prefEnabled)) {
422 prefEnabled = cx->options().wasmForTrustedPrinciples() && cx->realm() &&
423 cx->realm()->principals() &&
424 cx->realm()->principals()->isSystemOrAddonPrincipal();
425 }
426 // Do not check for compiler availability, as that may be run-time variant.
427 // For HasSupport() we want a stable answer depending only on prefs.
428 return prefEnabled && HasPlatformSupport(cx);
429 }
430
StreamingCompilationAvailable(JSContext * cx)431 bool wasm::StreamingCompilationAvailable(JSContext* cx) {
432 // This should match EnsureStreamSupport().
433 return HasSupport(cx) && AnyCompilerAvailable(cx) &&
434 cx->runtime()->offThreadPromiseState.ref().initialized() &&
435 CanUseExtraThreads() && cx->runtime()->consumeStreamCallback &&
436 cx->runtime()->reportStreamErrorCallback;
437 }
438
CodeCachingAvailable(JSContext * cx)439 bool wasm::CodeCachingAvailable(JSContext* cx) {
440 // Fuzzilli breaks the out-of-process compilation mechanism,
441 // so we disable it permanently in those builds.
442 #ifdef FUZZING_JS_FUZZILLI
443 return false;
444 #else
445
446 // At the moment, we require Ion support for code caching. The main reason
447 // for this is that wasm::CompileAndSerialize() does not have access to
448 // information about which optimizing compiler it should use. See comments in
449 // CompileAndSerialize(), below.
450 return StreamingCompilationAvailable(cx) && IonAvailable(cx);
451 #endif
452 }
453
454 // ============================================================================
455 // Imports
456
ThrowBadImportArg(JSContext * cx)457 static bool ThrowBadImportArg(JSContext* cx) {
458 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
459 JSMSG_WASM_BAD_IMPORT_ARG);
460 return false;
461 }
462
ThrowBadImportType(JSContext * cx,const char * field,const char * str)463 static bool ThrowBadImportType(JSContext* cx, const char* field,
464 const char* str) {
465 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
466 JSMSG_WASM_BAD_IMPORT_TYPE, field, str);
467 return false;
468 }
469
GetProperty(JSContext * cx,HandleObject obj,const char * chars,MutableHandleValue v)470 static bool GetProperty(JSContext* cx, HandleObject obj, const char* chars,
471 MutableHandleValue v) {
472 JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
473 if (!atom) {
474 return false;
475 }
476
477 RootedId id(cx, AtomToId(atom));
478 return GetProperty(cx, obj, obj, id, v);
479 }
480
GetImports(JSContext * cx,const Module & module,HandleObject importObj,ImportValues * imports)481 bool js::wasm::GetImports(JSContext* cx, const Module& module,
482 HandleObject importObj, ImportValues* imports) {
483 if (!module.imports().empty() && !importObj) {
484 return ThrowBadImportArg(cx);
485 }
486
487 const Metadata& metadata = module.metadata();
488
489 #ifdef ENABLE_WASM_EXCEPTIONS
490 uint32_t eventIndex = 0;
491 const EventDescVector& events = metadata.events;
492 #endif
493 uint32_t globalIndex = 0;
494 const GlobalDescVector& globals = metadata.globals;
495 uint32_t tableIndex = 0;
496 const TableDescVector& tables = metadata.tables;
497 for (const Import& import : module.imports()) {
498 RootedValue v(cx);
499 if (!GetProperty(cx, importObj, import.module.get(), &v)) {
500 return false;
501 }
502
503 if (!v.isObject()) {
504 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
505 JSMSG_WASM_BAD_IMPORT_FIELD,
506 import.module.get());
507 return false;
508 }
509
510 RootedObject obj(cx, &v.toObject());
511 if (!GetProperty(cx, obj, import.field.get(), &v)) {
512 return false;
513 }
514
515 switch (import.kind) {
516 case DefinitionKind::Function: {
517 if (!IsFunctionObject(v)) {
518 return ThrowBadImportType(cx, import.field.get(), "Function");
519 }
520
521 if (!imports->funcs.append(&v.toObject().as<JSFunction>())) {
522 return false;
523 }
524
525 break;
526 }
527 case DefinitionKind::Table: {
528 const uint32_t index = tableIndex++;
529 if (!v.isObject() || !v.toObject().is<WasmTableObject>()) {
530 return ThrowBadImportType(cx, import.field.get(), "Table");
531 }
532
533 RootedWasmTableObject obj(cx, &v.toObject().as<WasmTableObject>());
534 if (obj->table().elemType() != tables[index].elemType) {
535 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
536 JSMSG_WASM_BAD_TBL_TYPE_LINK);
537 return false;
538 }
539
540 if (!imports->tables.append(obj)) {
541 return false;
542 }
543 break;
544 }
545 case DefinitionKind::Memory: {
546 if (!v.isObject() || !v.toObject().is<WasmMemoryObject>()) {
547 return ThrowBadImportType(cx, import.field.get(), "Memory");
548 }
549
550 MOZ_ASSERT(!imports->memory);
551 imports->memory = &v.toObject().as<WasmMemoryObject>();
552 break;
553 }
554 #ifdef ENABLE_WASM_EXCEPTIONS
555 case DefinitionKind::Event: {
556 const uint32_t index = eventIndex++;
557 if (!v.isObject() || !v.toObject().is<WasmExceptionObject>()) {
558 return ThrowBadImportType(cx, import.field.get(), "Exception");
559 }
560
561 RootedWasmExceptionObject obj(cx,
562 &v.toObject().as<WasmExceptionObject>());
563
564 // Checks whether the signature of the imported exception object matches
565 // the signature declared in the exception import's EventDesc.
566 if (obj->resultType() != events[index].resultType()) {
567 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
568 JSMSG_WASM_BAD_EXN_SIG, import.module.get(),
569 import.field.get());
570 return false;
571 }
572
573 if (!imports->exceptionObjs.append(obj)) {
574 ReportOutOfMemory(cx);
575 return false;
576 }
577 break;
578 }
579 #endif
580 case DefinitionKind::Global: {
581 const uint32_t index = globalIndex++;
582 const GlobalDesc& global = globals[index];
583 MOZ_ASSERT(global.importIndex() == index);
584
585 RootedVal val(cx);
586 if (v.isObject() && v.toObject().is<WasmGlobalObject>()) {
587 RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
588
589 if (obj->isMutable() != global.isMutable()) {
590 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
591 JSMSG_WASM_BAD_GLOB_MUT_LINK);
592 return false;
593 }
594 if (obj->type() != global.type()) {
595 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
596 JSMSG_WASM_BAD_GLOB_TYPE_LINK);
597 return false;
598 }
599
600 if (imports->globalObjs.length() <= index &&
601 !imports->globalObjs.resize(index + 1)) {
602 ReportOutOfMemory(cx);
603 return false;
604 }
605 imports->globalObjs[index] = obj;
606 val = obj->val();
607 } else {
608 if (IsNumberType(global.type())) {
609 if (global.type() == ValType::I64 && !v.isBigInt()) {
610 return ThrowBadImportType(cx, import.field.get(), "BigInt");
611 }
612 if (global.type() != ValType::I64 && !v.isNumber()) {
613 return ThrowBadImportType(cx, import.field.get(), "Number");
614 }
615 } else {
616 MOZ_ASSERT(global.type().isReference());
617 if (!global.type().isExternRef() && !v.isObjectOrNull()) {
618 return ThrowBadImportType(cx, import.field.get(),
619 "Object-or-null value required for "
620 "non-externref reference type");
621 }
622 }
623
624 if (global.isMutable()) {
625 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
626 JSMSG_WASM_BAD_GLOB_MUT_LINK);
627 return false;
628 }
629
630 if (!Val::fromJSValue(cx, global.type(), v, &val)) {
631 return false;
632 }
633 }
634
635 if (!imports->globalValues.append(val)) {
636 return false;
637 }
638
639 break;
640 }
641 }
642 }
643
644 MOZ_ASSERT(globalIndex == globals.length() ||
645 !globals[globalIndex].isImport());
646
647 return true;
648 }
649
DescribeScriptedCaller(JSContext * cx,ScriptedCaller * caller,const char * introducer)650 static bool DescribeScriptedCaller(JSContext* cx, ScriptedCaller* caller,
651 const char* introducer) {
652 // Note: JS::DescribeScriptedCaller returns whether a scripted caller was
653 // found, not whether an error was thrown. This wrapper function converts
654 // back to the more ordinary false-if-error form.
655
656 JS::AutoFilename af;
657 if (JS::DescribeScriptedCaller(cx, &af, &caller->line)) {
658 caller->filename =
659 FormatIntroducedFilename(cx, af.get(), caller->line, introducer);
660 if (!caller->filename) {
661 return false;
662 }
663 }
664
665 return true;
666 }
667
668 // Parse the options bag that is optionally passed to functions that compile
669 // wasm. This is for internal experimentation purposes. See comments about the
670 // SIMD wormhole in WasmConstants.h.
671
ParseCompileOptions(JSContext * cx,HandleValue maybeOptions,FeatureOptions * options)672 static bool ParseCompileOptions(JSContext* cx, HandleValue maybeOptions,
673 FeatureOptions* options) {
674 if (SimdWormholeAvailable(cx)) {
675 if (maybeOptions.isObject()) {
676 RootedValue wormholeVal(cx);
677 RootedObject obj(cx, &maybeOptions.toObject());
678 if (!JS_GetProperty(cx, obj, "simdWormhole", &wormholeVal)) {
679 return false;
680 }
681 if (wormholeVal.isBoolean()) {
682 options->simdWormhole = wormholeVal.toBoolean();
683 }
684 }
685 }
686 return true;
687 }
688
InitCompileArgs(JSContext * cx,HandleValue maybeOptions,const char * introducer)689 static SharedCompileArgs InitCompileArgs(JSContext* cx,
690 HandleValue maybeOptions,
691 const char* introducer) {
692 ScriptedCaller scriptedCaller;
693 if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) {
694 return nullptr;
695 }
696
697 FeatureOptions options;
698 if (!ParseCompileOptions(cx, maybeOptions, &options)) {
699 return nullptr;
700 }
701 return CompileArgs::build(cx, std::move(scriptedCaller), options);
702 }
703
704 // ============================================================================
705 // Testing / Fuzzing support
706
Eval(JSContext * cx,Handle<TypedArrayObject * > code,HandleObject importObj,HandleValue maybeOptions,MutableHandleWasmInstanceObject instanceObj)707 bool wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code,
708 HandleObject importObj, HandleValue maybeOptions,
709 MutableHandleWasmInstanceObject instanceObj) {
710 if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
711 return false;
712 }
713
714 MutableBytes bytecode = cx->new_<ShareableBytes>();
715 if (!bytecode) {
716 return false;
717 }
718
719 if (!bytecode->append((uint8_t*)code->dataPointerEither().unwrap(),
720 code->byteLength())) {
721 ReportOutOfMemory(cx);
722 return false;
723 }
724
725 SharedCompileArgs compileArgs =
726 InitCompileArgs(cx, maybeOptions, "wasm_eval");
727 if (!compileArgs) {
728 return false;
729 }
730
731 UniqueChars error;
732 UniqueCharsVector warnings;
733 SharedModule module =
734 CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
735 if (!module) {
736 if (error) {
737 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
738 JSMSG_WASM_COMPILE_ERROR, error.get());
739 return false;
740 }
741 ReportOutOfMemory(cx);
742 return false;
743 }
744
745 Rooted<ImportValues> imports(cx);
746 if (!GetImports(cx, *module, importObj, imports.address())) {
747 return false;
748 }
749
750 return module->instantiate(cx, imports.get(), nullptr, instanceObj);
751 }
752
753 struct MOZ_STACK_CLASS SerializeListener : JS::OptimizedEncodingListener {
754 // MOZ_STACK_CLASS means these can be nops.
AddRefSerializeListener755 MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override { return 0; }
ReleaseSerializeListener756 MozExternalRefCountType MOZ_XPCOM_ABI Release() override { return 0; }
757
758 DebugOnly<bool> called = false;
759 Bytes* serialized;
SerializeListenerSerializeListener760 explicit SerializeListener(Bytes* serialized) : serialized(serialized) {}
761
storeOptimizedEncodingSerializeListener762 void storeOptimizedEncoding(JS::UniqueOptimizedEncodingBytes bytes) override {
763 MOZ_ASSERT(!called);
764 called = true;
765 if (serialized->resize(bytes->length())) {
766 memcpy(serialized->begin(), bytes->begin(), bytes->length());
767 }
768 }
769 };
770
CompileAndSerialize(const ShareableBytes & bytecode,Bytes * serialized)771 bool wasm::CompileAndSerialize(const ShareableBytes& bytecode,
772 Bytes* serialized) {
773 MutableCompileArgs compileArgs = js_new<CompileArgs>(ScriptedCaller());
774 if (!compileArgs) {
775 return false;
776 }
777
778 // The caller has ensured CodeCachingAvailable(). Moreover, we want to ensure
779 // we go straight to tier-2 so that we synchronously call
780 // JS::OptimizedEncodingListener::storeOptimizedEncoding().
781 compileArgs->baselineEnabled = false;
782
783 // We always pick Ion here, and we depend on CodeCachingAvailable() having
784 // determined that Ion is available, see comments at CodeCachingAvailable().
785 // To do better, we need to pass information about which compiler that should
786 // be used into CompileAndSerialize().
787 compileArgs->ionEnabled = true;
788
789 // The caller must ensure that huge memory support is configured the same in
790 // the receiving process of this serialized module.
791 compileArgs->features.hugeMemory = wasm::IsHugeMemoryEnabled();
792
793 SerializeListener listener(serialized);
794
795 UniqueChars error;
796 UniqueCharsVector warnings;
797 SharedModule module =
798 CompileBuffer(*compileArgs, bytecode, &error, &warnings, &listener);
799 if (!module) {
800 fprintf(stderr, "Compilation error: %s\n", error ? error.get() : "oom");
801 return false;
802 }
803
804 MOZ_ASSERT(module->code().hasTier(Tier::Serialized));
805 MOZ_ASSERT(listener.called);
806 return !listener.serialized->empty();
807 }
808
DeserializeModule(JSContext * cx,const Bytes & serialized,MutableHandleObject moduleObj)809 bool wasm::DeserializeModule(JSContext* cx, const Bytes& serialized,
810 MutableHandleObject moduleObj) {
811 MutableModule module =
812 Module::deserialize(serialized.begin(), serialized.length());
813 if (!module) {
814 ReportOutOfMemory(cx);
815 return false;
816 }
817
818 moduleObj.set(module->createObject(cx));
819 return !!moduleObj;
820 }
821
822 // ============================================================================
823 // Common functions
824
825 // '[EnforceRange] unsigned long' types are coerced with
826 // ConvertToInt(v, 32, 'unsigned')
827 // defined in Web IDL Section 3.2.4.9.
EnforceRangeU32(JSContext * cx,HandleValue v,const char * kind,const char * noun,uint32_t * u32)828 static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
829 const char* noun, uint32_t* u32) {
830 // Step 4.
831 double x;
832 if (!ToNumber(cx, v, &x)) {
833 return false;
834 }
835
836 // Step 5.
837 if (mozilla::IsNegativeZero(x)) {
838 x = 0.0;
839 }
840
841 // Step 6.1.
842 if (!mozilla::IsFinite(x)) {
843 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
844 JSMSG_WASM_BAD_UINT32, kind, noun);
845 return false;
846 }
847
848 // Step 6.2.
849 x = JS::ToInteger(x);
850
851 // Step 6.3.
852 if (x < 0 || x > double(UINT32_MAX)) {
853 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
854 JSMSG_WASM_BAD_UINT32, kind, noun);
855 return false;
856 }
857
858 *u32 = uint32_t(x);
859 MOZ_ASSERT(double(*u32) == x);
860 return true;
861 }
862
GetLimits(JSContext * cx,HandleObject obj,uint32_t maximumField,const char * kind,Limits * limits,Shareable allowShared)863 static bool GetLimits(JSContext* cx, HandleObject obj, uint32_t maximumField,
864 const char* kind, Limits* limits, Shareable allowShared) {
865 JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
866 if (!initialAtom) {
867 return false;
868 }
869 RootedId initialId(cx, AtomToId(initialAtom));
870
871 RootedValue initialVal(cx);
872 if (!GetProperty(cx, obj, obj, initialId, &initialVal)) {
873 return false;
874 }
875
876 uint32_t initial = 0;
877 if (!initialVal.isUndefined() &&
878 !EnforceRangeU32(cx, initialVal, kind, "initial size", &initial)) {
879 return false;
880 }
881 limits->initial = initial;
882
883 if (limits->initial > maximumField) {
884 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
885 kind, "initial size");
886 return false;
887 }
888
889 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
890 // Get minimum parameter.
891 JSAtom* minimumAtom = Atomize(cx, "minimum", strlen("minimum"));
892 if (!minimumAtom) {
893 return false;
894 }
895 RootedId minimumId(cx, AtomToId(minimumAtom));
896
897 RootedValue minimumVal(cx);
898 if (!GetProperty(cx, obj, obj, minimumId, &minimumVal)) {
899 return false;
900 }
901
902 uint32_t minimum = 0;
903 if (!minimumVal.isUndefined() &&
904 !EnforceRangeU32(cx, minimumVal, kind, "initial size", &minimum)) {
905 return false;
906 }
907 if (!minimumVal.isUndefined()) {
908 limits->initial = minimum;
909 }
910 #endif
911
912 // Get maximum parameter.
913 JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
914 if (!maximumAtom) {
915 return false;
916 }
917 RootedId maximumId(cx, AtomToId(maximumAtom));
918
919 RootedValue maxVal(cx);
920 if (!GetProperty(cx, obj, obj, maximumId, &maxVal)) {
921 return false;
922 }
923
924 // maxVal does not have a default value.
925 if (!maxVal.isUndefined()) {
926 uint32_t maximum = 0;
927 if (!EnforceRangeU32(cx, maxVal, kind, "maximum size", &maximum)) {
928 return false;
929 }
930 limits->maximum = Some(maximum);
931
932 if (*limits->maximum > maximumField || limits->initial > *limits->maximum) {
933 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
934 JSMSG_WASM_BAD_RANGE, kind, "maximum size");
935 return false;
936 }
937 }
938
939 limits->shared = Shareable::False;
940
941 if (allowShared == Shareable::True) {
942 JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
943 if (!sharedAtom) {
944 return false;
945 }
946 RootedId sharedId(cx, AtomToId(sharedAtom));
947
948 RootedValue sharedVal(cx);
949 if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
950 return false;
951 }
952
953 // shared's default value is false, which is already the value set above.
954 if (!sharedVal.isUndefined()) {
955 limits->shared =
956 ToBoolean(sharedVal) ? Shareable::True : Shareable::False;
957
958 if (limits->shared == Shareable::True) {
959 if (maxVal.isUndefined()) {
960 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
961 JSMSG_WASM_MISSING_MAXIMUM, kind);
962 return false;
963 }
964
965 if (!cx->realm()
966 ->creationOptions()
967 .getSharedMemoryAndAtomicsEnabled()) {
968 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
969 JSMSG_WASM_NO_SHMEM_LINK);
970 return false;
971 }
972 }
973 }
974 }
975
976 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
977 // Check both minimum and initial are not supplied.
978 if (minimumVal.isUndefined() == initialVal.isUndefined()) {
979 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
980 JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum", "initial");
981 return false;
982 }
983 #else
984 if (initialVal.isUndefined()) {
985 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
986 JSMSG_WASM_MISSING_REQUIRED, "initial");
987 return false;
988 }
989 #endif
990
991 return true;
992 }
993
994 template <class Class, const char* name>
CreateWasmConstructor(JSContext * cx,JSProtoKey key)995 static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) {
996 RootedAtom className(cx, Atomize(cx, name, strlen(name)));
997 if (!className) {
998 return nullptr;
999 }
1000
1001 return NewNativeConstructor(cx, Class::construct, 1, className);
1002 }
1003
1004 // ============================================================================
1005 // WebAssembly.Module class and methods
1006
1007 const JSClassOps WasmModuleObject::classOps_ = {
1008 nullptr, // addProperty
1009 nullptr, // delProperty
1010 nullptr, // enumerate
1011 nullptr, // newEnumerate
1012 nullptr, // resolve
1013 nullptr, // mayResolve
1014 WasmModuleObject::finalize, // finalize
1015 nullptr, // call
1016 nullptr, // hasInstance
1017 nullptr, // construct
1018 nullptr, // trace
1019 };
1020
1021 const JSClass WasmModuleObject::class_ = {
1022 "WebAssembly.Module",
1023 JSCLASS_DELAY_METADATA_BUILDER |
1024 JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
1025 JSCLASS_FOREGROUND_FINALIZE,
1026 &WasmModuleObject::classOps_,
1027 &WasmModuleObject::classSpec_,
1028 };
1029
1030 const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_;
1031
1032 static constexpr char WasmModuleName[] = "Module";
1033
1034 const ClassSpec WasmModuleObject::classSpec_ = {
1035 CreateWasmConstructor<WasmModuleObject, WasmModuleName>,
1036 GenericCreatePrototype<WasmModuleObject>,
1037 WasmModuleObject::static_methods,
1038 nullptr,
1039 WasmModuleObject::methods,
1040 WasmModuleObject::properties,
1041 nullptr,
1042 ClassSpec::DontDefineConstructor};
1043
1044 const JSPropertySpec WasmModuleObject::properties[] = {
1045 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Module", JSPROP_READONLY),
1046 JS_PS_END};
1047
1048 const JSFunctionSpec WasmModuleObject::methods[] = {JS_FS_END};
1049
1050 const JSFunctionSpec WasmModuleObject::static_methods[] = {
1051 JS_FN("imports", WasmModuleObject::imports, 1, JSPROP_ENUMERATE),
1052 JS_FN("exports", WasmModuleObject::exports, 1, JSPROP_ENUMERATE),
1053 JS_FN("customSections", WasmModuleObject::customSections, 2,
1054 JSPROP_ENUMERATE),
1055 JS_FS_END};
1056
1057 /* static */
finalize(JSFreeOp * fop,JSObject * obj)1058 void WasmModuleObject::finalize(JSFreeOp* fop, JSObject* obj) {
1059 const Module& module = obj->as<WasmModuleObject>().module();
1060 obj->zone()->decJitMemory(module.codeLength(module.code().stableTier()));
1061 fop->release(obj, &module, module.gcMallocBytesExcludingCode(),
1062 MemoryUse::WasmModule);
1063 }
1064
IsModuleObject(JSObject * obj,const Module ** module)1065 static bool IsModuleObject(JSObject* obj, const Module** module) {
1066 WasmModuleObject* mobj = obj->maybeUnwrapIf<WasmModuleObject>();
1067 if (!mobj) {
1068 return false;
1069 }
1070
1071 *module = &mobj->module();
1072 return true;
1073 }
1074
GetModuleArg(JSContext * cx,CallArgs args,uint32_t numRequired,const char * name,const Module ** module)1075 static bool GetModuleArg(JSContext* cx, CallArgs args, uint32_t numRequired,
1076 const char* name, const Module** module) {
1077 if (!args.requireAtLeast(cx, name, numRequired)) {
1078 return false;
1079 }
1080
1081 if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
1082 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1083 JSMSG_WASM_BAD_MOD_ARG);
1084 return false;
1085 }
1086
1087 return true;
1088 }
1089
1090 struct KindNames {
1091 RootedPropertyName kind;
1092 RootedPropertyName table;
1093 RootedPropertyName memory;
1094 RootedPropertyName event;
1095 RootedPropertyName signature;
1096
KindNamesKindNames1097 explicit KindNames(JSContext* cx)
1098 : kind(cx), table(cx), memory(cx), event(cx), signature(cx) {}
1099 };
1100
InitKindNames(JSContext * cx,KindNames * names)1101 static bool InitKindNames(JSContext* cx, KindNames* names) {
1102 JSAtom* kind = Atomize(cx, "kind", strlen("kind"));
1103 if (!kind) {
1104 return false;
1105 }
1106 names->kind = kind->asPropertyName();
1107
1108 JSAtom* table = Atomize(cx, "table", strlen("table"));
1109 if (!table) {
1110 return false;
1111 }
1112 names->table = table->asPropertyName();
1113
1114 JSAtom* memory = Atomize(cx, "memory", strlen("memory"));
1115 if (!memory) {
1116 return false;
1117 }
1118 names->memory = memory->asPropertyName();
1119
1120 #ifdef ENABLE_WASM_EXCEPTIONS
1121 JSAtom* event = Atomize(cx, "event", strlen("event"));
1122 if (!event) {
1123 return false;
1124 }
1125 names->event = event->asPropertyName();
1126 #endif
1127
1128 JSAtom* signature = Atomize(cx, "signature", strlen("signature"));
1129 if (!signature) {
1130 return false;
1131 }
1132 names->signature = signature->asPropertyName();
1133
1134 return true;
1135 }
1136
KindToString(JSContext * cx,const KindNames & names,DefinitionKind kind)1137 static JSString* KindToString(JSContext* cx, const KindNames& names,
1138 DefinitionKind kind) {
1139 switch (kind) {
1140 case DefinitionKind::Function:
1141 return cx->names().function;
1142 case DefinitionKind::Table:
1143 return names.table;
1144 case DefinitionKind::Memory:
1145 return names.memory;
1146 case DefinitionKind::Global:
1147 return cx->names().global;
1148 #ifdef ENABLE_WASM_EXCEPTIONS
1149 case DefinitionKind::Event:
1150 return names.event;
1151 #endif
1152 }
1153
1154 MOZ_CRASH("invalid kind");
1155 }
1156
FuncTypeToString(JSContext * cx,const FuncType & funcType)1157 static JSString* FuncTypeToString(JSContext* cx, const FuncType& funcType) {
1158 JSStringBuilder buf(cx);
1159 if (!buf.append('(')) {
1160 return nullptr;
1161 }
1162
1163 bool first = true;
1164 for (ValType arg : funcType.args()) {
1165 if (!first && !buf.append(", ", strlen(", "))) {
1166 return nullptr;
1167 }
1168
1169 UniqueChars argStr = ToString(arg);
1170 if (!argStr) {
1171 return nullptr;
1172 }
1173
1174 if (!buf.append(argStr.get(), strlen(argStr.get()))) {
1175 return nullptr;
1176 }
1177
1178 first = false;
1179 }
1180
1181 if (!buf.append(") -> (", strlen(") -> ("))) {
1182 return nullptr;
1183 }
1184
1185 first = true;
1186 for (ValType result : funcType.results()) {
1187 if (!first && !buf.append(", ", strlen(", "))) {
1188 return nullptr;
1189 }
1190
1191 UniqueChars resultStr = ToString(result);
1192 if (!resultStr) {
1193 return nullptr;
1194 }
1195
1196 if (!buf.append(resultStr.get(), strlen(resultStr.get()))) {
1197 return nullptr;
1198 }
1199
1200 first = false;
1201 }
1202
1203 if (!buf.append(')')) {
1204 return nullptr;
1205 }
1206
1207 return buf.finishString();
1208 }
1209
UTF8CharsToString(JSContext * cx,const char * chars)1210 static JSString* UTF8CharsToString(JSContext* cx, const char* chars) {
1211 return NewStringCopyUTF8Z<CanGC>(cx,
1212 JS::ConstUTF8CharsZ(chars, strlen(chars)));
1213 }
1214
1215 /* static */
imports(JSContext * cx,unsigned argc,Value * vp)1216 bool WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp) {
1217 CallArgs args = CallArgsFromVp(argc, vp);
1218
1219 const Module* module;
1220 if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.imports", &module)) {
1221 return false;
1222 }
1223
1224 KindNames names(cx);
1225 if (!InitKindNames(cx, &names)) {
1226 return false;
1227 }
1228
1229 RootedValueVector elems(cx);
1230 if (!elems.reserve(module->imports().length())) {
1231 return false;
1232 }
1233
1234 const FuncImportVector& funcImports =
1235 module->metadata(module->code().stableTier()).funcImports;
1236
1237 size_t numFuncImport = 0;
1238 for (const Import& import : module->imports()) {
1239 Rooted<IdValueVector> props(cx, IdValueVector(cx));
1240 if (!props.reserve(3)) {
1241 return false;
1242 }
1243
1244 JSString* moduleStr = UTF8CharsToString(cx, import.module.get());
1245 if (!moduleStr) {
1246 return false;
1247 }
1248 props.infallibleAppend(
1249 IdValuePair(NameToId(cx->names().module), StringValue(moduleStr)));
1250
1251 JSString* nameStr = UTF8CharsToString(cx, import.field.get());
1252 if (!nameStr) {
1253 return false;
1254 }
1255 props.infallibleAppend(
1256 IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
1257
1258 JSString* kindStr = KindToString(cx, names, import.kind);
1259 if (!kindStr) {
1260 return false;
1261 }
1262 props.infallibleAppend(
1263 IdValuePair(NameToId(names.kind), StringValue(kindStr)));
1264
1265 if (fuzzingSafe && import.kind == DefinitionKind::Function) {
1266 JSString* ftStr =
1267 FuncTypeToString(cx, funcImports[numFuncImport++].funcType());
1268 if (!ftStr) {
1269 return false;
1270 }
1271 if (!props.append(
1272 IdValuePair(NameToId(names.signature), StringValue(ftStr)))) {
1273 return false;
1274 }
1275 }
1276
1277 JSObject* obj = NewPlainObjectWithProperties(cx, props.begin(),
1278 props.length(), GenericObject);
1279 if (!obj) {
1280 return false;
1281 }
1282
1283 elems.infallibleAppend(ObjectValue(*obj));
1284 }
1285
1286 JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
1287 if (!arr) {
1288 return false;
1289 }
1290
1291 args.rval().setObject(*arr);
1292 return true;
1293 }
1294
1295 /* static */
exports(JSContext * cx,unsigned argc,Value * vp)1296 bool WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp) {
1297 CallArgs args = CallArgsFromVp(argc, vp);
1298
1299 const Module* module;
1300 if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.exports", &module)) {
1301 return false;
1302 }
1303
1304 KindNames names(cx);
1305 if (!InitKindNames(cx, &names)) {
1306 return false;
1307 }
1308
1309 RootedValueVector elems(cx);
1310 if (!elems.reserve(module->exports().length())) {
1311 return false;
1312 }
1313
1314 for (const Export& exp : module->exports()) {
1315 Rooted<IdValueVector> props(cx, IdValueVector(cx));
1316 if (!props.reserve(2)) {
1317 return false;
1318 }
1319
1320 JSString* nameStr = UTF8CharsToString(cx, exp.fieldName());
1321 if (!nameStr) {
1322 return false;
1323 }
1324 props.infallibleAppend(
1325 IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
1326
1327 JSString* kindStr = KindToString(cx, names, exp.kind());
1328 if (!kindStr) {
1329 return false;
1330 }
1331 props.infallibleAppend(
1332 IdValuePair(NameToId(names.kind), StringValue(kindStr)));
1333
1334 if (fuzzingSafe && exp.kind() == DefinitionKind::Function) {
1335 const FuncExport& fe = module->metadata(module->code().stableTier())
1336 .lookupFuncExport(exp.funcIndex());
1337 JSString* ftStr = FuncTypeToString(cx, fe.funcType());
1338 if (!ftStr) {
1339 return false;
1340 }
1341 if (!props.append(
1342 IdValuePair(NameToId(names.signature), StringValue(ftStr)))) {
1343 return false;
1344 }
1345 }
1346
1347 JSObject* obj = NewPlainObjectWithProperties(cx, props.begin(),
1348 props.length(), GenericObject);
1349 if (!obj) {
1350 return false;
1351 }
1352
1353 elems.infallibleAppend(ObjectValue(*obj));
1354 }
1355
1356 JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
1357 if (!arr) {
1358 return false;
1359 }
1360
1361 args.rval().setObject(*arr);
1362 return true;
1363 }
1364
1365 /* static */
customSections(JSContext * cx,unsigned argc,Value * vp)1366 bool WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp) {
1367 CallArgs args = CallArgsFromVp(argc, vp);
1368
1369 const Module* module;
1370 if (!GetModuleArg(cx, args, 2, "WebAssembly.Module.customSections",
1371 &module)) {
1372 return false;
1373 }
1374
1375 Vector<char, 8> name(cx);
1376 {
1377 RootedString str(cx, ToString(cx, args.get(1)));
1378 if (!str) {
1379 return false;
1380 }
1381
1382 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1383 if (!linear) {
1384 return false;
1385 }
1386
1387 if (!name.initLengthUninitialized(
1388 JS::GetDeflatedUTF8StringLength(linear))) {
1389 return false;
1390 }
1391
1392 (void)JS::DeflateStringToUTF8Buffer(linear,
1393 Span(name.begin(), name.length()));
1394 }
1395
1396 RootedValueVector elems(cx);
1397 RootedArrayBufferObject buf(cx);
1398 for (const CustomSection& cs : module->customSections()) {
1399 if (name.length() != cs.name.length()) {
1400 continue;
1401 }
1402 if (memcmp(name.begin(), cs.name.begin(), name.length()) != 0) {
1403 continue;
1404 }
1405
1406 buf = ArrayBufferObject::createZeroed(cx, cs.payload->length());
1407 if (!buf) {
1408 return false;
1409 }
1410
1411 memcpy(buf->dataPointer(), cs.payload->begin(), cs.payload->length());
1412 if (!elems.append(ObjectValue(*buf))) {
1413 return false;
1414 }
1415 }
1416
1417 JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
1418 if (!arr) {
1419 return false;
1420 }
1421
1422 args.rval().setObject(*arr);
1423 return true;
1424 }
1425
1426 /* static */
create(JSContext * cx,const Module & module,HandleObject proto)1427 WasmModuleObject* WasmModuleObject::create(JSContext* cx, const Module& module,
1428 HandleObject proto) {
1429 AutoSetNewObjectMetadata metadata(cx);
1430 auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
1431 if (!obj) {
1432 return nullptr;
1433 }
1434
1435 // This accounts for module allocation size (excluding code which is handled
1436 // separately - see below). This assumes that the size of associated data
1437 // doesn't change for the life of the WasmModuleObject. The size is counted
1438 // once per WasmModuleObject referencing a Module.
1439 InitReservedSlot(obj, MODULE_SLOT, const_cast<Module*>(&module),
1440 module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule);
1441 module.AddRef();
1442
1443 // Bug 1569888: We account for the first tier here; the second tier, if
1444 // different, also needs to be accounted for.
1445 cx->zone()->incJitMemory(module.codeLength(module.code().stableTier()));
1446 return obj;
1447 }
1448
GetBufferSource(JSContext * cx,JSObject * obj,unsigned errorNumber,MutableBytes * bytecode)1449 static bool GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber,
1450 MutableBytes* bytecode) {
1451 *bytecode = cx->new_<ShareableBytes>();
1452 if (!*bytecode) {
1453 return false;
1454 }
1455
1456 JSObject* unwrapped = CheckedUnwrapStatic(obj);
1457
1458 SharedMem<uint8_t*> dataPointer;
1459 size_t byteLength;
1460 if (!unwrapped || !IsBufferSource(unwrapped, &dataPointer, &byteLength)) {
1461 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
1462 return false;
1463 }
1464
1465 if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) {
1466 ReportOutOfMemory(cx);
1467 return false;
1468 }
1469
1470 return true;
1471 }
1472
ReportCompileWarnings(JSContext * cx,const UniqueCharsVector & warnings)1473 static bool ReportCompileWarnings(JSContext* cx,
1474 const UniqueCharsVector& warnings) {
1475 // Avoid spamming the console.
1476 size_t numWarnings = std::min<size_t>(warnings.length(), 3);
1477
1478 for (size_t i = 0; i < numWarnings; i++) {
1479 if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, warnings[i].get())) {
1480 return false;
1481 }
1482 }
1483
1484 if (warnings.length() > numWarnings) {
1485 if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING,
1486 "other warnings suppressed")) {
1487 return false;
1488 }
1489 }
1490
1491 return true;
1492 }
1493
1494 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)1495 bool WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp) {
1496 CallArgs callArgs = CallArgsFromVp(argc, vp);
1497
1498 Log(cx, "sync new Module() started");
1499
1500 if (!ThrowIfNotConstructing(cx, callArgs, "Module")) {
1501 return false;
1502 }
1503
1504 if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1)) {
1505 return false;
1506 }
1507
1508 if (!callArgs[0].isObject()) {
1509 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1510 JSMSG_WASM_BAD_BUF_ARG);
1511 return false;
1512 }
1513
1514 MutableBytes bytecode;
1515 if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
1516 &bytecode)) {
1517 return false;
1518 }
1519
1520 SharedCompileArgs compileArgs =
1521 InitCompileArgs(cx, callArgs.get(1), "WebAssembly.Module");
1522 if (!compileArgs) {
1523 return false;
1524 }
1525
1526 UniqueChars error;
1527 UniqueCharsVector warnings;
1528 SharedModule module =
1529 CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
1530 if (!module) {
1531 if (error) {
1532 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1533 JSMSG_WASM_COMPILE_ERROR, error.get());
1534 return false;
1535 }
1536 ReportOutOfMemory(cx);
1537 return false;
1538 }
1539
1540 if (!ReportCompileWarnings(cx, warnings)) {
1541 return false;
1542 }
1543
1544 RootedObject proto(cx);
1545 if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, JSProto_WasmModule,
1546 &proto)) {
1547 return false;
1548 }
1549 if (!proto) {
1550 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmModule);
1551 }
1552
1553 RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
1554 if (!moduleObj) {
1555 return false;
1556 }
1557
1558 Log(cx, "sync new Module() succeded");
1559
1560 callArgs.rval().setObject(*moduleObj);
1561 return true;
1562 }
1563
module() const1564 const Module& WasmModuleObject::module() const {
1565 MOZ_ASSERT(is<WasmModuleObject>());
1566 return *(const Module*)getReservedSlot(MODULE_SLOT).toPrivate();
1567 }
1568
1569 // ============================================================================
1570 // WebAssembly.Instance class and methods
1571
1572 const JSClassOps WasmInstanceObject::classOps_ = {
1573 nullptr, // addProperty
1574 nullptr, // delProperty
1575 nullptr, // enumerate
1576 nullptr, // newEnumerate
1577 nullptr, // resolve
1578 nullptr, // mayResolve
1579 WasmInstanceObject::finalize, // finalize
1580 nullptr, // call
1581 nullptr, // hasInstance
1582 nullptr, // construct
1583 WasmInstanceObject::trace, // trace
1584 };
1585
1586 const JSClass WasmInstanceObject::class_ = {
1587 "WebAssembly.Instance",
1588 JSCLASS_DELAY_METADATA_BUILDER |
1589 JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
1590 JSCLASS_FOREGROUND_FINALIZE,
1591 &WasmInstanceObject::classOps_,
1592 &WasmInstanceObject::classSpec_,
1593 };
1594
1595 const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_;
1596
1597 static constexpr char WasmInstanceName[] = "Instance";
1598
1599 const ClassSpec WasmInstanceObject::classSpec_ = {
1600 CreateWasmConstructor<WasmInstanceObject, WasmInstanceName>,
1601 GenericCreatePrototype<WasmInstanceObject>,
1602 WasmInstanceObject::static_methods,
1603 nullptr,
1604 WasmInstanceObject::methods,
1605 WasmInstanceObject::properties,
1606 nullptr,
1607 ClassSpec::DontDefineConstructor};
1608
IsInstance(HandleValue v)1609 static bool IsInstance(HandleValue v) {
1610 return v.isObject() && v.toObject().is<WasmInstanceObject>();
1611 }
1612
1613 /* static */
exportsGetterImpl(JSContext * cx,const CallArgs & args)1614 bool WasmInstanceObject::exportsGetterImpl(JSContext* cx,
1615 const CallArgs& args) {
1616 args.rval().setObject(
1617 args.thisv().toObject().as<WasmInstanceObject>().exportsObj());
1618 return true;
1619 }
1620
1621 /* static */
exportsGetter(JSContext * cx,unsigned argc,Value * vp)1622 bool WasmInstanceObject::exportsGetter(JSContext* cx, unsigned argc,
1623 Value* vp) {
1624 CallArgs args = CallArgsFromVp(argc, vp);
1625 return CallNonGenericMethod<IsInstance, exportsGetterImpl>(cx, args);
1626 }
1627
1628 const JSPropertySpec WasmInstanceObject::properties[] = {
1629 JS_PSG("exports", WasmInstanceObject::exportsGetter, JSPROP_ENUMERATE),
1630 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Instance", JSPROP_READONLY),
1631 JS_PS_END};
1632
1633 const JSFunctionSpec WasmInstanceObject::methods[] = {JS_FS_END};
1634
1635 const JSFunctionSpec WasmInstanceObject::static_methods[] = {JS_FS_END};
1636
isNewborn() const1637 bool WasmInstanceObject::isNewborn() const {
1638 MOZ_ASSERT(is<WasmInstanceObject>());
1639 return getReservedSlot(INSTANCE_SLOT).isUndefined();
1640 }
1641
1642 // WeakScopeMap maps from function index to js::Scope. This maps is weak
1643 // to avoid holding scope objects alive. The scopes are normally created
1644 // during debugging.
1645 //
1646 // This is defined here in order to avoid recursive dependency between
1647 // WasmJS.h and Scope.h.
1648 using WasmFunctionScopeMap =
1649 JS::WeakCache<GCHashMap<uint32_t, WeakHeapPtr<WasmFunctionScope*>,
1650 DefaultHasher<uint32_t>, ZoneAllocPolicy>>;
1651 class WasmInstanceObject::UnspecifiedScopeMap {
1652 public:
asWasmFunctionScopeMap()1653 WasmFunctionScopeMap& asWasmFunctionScopeMap() {
1654 return *(WasmFunctionScopeMap*)this;
1655 }
1656 };
1657
1658 /* static */
finalize(JSFreeOp * fop,JSObject * obj)1659 void WasmInstanceObject::finalize(JSFreeOp* fop, JSObject* obj) {
1660 WasmInstanceObject& instance = obj->as<WasmInstanceObject>();
1661 fop->delete_(obj, &instance.exports(), MemoryUse::WasmInstanceExports);
1662 fop->delete_(obj, &instance.scopes().asWasmFunctionScopeMap(),
1663 MemoryUse::WasmInstanceScopes);
1664 fop->delete_(obj, &instance.indirectGlobals(),
1665 MemoryUse::WasmInstanceGlobals);
1666 if (!instance.isNewborn()) {
1667 if (instance.instance().debugEnabled()) {
1668 instance.instance().debug().finalize(fop);
1669 }
1670 fop->delete_(obj, &instance.instance(), MemoryUse::WasmInstanceInstance);
1671 }
1672 }
1673
1674 /* static */
trace(JSTracer * trc,JSObject * obj)1675 void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) {
1676 WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
1677 instanceObj.exports().trace(trc);
1678 instanceObj.indirectGlobals().trace(trc);
1679 if (!instanceObj.isNewborn()) {
1680 instanceObj.instance().tracePrivate(trc);
1681 }
1682 }
1683
1684 /* static */
create(JSContext * cx,SharedCode code,const DataSegmentVector & dataSegments,const ElemSegmentVector & elemSegments,UniqueTlsData tlsData,HandleWasmMemoryObject memory,SharedExceptionTagVector && exceptionTags,SharedTableVector && tables,const JSFunctionVector & funcImports,const GlobalDescVector & globals,const ValVector & globalImportValues,const WasmGlobalObjectVector & globalObjs,HandleObject proto,UniqueDebugState maybeDebug)1685 WasmInstanceObject* WasmInstanceObject::create(
1686 JSContext* cx, SharedCode code, const DataSegmentVector& dataSegments,
1687 const ElemSegmentVector& elemSegments, UniqueTlsData tlsData,
1688 HandleWasmMemoryObject memory, SharedExceptionTagVector&& exceptionTags,
1689 SharedTableVector&& tables, const JSFunctionVector& funcImports,
1690 const GlobalDescVector& globals, const ValVector& globalImportValues,
1691 const WasmGlobalObjectVector& globalObjs, HandleObject proto,
1692 UniqueDebugState maybeDebug) {
1693 Rooted<UniquePtr<ExportMap>> exports(cx,
1694 js::MakeUnique<ExportMap>(cx->zone()));
1695 if (!exports) {
1696 ReportOutOfMemory(cx);
1697 return nullptr;
1698 }
1699
1700 UniquePtr<WasmFunctionScopeMap> scopes =
1701 js::MakeUnique<WasmFunctionScopeMap>(cx->zone(), cx->zone());
1702 if (!scopes) {
1703 ReportOutOfMemory(cx);
1704 return nullptr;
1705 }
1706 // Note that `scopes` is a WeakCache, auto-linked into a sweep list on the
1707 // Zone, and so does not require rooting.
1708
1709 uint32_t indirectGlobals = 0;
1710
1711 for (uint32_t i = 0; i < globalObjs.length(); i++) {
1712 if (globalObjs[i] && globals[i].isIndirect()) {
1713 indirectGlobals++;
1714 }
1715 }
1716
1717 Rooted<UniquePtr<GlobalObjectVector>> indirectGlobalObjs(
1718 cx, js::MakeUnique<GlobalObjectVector>(cx->zone()));
1719 if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals)) {
1720 ReportOutOfMemory(cx);
1721 return nullptr;
1722 }
1723
1724 {
1725 uint32_t next = 0;
1726 for (uint32_t i = 0; i < globalObjs.length(); i++) {
1727 if (globalObjs[i] && globals[i].isIndirect()) {
1728 (*indirectGlobalObjs)[next++] = globalObjs[i];
1729 }
1730 }
1731 }
1732
1733 Instance* instance = nullptr;
1734 RootedWasmInstanceObject obj(cx);
1735
1736 {
1737 // We must delay creating metadata for this object until after all its
1738 // slots have been initialized. We must also create the metadata before
1739 // calling Instance::init as that may allocate new objects.
1740 AutoSetNewObjectMetadata metadata(cx);
1741 obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto);
1742 if (!obj) {
1743 return nullptr;
1744 }
1745
1746 MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
1747
1748 // Finalization assumes these slots are always initialized:
1749 InitReservedSlot(obj, EXPORTS_SLOT, exports.release(),
1750 MemoryUse::WasmInstanceExports);
1751
1752 InitReservedSlot(obj, SCOPES_SLOT, scopes.release(),
1753 MemoryUse::WasmInstanceScopes);
1754
1755 InitReservedSlot(obj, GLOBALS_SLOT, indirectGlobalObjs.release(),
1756 MemoryUse::WasmInstanceGlobals);
1757
1758 obj->initReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());
1759
1760 // The INSTANCE_SLOT may not be initialized if Instance allocation fails,
1761 // leading to an observable "newborn" state in tracing/finalization.
1762 MOZ_ASSERT(obj->isNewborn());
1763
1764 // Root the Instance via WasmInstanceObject before any possible GC.
1765 instance = cx->new_<Instance>(cx, obj, code, std::move(tlsData), memory,
1766 std::move(exceptionTags), std::move(tables),
1767 std::move(maybeDebug));
1768 if (!instance) {
1769 return nullptr;
1770 }
1771
1772 InitReservedSlot(obj, INSTANCE_SLOT, instance,
1773 MemoryUse::WasmInstanceInstance);
1774 MOZ_ASSERT(!obj->isNewborn());
1775 }
1776
1777 if (!instance->init(cx, funcImports, globalImportValues, globalObjs,
1778 dataSegments, elemSegments)) {
1779 return nullptr;
1780 }
1781
1782 return obj;
1783 }
1784
initExportsObj(JSObject & exportsObj)1785 void WasmInstanceObject::initExportsObj(JSObject& exportsObj) {
1786 MOZ_ASSERT(getReservedSlot(EXPORTS_OBJ_SLOT).isUndefined());
1787 setReservedSlot(EXPORTS_OBJ_SLOT, ObjectValue(exportsObj));
1788 }
1789
GetImportArg(JSContext * cx,CallArgs callArgs,MutableHandleObject importObj)1790 static bool GetImportArg(JSContext* cx, CallArgs callArgs,
1791 MutableHandleObject importObj) {
1792 if (!callArgs.get(1).isUndefined()) {
1793 if (!callArgs[1].isObject()) {
1794 return ThrowBadImportArg(cx);
1795 }
1796 importObj.set(&callArgs[1].toObject());
1797 }
1798 return true;
1799 }
1800
1801 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)1802 bool WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp) {
1803 CallArgs args = CallArgsFromVp(argc, vp);
1804
1805 Log(cx, "sync new Instance() started");
1806
1807 if (!ThrowIfNotConstructing(cx, args, "Instance")) {
1808 return false;
1809 }
1810
1811 if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1)) {
1812 return false;
1813 }
1814
1815 const Module* module;
1816 if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
1817 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1818 JSMSG_WASM_BAD_MOD_ARG);
1819 return false;
1820 }
1821
1822 RootedObject importObj(cx);
1823 if (!GetImportArg(cx, args, &importObj)) {
1824 return false;
1825 }
1826
1827 RootedObject instanceProto(cx);
1828 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmInstance,
1829 &instanceProto)) {
1830 return false;
1831 }
1832 if (!instanceProto) {
1833 instanceProto =
1834 GlobalObject::getOrCreatePrototype(cx, JSProto_WasmInstance);
1835 }
1836
1837 Rooted<ImportValues> imports(cx);
1838 if (!GetImports(cx, *module, importObj, imports.address())) {
1839 return false;
1840 }
1841
1842 RootedWasmInstanceObject instanceObj(cx);
1843 if (!module->instantiate(cx, imports.get(), instanceProto, &instanceObj)) {
1844 return false;
1845 }
1846
1847 Log(cx, "sync new Instance() succeeded");
1848
1849 args.rval().setObject(*instanceObj);
1850 return true;
1851 }
1852
instance() const1853 Instance& WasmInstanceObject::instance() const {
1854 MOZ_ASSERT(!isNewborn());
1855 return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate();
1856 }
1857
exportsObj() const1858 JSObject& WasmInstanceObject::exportsObj() const {
1859 return getReservedSlot(EXPORTS_OBJ_SLOT).toObject();
1860 }
1861
exports() const1862 WasmInstanceObject::ExportMap& WasmInstanceObject::exports() const {
1863 return *(ExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate();
1864 }
1865
scopes() const1866 WasmInstanceObject::UnspecifiedScopeMap& WasmInstanceObject::scopes() const {
1867 return *(UnspecifiedScopeMap*)(getReservedSlot(SCOPES_SLOT).toPrivate());
1868 }
1869
indirectGlobals() const1870 WasmInstanceObject::GlobalObjectVector& WasmInstanceObject::indirectGlobals()
1871 const {
1872 return *(GlobalObjectVector*)getReservedSlot(GLOBALS_SLOT).toPrivate();
1873 }
1874
WasmCall(JSContext * cx,unsigned argc,Value * vp)1875 static bool WasmCall(JSContext* cx, unsigned argc, Value* vp) {
1876 CallArgs args = CallArgsFromVp(argc, vp);
1877 RootedFunction callee(cx, &args.callee().as<JSFunction>());
1878
1879 Instance& instance = ExportedFunctionToInstance(callee);
1880 uint32_t funcIndex = ExportedFunctionToFuncIndex(callee);
1881 return instance.callExport(cx, funcIndex, args);
1882 }
1883
1884 /*
1885 * [SMDOC] Exported wasm functions and the jit-entry stubs
1886 *
1887 * ## The kinds of exported functions
1888 *
1889 * There are several kinds of exported wasm functions. /Explicitly/ exported
1890 * functions are:
1891 *
1892 * - any wasm function exported via the export section
1893 * - any asm.js export
1894 * - the module start function
1895 *
1896 * There are also /implicitly/ exported functions, these are the functions whose
1897 * indices in the module are referenced outside the code segment, eg, in element
1898 * segments and in global initializers.
1899 *
1900 * ## Wasm functions as JSFunctions
1901 *
1902 * Any exported function can be manipulated by JS and wasm code, and to both the
1903 * exported function is represented as a JSFunction. To JS, that means that the
1904 * function can be called in the same way as any other JSFunction. To Wasm, it
1905 * means that the function is a reference with the same representation as
1906 * externref.
1907 *
1908 * However, the JSFunction object is created only when the function value is
1909 * actually exposed to JS the first time. The creation is performed by
1910 * getExportedFunction(), below, as follows:
1911 *
1912 * - a function exported via the export section (or from asm.js) is created
1913 * when the export object is created, which happens at instantiation time.
1914 *
1915 * - a function implicitly exported via a table is created when the table
1916 * element is read (by JS or wasm) and a function value is needed to
1917 * represent that value. Functions stored in tables by initializers have a
1918 * special representation that does not require the function object to be
1919 * created.
1920 *
1921 * - a function implicitly exported via a global initializer is created when
1922 * the global is initialized.
1923 *
1924 * - a function referenced from a ref.func instruction in code is created when
1925 * that instruction is executed the first time.
1926 *
1927 * The JSFunction representing a wasm function never changes: every reference to
1928 * the wasm function that exposes the JSFunction gets the same JSFunction. In
1929 * particular, imported functions already have a JSFunction representation (from
1930 * JS or from their home module), and will be exposed using that representation.
1931 *
1932 * The mapping from a wasm function to its JSFunction is instance-specific, and
1933 * held in a hashmap in the instance. If a module is shared across multiple
1934 * instances, possibly in multiple threads, each instance will have its own
1935 * JSFunction representing the wasm function.
1936 *
1937 * ## Stubs -- interpreter, eager, lazy, provisional, and absent
1938 *
1939 * While a Wasm exported function is just a JSFunction, the internal wasm ABI is
1940 * neither the C++ ABI nor the JS JIT ABI, so there needs to be an extra step
1941 * when C++ or JS JIT code calls wasm code. For this, execution passes through
1942 * a stub that is adapted to both the JS caller and the wasm callee.
1943 *
1944 * ### Interpreter stubs and jit-entry stubs
1945 *
1946 * When JS interpreted code calls a wasm function, we end up in
1947 * Instance::callExport() to execute the call. This function must enter wasm,
1948 * and to do this it uses a stub that is specific to the wasm function (see
1949 * GenerateInterpEntry) that is callable with the C++ interpreter ABI and which
1950 * will convert arguments as necessary and enter compiled wasm code.
1951 *
1952 * The interpreter stub is created eagerly, when the module is compiled.
1953 *
1954 * However, the interpreter call path is slow, and when JS jitted code calls
1955 * wasm we want to do better. In this case, there is a different, optimized
1956 * stub that is to be invoked, and it uses the JIT ABI. This is the jit-entry
1957 * stub for the function. Jitted code will call a wasm function's jit-entry
1958 * stub to invoke the function with the JIT ABI. The stub will adapt the call
1959 * to the wasm ABI.
1960 *
1961 * Some jit-entry stubs are created eagerly and some are created lazily.
1962 *
1963 * ### Eager jit-entry stubs
1964 *
1965 * The explicitly exported functions have stubs created for them eagerly. Eager
1966 * stubs are created with their tier when the module is compiled, see
1967 * ModuleGenerator::finishCodeTier(), which calls wasm::GenerateStubs(), which
1968 * generates stubs for functions with eager stubs.
1969 *
1970 * An eager stub for tier-1 is upgraded to tier-2 if the module tiers up, see
1971 * below.
1972 *
1973 * ### Lazy jit-entry stubs
1974 *
1975 * Stubs are created lazily for all implicitly exported functions. These
1976 * functions may flow out to JS, but will only need a stub if they are ever
1977 * called from jitted code. (That's true for explicitly exported functions too,
1978 * but for them the presumption is that they will be called.)
1979 *
1980 * Lazy stubs are created only when they are needed, and they are /doubly/ lazy,
1981 * see getExportedFunction(), below: A function implicitly exported via a table
1982 * or global may be manipulated eagerly by host code without actually being
1983 * called (maybe ever), so we do not generate a lazy stub when the function
1984 * object escapes to JS, but instead delay stub generation until the function is
1985 * actually called.
1986 *
1987 * ### The provisional lazy jit-entry stub
1988 *
1989 * However, JS baseline compilation needs to have a stub to start with in order
1990 * to allow it to attach CacheIR data to the call (or it deoptimizes the call as
1991 * a C++ call). Thus when the JSFunction for the wasm export is retrieved by JS
1992 * code, a /provisional/ lazy jit-entry stub is associated with the function.
1993 * The stub will invoke the wasm function on the slow interpreter path via
1994 * callExport - if the function is ever called - and will cause a fast jit-entry
1995 * stub to be created at the time of the call. The provisional lazy stub is
1996 * shared globally, it contains no function-specific or context-specific data.
1997 *
1998 * Thus, the final lazy jit-entry stubs are eventually created by
1999 * Instance::callExport, when a call is routed through it on the slow path for
2000 * any of the reasons given above.
2001 *
2002 * ### Absent jit-entry stubs
2003 *
2004 * Some functions never get jit-entry stubs. The predicate canHaveJitEntry()
2005 * determines if a wasm function gets a stub, and it will deny this if the
2006 * function's signature exposes non-JS-compatible types (such as v128) or if
2007 * stub optimization has been disabled by a jit option. Calls to these
2008 * functions will continue to go via callExport and use the slow interpreter
2009 * stub.
2010 *
2011 * ## The jit-entry jump table
2012 *
2013 * The mapping from the exported function to its jit-entry stub is implemented
2014 * by the jit-entry jump table in the JumpTables object (see WasmCode.h). The
2015 * jit-entry jump table entry for a function holds a stub that the jit can call
2016 * to perform fast calls.
2017 *
2018 * While there is a single contiguous jump table, it has two logical sections:
2019 * one for eager stubs, and one for lazy stubs. These sections are initialized
2020 * and updated separately, using logic that is specific to each section.
2021 *
2022 * The value of the table element for an eager stub is a pointer to the stub
2023 * code in the current tier. The pointer is installed just after the creation
2024 * of the stub, before any code in the module is executed. If the module later
2025 * tiers up, the eager jit-entry stub for tier-1 code is replaced by one for
2026 * tier-2 code, see the next section.
2027 *
2028 * Initially the value of the jump table element for a lazy stub is null.
2029 *
2030 * If the function is retrieved by JS (by getExportedFunction()) and is not
2031 * barred from having a jit-entry, then the stub is upgraded to the shared
2032 * provisional lazy jit-entry stub. This upgrade happens to be racy if the
2033 * module is shared, and so the update is atomic and only happens if the entry
2034 * is already null. Since the provisional lazy stub is shared, this is fine; if
2035 * several threads try to upgrade at the same time, it is to the same shared
2036 * value.
2037 *
2038 * If the retrieved function is later invoked (via callExport()), the stub is
2039 * upgraded to an actual jit-entry stub for the current code tier, again if the
2040 * function is allowed to have a jit-entry. This is not racy -- though multiple
2041 * threads can be trying to create a jit-entry stub at the same time, they do so
2042 * under a lock and only the first to take the lock will be allowed to create a
2043 * stub, the others will reuse the first-installed stub.
2044 *
2045 * If the module later tiers up, the lazy jit-entry stub for tier-1 code (if it
2046 * exists) is replaced by one for tier-2 code, see the next section.
2047 *
2048 * (Note, the InterpEntry stub is never stored in the jit-entry table, as it
2049 * uses the C++ ABI, not the JIT ABI. It is accessible through the
2050 * FunctionEntry.)
2051 *
2052 * ### Interaction of the jit-entry jump table and tiering
2053 *
2054 * (For general info about tiering, see the comment in WasmCompile.cpp.)
2055 *
2056 * The jit-entry stub, whether eager or lazy, is specific to a code tier - a
2057 * stub will invoke the code for its function for the tier. When we tier up,
2058 * new jit-entry stubs must be created that reference tier-2 code, and must then
2059 * be patched into the jit-entry table. The complication here is that, since
2060 * the jump table is shared with its code between instances on multiple threads,
2061 * tier-1 code is running on other threads and new tier-1 specific jit-entry
2062 * stubs may be created concurrently with trying to create the tier-2 stubs on
2063 * the thread that performs the tiering-up. Indeed, there may also be
2064 * concurrent attempts to upgrade null jit-entries to the provisional lazy stub.
2065 *
2066 * Eager stubs:
2067 *
2068 * - Eager stubs for tier-2 code are patched in racily by Module::finishTier2()
2069 * along with code pointers for tiering; nothing conflicts with these writes.
2070 *
2071 * Lazy stubs:
2072 *
2073 * - An upgrade from a null entry to a lazy provisional stub is atomic and can
2074 * only happen if the entry is null, and it only happens in
2075 * getExportedFunction(). No lazy provisional stub will be installed if
2076 * there's another stub present.
2077 *
2078 * - The lazy tier-appropriate stub is installed by callExport() (really by
2079 * EnsureEntryStubs()) during the first invocation of the exported function
2080 * that reaches callExport(). That invocation must be from within JS, and so
2081 * the jit-entry element can't be null, because a prior getExportedFunction()
2082 * will have ensured that it is not: the lazy provisional stub will have been
2083 * installed. Hence the installing of the lazy tier-appropriate stub does
2084 * not race with the installing of the lazy provisional stub.
2085 *
2086 * - A lazy tier-1 stub is upgraded to a lazy tier-2 stub by
2087 * Module::finishTier2(). The upgrade needs to ensure that all tier-1 stubs
2088 * are upgraded, and that once the upgrade is finished, callExport() will
2089 * only create tier-2 lazy stubs. (This upgrading does not upgrade lazy
2090 * provisional stubs or absent stubs.)
2091 *
2092 * The locking protocol ensuring that all stubs are upgraded properly and
2093 * that the system switches to creating tier-2 stubs is implemented in
2094 * Module::finishTier2() and EnsureEntryStubs():
2095 *
2096 * There are two locks, one per code tier.
2097 *
2098 * EnsureEntryStubs() is attempting to create a tier-appropriate lazy stub,
2099 * so it takes the lock for the current best tier, checks to see if there is
2100 * a stub, and exits if there is. If the tier changed racily it takes the
2101 * other lock too, since that is now the lock for the best tier. Then it
2102 * creates the stub, installs it, and releases the locks. Thus at most one
2103 * stub per tier can be created at a time.
2104 *
2105 * Module::finishTier2() takes both locks (tier-1 before tier-2), thus
2106 * preventing EnsureEntryStubs() from creating stubs while stub upgrading is
2107 * going on, and itself waiting until EnsureEntryStubs() is not active. Once
2108 * it has both locks, it upgrades all lazy stubs and makes tier-2 the new
2109 * best tier. Should EnsureEntryStubs subsequently enter, it will find that
2110 * a stub already exists at tier-2 and will exit early.
2111 *
2112 * (It would seem that the locking protocol could be simplified a little by
2113 * having only one lock, hanging off the Code object, or by unconditionally
2114 * taking both locks in EnsureEntryStubs(). However, in some cases where we
2115 * acquire a lock the Code object is not readily available, so plumbing would
2116 * have to be added, and in EnsureEntryStubs(), there are sometimes not two code
2117 * tiers.)
2118 *
2119 * ## Stub lifetimes and serialization
2120 *
2121 * Eager jit-entry stub code, along with stub code for import functions, is
2122 * serialized along with the tier-2 code for the module.
2123 *
2124 * Lazy stub code and thunks for builtin functions (including the provisional
2125 * lazy jit-entry stub) are never serialized.
2126 */
2127
2128 /* static */
getExportedFunction(JSContext * cx,HandleWasmInstanceObject instanceObj,uint32_t funcIndex,MutableHandleFunction fun)2129 bool WasmInstanceObject::getExportedFunction(
2130 JSContext* cx, HandleWasmInstanceObject instanceObj, uint32_t funcIndex,
2131 MutableHandleFunction fun) {
2132 if (ExportMap::Ptr p = instanceObj->exports().lookup(funcIndex)) {
2133 fun.set(p->value());
2134 return true;
2135 }
2136
2137 const Instance& instance = instanceObj->instance();
2138 const FuncExport& funcExport =
2139 instance.metadata(instance.code().bestTier()).lookupFuncExport(funcIndex);
2140 unsigned numArgs = funcExport.funcType().args().length();
2141
2142 if (instance.isAsmJS()) {
2143 // asm.js needs to act like a normal JS function which means having the
2144 // name from the original source and being callable as a constructor.
2145 RootedAtom name(cx, instance.getFuncDisplayAtom(cx, funcIndex));
2146 if (!name) {
2147 return false;
2148 }
2149 fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name,
2150 gc::AllocKind::FUNCTION_EXTENDED,
2151 TenuredObject, FunctionFlags::ASMJS_CTOR));
2152 if (!fun) {
2153 return false;
2154 }
2155
2156 // asm.js does not support jit entries.
2157 fun->setWasmFuncIndex(funcIndex);
2158 } else {
2159 RootedAtom name(cx, NumberToAtom(cx, funcIndex));
2160 if (!name) {
2161 return false;
2162 }
2163
2164 fun.set(NewNativeFunction(cx, WasmCall, numArgs, name,
2165 gc::AllocKind::FUNCTION_EXTENDED, TenuredObject,
2166 FunctionFlags::WASM));
2167 if (!fun) {
2168 return false;
2169 }
2170
2171 // Some applications eagerly access all table elements which currently
2172 // triggers worst-case behavior for lazy stubs, since each will allocate a
2173 // separate 4kb code page. Most eagerly-accessed functions are not called,
2174 // so use a shared, provisional (and slow) lazy stub as JitEntry and wait
2175 // until Instance::callExport() to create the fast entry stubs.
2176 if (funcExport.canHaveJitEntry()) {
2177 if (!funcExport.hasEagerStubs()) {
2178 if (!EnsureBuiltinThunksInitialized()) {
2179 return false;
2180 }
2181 void* provisionalLazyJitEntryStub = ProvisionalLazyJitEntryStub();
2182 MOZ_ASSERT(provisionalLazyJitEntryStub);
2183 instance.code().setJitEntryIfNull(funcIndex,
2184 provisionalLazyJitEntryStub);
2185 }
2186 fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex));
2187 } else {
2188 fun->setWasmFuncIndex(funcIndex);
2189 }
2190 }
2191
2192 fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT,
2193 ObjectValue(*instanceObj));
2194
2195 void* tlsData = instanceObj->instance().tlsData();
2196 fun->setExtendedSlot(FunctionExtended::WASM_TLSDATA_SLOT,
2197 PrivateValue(tlsData));
2198
2199 if (!instanceObj->exports().putNew(funcIndex, fun)) {
2200 ReportOutOfMemory(cx);
2201 return false;
2202 }
2203
2204 return true;
2205 }
2206
getExportedFunctionCodeRange(JSFunction * fun,Tier tier)2207 const CodeRange& WasmInstanceObject::getExportedFunctionCodeRange(
2208 JSFunction* fun, Tier tier) {
2209 uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
2210 MOZ_ASSERT(exports().lookup(funcIndex)->value() == fun);
2211 const MetadataTier& metadata = instance().metadata(tier);
2212 return metadata.codeRange(metadata.lookupFuncExport(funcIndex));
2213 }
2214
2215 /* static */
getScope(JSContext * cx,HandleWasmInstanceObject instanceObj)2216 WasmInstanceScope* WasmInstanceObject::getScope(
2217 JSContext* cx, HandleWasmInstanceObject instanceObj) {
2218 if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined()) {
2219 return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT)
2220 .toGCThing();
2221 }
2222
2223 Rooted<WasmInstanceScope*> instanceScope(
2224 cx, WasmInstanceScope::create(cx, instanceObj));
2225 if (!instanceScope) {
2226 return nullptr;
2227 }
2228
2229 instanceObj->setReservedSlot(INSTANCE_SCOPE_SLOT,
2230 PrivateGCThingValue(instanceScope));
2231
2232 return instanceScope;
2233 }
2234
2235 /* static */
getFunctionScope(JSContext * cx,HandleWasmInstanceObject instanceObj,uint32_t funcIndex)2236 WasmFunctionScope* WasmInstanceObject::getFunctionScope(
2237 JSContext* cx, HandleWasmInstanceObject instanceObj, uint32_t funcIndex) {
2238 if (auto p =
2239 instanceObj->scopes().asWasmFunctionScopeMap().lookup(funcIndex)) {
2240 return p->value();
2241 }
2242
2243 Rooted<WasmInstanceScope*> instanceScope(
2244 cx, WasmInstanceObject::getScope(cx, instanceObj));
2245 if (!instanceScope) {
2246 return nullptr;
2247 }
2248
2249 Rooted<WasmFunctionScope*> funcScope(
2250 cx, WasmFunctionScope::create(cx, instanceScope, funcIndex));
2251 if (!funcScope) {
2252 return nullptr;
2253 }
2254
2255 if (!instanceObj->scopes().asWasmFunctionScopeMap().putNew(funcIndex,
2256 funcScope)) {
2257 ReportOutOfMemory(cx);
2258 return nullptr;
2259 }
2260
2261 return funcScope;
2262 }
2263
IsWasmExportedFunction(JSFunction * fun)2264 bool wasm::IsWasmExportedFunction(JSFunction* fun) {
2265 return fun->kind() == FunctionFlags::Wasm;
2266 }
2267
ExportedFunctionToInstance(JSFunction * fun)2268 Instance& wasm::ExportedFunctionToInstance(JSFunction* fun) {
2269 return ExportedFunctionToInstanceObject(fun)->instance();
2270 }
2271
ExportedFunctionToInstanceObject(JSFunction * fun)2272 WasmInstanceObject* wasm::ExportedFunctionToInstanceObject(JSFunction* fun) {
2273 MOZ_ASSERT(fun->kind() == FunctionFlags::Wasm ||
2274 fun->kind() == FunctionFlags::AsmJS);
2275 const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT);
2276 return &v.toObject().as<WasmInstanceObject>();
2277 }
2278
ExportedFunctionToFuncIndex(JSFunction * fun)2279 uint32_t wasm::ExportedFunctionToFuncIndex(JSFunction* fun) {
2280 Instance& instance = ExportedFunctionToInstanceObject(fun)->instance();
2281 return instance.code().getFuncIndex(fun);
2282 }
2283
2284 // ============================================================================
2285 // WebAssembly.Memory class and methods
2286
2287 const JSClassOps WasmMemoryObject::classOps_ = {
2288 nullptr, // addProperty
2289 nullptr, // delProperty
2290 nullptr, // enumerate
2291 nullptr, // newEnumerate
2292 nullptr, // resolve
2293 nullptr, // mayResolve
2294 WasmMemoryObject::finalize, // finalize
2295 nullptr, // call
2296 nullptr, // hasInstance
2297 nullptr, // construct
2298 nullptr, // trace
2299 };
2300
2301 const JSClass WasmMemoryObject::class_ = {
2302 "WebAssembly.Memory",
2303 JSCLASS_DELAY_METADATA_BUILDER |
2304 JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
2305 JSCLASS_FOREGROUND_FINALIZE,
2306 &WasmMemoryObject::classOps_, &WasmMemoryObject::classSpec_};
2307
2308 const JSClass& WasmMemoryObject::protoClass_ = PlainObject::class_;
2309
2310 static constexpr char WasmMemoryName[] = "Memory";
2311
2312 const ClassSpec WasmMemoryObject::classSpec_ = {
2313 CreateWasmConstructor<WasmMemoryObject, WasmMemoryName>,
2314 GenericCreatePrototype<WasmMemoryObject>,
2315 WasmMemoryObject::static_methods,
2316 nullptr,
2317 WasmMemoryObject::methods,
2318 WasmMemoryObject::properties,
2319 nullptr,
2320 ClassSpec::DontDefineConstructor};
2321
2322 /* static */
finalize(JSFreeOp * fop,JSObject * obj)2323 void WasmMemoryObject::finalize(JSFreeOp* fop, JSObject* obj) {
2324 WasmMemoryObject& memory = obj->as<WasmMemoryObject>();
2325 if (memory.hasObservers()) {
2326 fop->delete_(obj, &memory.observers(), MemoryUse::WasmMemoryObservers);
2327 }
2328 }
2329
2330 /* static */
create(JSContext * cx,HandleArrayBufferObjectMaybeShared buffer,HandleObject proto)2331 WasmMemoryObject* WasmMemoryObject::create(
2332 JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
2333 HandleObject proto) {
2334 AutoSetNewObjectMetadata metadata(cx);
2335 auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto);
2336 if (!obj) {
2337 return nullptr;
2338 }
2339
2340 obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer));
2341 MOZ_ASSERT(!obj->hasObservers());
2342 return obj;
2343 }
2344
2345 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)2346 bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
2347 CallArgs args = CallArgsFromVp(argc, vp);
2348
2349 if (!ThrowIfNotConstructing(cx, args, "Memory")) {
2350 return false;
2351 }
2352
2353 if (!args.requireAtLeast(cx, "WebAssembly.Memory", 1)) {
2354 return false;
2355 }
2356
2357 if (!args.get(0).isObject()) {
2358 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2359 JSMSG_WASM_BAD_DESC_ARG, "memory");
2360 return false;
2361 }
2362
2363 RootedObject obj(cx, &args[0].toObject());
2364 Limits limits;
2365 if (!GetLimits(cx, obj, MaxMemory32LimitField, "Memory", &limits,
2366 Shareable::True)) {
2367 return false;
2368 }
2369
2370 if (Pages(limits.initial) > MaxMemory32Pages()) {
2371 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2372 JSMSG_WASM_MEM_IMP_LIMIT);
2373 return false;
2374 }
2375 MemoryDesc memory(MemoryKind::Memory32, limits);
2376
2377 RootedArrayBufferObjectMaybeShared buffer(cx);
2378 if (!CreateWasmBuffer32(cx, memory, &buffer)) {
2379 return false;
2380 }
2381
2382 RootedObject proto(cx);
2383 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmMemory,
2384 &proto)) {
2385 return false;
2386 }
2387 if (!proto) {
2388 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmMemory);
2389 }
2390
2391 RootedWasmMemoryObject memoryObj(cx,
2392 WasmMemoryObject::create(cx, buffer, proto));
2393 if (!memoryObj) {
2394 return false;
2395 }
2396
2397 args.rval().setObject(*memoryObj);
2398 return true;
2399 }
2400
IsMemory(HandleValue v)2401 static bool IsMemory(HandleValue v) {
2402 return v.isObject() && v.toObject().is<WasmMemoryObject>();
2403 }
2404
2405 /* static */
bufferGetterImpl(JSContext * cx,const CallArgs & args)2406 bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) {
2407 RootedWasmMemoryObject memoryObj(
2408 cx, &args.thisv().toObject().as<WasmMemoryObject>());
2409 RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer());
2410
2411 if (memoryObj->isShared()) {
2412 size_t memoryLength = memoryObj->volatileMemoryLength();
2413 MOZ_ASSERT(memoryLength >= buffer->byteLength());
2414
2415 if (memoryLength > buffer->byteLength()) {
2416 RootedSharedArrayBufferObject newBuffer(
2417 cx, SharedArrayBufferObject::New(
2418 cx, memoryObj->sharedArrayRawBuffer(), memoryLength));
2419 if (!newBuffer) {
2420 return false;
2421 }
2422 // OK to addReference after we try to allocate because the memoryObj
2423 // keeps the rawBuffer alive.
2424 if (!memoryObj->sharedArrayRawBuffer()->addReference()) {
2425 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2426 JSMSG_SC_SAB_REFCNT_OFLO);
2427 return false;
2428 }
2429 buffer = newBuffer;
2430 memoryObj->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuffer));
2431 }
2432 }
2433
2434 args.rval().setObject(*buffer);
2435 return true;
2436 }
2437
2438 /* static */
bufferGetter(JSContext * cx,unsigned argc,Value * vp)2439 bool WasmMemoryObject::bufferGetter(JSContext* cx, unsigned argc, Value* vp) {
2440 CallArgs args = CallArgsFromVp(argc, vp);
2441 return CallNonGenericMethod<IsMemory, bufferGetterImpl>(cx, args);
2442 }
2443
2444 const JSPropertySpec WasmMemoryObject::properties[] = {
2445 JS_PSG("buffer", WasmMemoryObject::bufferGetter, JSPROP_ENUMERATE),
2446 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Memory", JSPROP_READONLY),
2447 JS_PS_END};
2448
2449 /* static */
growImpl(JSContext * cx,const CallArgs & args)2450 bool WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args) {
2451 RootedWasmMemoryObject memory(
2452 cx, &args.thisv().toObject().as<WasmMemoryObject>());
2453
2454 if (!args.requireAtLeast(cx, "WebAssembly.Memory.grow", 1)) {
2455 return false;
2456 }
2457
2458 uint32_t delta;
2459 if (!EnforceRangeU32(cx, args.get(0), "Memory", "grow delta", &delta)) {
2460 return false;
2461 }
2462
2463 uint32_t ret = grow(memory, delta, cx);
2464
2465 if (ret == uint32_t(-1)) {
2466 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
2467 "memory");
2468 return false;
2469 }
2470
2471 args.rval().setInt32(ret);
2472 return true;
2473 }
2474
2475 /* static */
grow(JSContext * cx,unsigned argc,Value * vp)2476 bool WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp) {
2477 CallArgs args = CallArgsFromVp(argc, vp);
2478 return CallNonGenericMethod<IsMemory, growImpl>(cx, args);
2479 }
2480
2481 const JSFunctionSpec WasmMemoryObject::methods[] = {
2482 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
2483 JS_FN("type", WasmMemoryObject::type, 0, JSPROP_ENUMERATE),
2484 #endif
2485 JS_FN("grow", WasmMemoryObject::grow, 1, JSPROP_ENUMERATE), JS_FS_END};
2486
2487 const JSFunctionSpec WasmMemoryObject::static_methods[] = {JS_FS_END};
2488
buffer() const2489 ArrayBufferObjectMaybeShared& WasmMemoryObject::buffer() const {
2490 return getReservedSlot(BUFFER_SLOT)
2491 .toObject()
2492 .as<ArrayBufferObjectMaybeShared>();
2493 }
2494
sharedArrayRawBuffer() const2495 SharedArrayRawBuffer* WasmMemoryObject::sharedArrayRawBuffer() const {
2496 MOZ_ASSERT(isShared());
2497 return buffer().as<SharedArrayBufferObject>().rawBufferObject();
2498 }
2499
2500 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
typeImpl(JSContext * cx,const CallArgs & args)2501 bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) {
2502 RootedWasmMemoryObject memoryObj(
2503 cx, &args.thisv().toObject().as<WasmMemoryObject>());
2504 Rooted<IdValueVector> props(cx, IdValueVector(cx));
2505
2506 Maybe<Pages> maxPages = memoryObj->maxPages();
2507 if (maxPages.isSome()) {
2508 uint32_t maxPages32 = mozilla::AssertedCast<uint32_t>(maxPages->value());
2509 if (!props.append(IdValuePair(NameToId(cx->names().maximum),
2510 Int32Value(maxPages32)))) {
2511 return false;
2512 }
2513 }
2514
2515 uint32_t minimumPages =
2516 mozilla::AssertedCast<uint32_t>(memoryObj->volatilePages().value());
2517 if (!props.append(IdValuePair(NameToId(cx->names().minimum),
2518 Int32Value(minimumPages)))) {
2519 return false;
2520 }
2521
2522 if (!props.append(IdValuePair(NameToId(cx->names().shared),
2523 BooleanValue(memoryObj->isShared())))) {
2524 return false;
2525 }
2526
2527 JSObject* memoryType = NewPlainObjectWithProperties(
2528 cx, props.begin(), props.length(), GenericObject);
2529 if (!memoryType) {
2530 return false;
2531 }
2532 args.rval().setObject(*memoryType);
2533 return true;
2534 }
2535
type(JSContext * cx,unsigned argc,Value * vp)2536 bool WasmMemoryObject::type(JSContext* cx, unsigned argc, Value* vp) {
2537 CallArgs args = CallArgsFromVp(argc, vp);
2538 return CallNonGenericMethod<IsMemory, typeImpl>(cx, args);
2539 }
2540 #endif
2541
volatileMemoryLength() const2542 size_t WasmMemoryObject::volatileMemoryLength() const {
2543 if (isShared()) {
2544 return sharedArrayRawBuffer()->volatileByteLength();
2545 }
2546 return buffer().byteLength();
2547 }
2548
volatilePages() const2549 wasm::Pages WasmMemoryObject::volatilePages() const {
2550 if (isShared()) {
2551 return sharedArrayRawBuffer()->volatileWasmPages();
2552 }
2553 return buffer().wasmPages();
2554 }
2555
maxPages() const2556 Maybe<wasm::Pages> WasmMemoryObject::maxPages() const {
2557 if (isShared()) {
2558 return Some(sharedArrayRawBuffer()->wasmMaxPages());
2559 }
2560 return buffer().wasmMaxPages();
2561 }
2562
isShared() const2563 bool WasmMemoryObject::isShared() const {
2564 return buffer().is<SharedArrayBufferObject>();
2565 }
2566
hasObservers() const2567 bool WasmMemoryObject::hasObservers() const {
2568 return !getReservedSlot(OBSERVERS_SLOT).isUndefined();
2569 }
2570
observers() const2571 WasmMemoryObject::InstanceSet& WasmMemoryObject::observers() const {
2572 MOZ_ASSERT(hasObservers());
2573 return *reinterpret_cast<InstanceSet*>(
2574 getReservedSlot(OBSERVERS_SLOT).toPrivate());
2575 }
2576
getOrCreateObservers(JSContext * cx)2577 WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers(
2578 JSContext* cx) {
2579 if (!hasObservers()) {
2580 auto observers = MakeUnique<InstanceSet>(cx->zone(), cx->zone());
2581 if (!observers) {
2582 ReportOutOfMemory(cx);
2583 return nullptr;
2584 }
2585
2586 InitReservedSlot(this, OBSERVERS_SLOT, observers.release(),
2587 MemoryUse::WasmMemoryObservers);
2588 }
2589
2590 return &observers();
2591 }
2592
isHuge() const2593 bool WasmMemoryObject::isHuge() const {
2594 #ifdef WASM_SUPPORTS_HUGE_MEMORY
2595 // TODO: Turn this into a static_assert, if we are able to make
2596 // MaxMemory32Bytes() constexpr once the dust settles for the 4GB heaps.
2597 MOZ_ASSERT(MaxMemory32Bytes() < HugeMappedSize,
2598 "Non-huge buffer may be confused as huge");
2599 return buffer().wasmMappedSize() >= HugeMappedSize;
2600 #else
2601 return false;
2602 #endif
2603 }
2604
movingGrowable() const2605 bool WasmMemoryObject::movingGrowable() const {
2606 return !isHuge() && !buffer().wasmMaxPages();
2607 }
2608
boundsCheckLimit() const2609 size_t WasmMemoryObject::boundsCheckLimit() const {
2610 if (!buffer().isWasm() || isHuge()) {
2611 return buffer().byteLength();
2612 }
2613 size_t mappedSize = buffer().wasmMappedSize();
2614 #if !defined(JS_64BIT) || defined(ENABLE_WASM_CRANELIFT)
2615 // See clamping performed in CreateSpecificWasmBuffer(). On 32-bit systems
2616 // and on 64-bit with Cranelift, we do not want to overflow a uint32_t. For
2617 // the other 64-bit compilers, all constraints are implied by the largest
2618 // accepted value for a memory's max field.
2619 MOZ_ASSERT(mappedSize < UINT32_MAX);
2620 #endif
2621 MOZ_ASSERT(mappedSize % wasm::PageSize == 0);
2622 MOZ_ASSERT(mappedSize >= wasm::GuardSize);
2623 MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize));
2624 size_t limit = mappedSize - wasm::GuardSize;
2625 MOZ_ASSERT(limit <= MaxMemory32BoundsCheckLimit());
2626 return limit;
2627 }
2628
addMovingGrowObserver(JSContext * cx,WasmInstanceObject * instance)2629 bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx,
2630 WasmInstanceObject* instance) {
2631 MOZ_ASSERT(movingGrowable());
2632
2633 InstanceSet* observers = getOrCreateObservers(cx);
2634 if (!observers) {
2635 return false;
2636 }
2637
2638 if (!observers->putNew(instance)) {
2639 ReportOutOfMemory(cx);
2640 return false;
2641 }
2642
2643 return true;
2644 }
2645
2646 /* static */
growShared(HandleWasmMemoryObject memory,uint32_t delta)2647 uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
2648 uint32_t delta) {
2649 SharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer();
2650 SharedArrayRawBuffer::Lock lock(rawBuf);
2651
2652 Pages oldNumPages = rawBuf->volatileWasmPages();
2653 Pages newPages = oldNumPages;
2654 if (!newPages.checkedIncrement(Pages(delta))) {
2655 return -1;
2656 }
2657
2658 // Always check against the max here, do not rely on the buffer resizers to
2659 // use the correct limit, they don't have enough context.
2660 if (newPages > MaxMemory32Pages()) {
2661 return -1;
2662 }
2663
2664 if (newPages > rawBuf->wasmMaxPages()) {
2665 return -1;
2666 }
2667
2668 if (!rawBuf->wasmGrowToPagesInPlace(lock, newPages)) {
2669 return -1;
2670 }
2671 // New buffer objects will be created lazily in all agents (including in
2672 // this agent) by bufferGetterImpl, above, so no more work to do here.
2673
2674 // It is safe to cast to uint32_t, as oldNumPages was within our
2675 // implementation limits of MaxMemory32Pages(), which is within uint32_t.
2676 return uint32_t(oldNumPages.value());
2677 }
2678
2679 /* static */
grow(HandleWasmMemoryObject memory,uint32_t delta,JSContext * cx)2680 uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
2681 JSContext* cx) {
2682 if (memory->isShared()) {
2683 return growShared(memory, delta);
2684 }
2685
2686 RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>());
2687
2688 #if !defined(JS_64BIT) || defined(ENABLE_WASM_CRANELIFT)
2689 // TODO (large ArrayBuffer): For Cranelift, limit the memory size to something
2690 // that fits in a uint32_t. See more information at the definition of
2691 // MaxMemory32Bytes().
2692 //
2693 // TODO: Turn this into a static_assert, if we are able to make
2694 // MaxMemory32Bytes() constexpr once the dust settles for the 4GB heaps.
2695 MOZ_ASSERT(MaxMemory32Bytes() <= UINT32_MAX, "Avoid 32-bit overflows");
2696 #endif
2697
2698 Pages oldNumPages = oldBuf->wasmPages();
2699 Pages newPages = oldNumPages;
2700 if (!newPages.checkedIncrement(Pages(delta))) {
2701 return -1;
2702 }
2703
2704 // Always check against the max here, do not rely on the buffer resizers to
2705 // use the correct limit, they don't have enough context.
2706 if (newPages > MaxMemory32Pages()) {
2707 return -1;
2708 }
2709
2710 RootedArrayBufferObject newBuf(cx);
2711
2712 if (memory->movingGrowable()) {
2713 MOZ_ASSERT(!memory->isHuge());
2714 if (!ArrayBufferObject::wasmMovingGrowToPages(newPages, oldBuf, &newBuf,
2715 cx)) {
2716 return -1;
2717 }
2718 } else {
2719 if (Maybe<Pages> maxPages = oldBuf->wasmMaxPages()) {
2720 if (newPages > *maxPages) {
2721 return -1;
2722 }
2723 }
2724
2725 if (!ArrayBufferObject::wasmGrowToPagesInPlace(newPages, oldBuf, &newBuf,
2726 cx)) {
2727 return -1;
2728 }
2729 }
2730
2731 memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));
2732
2733 // Only notify moving-grow-observers after the BUFFER_SLOT has been updated
2734 // since observers will call buffer().
2735 if (memory->hasObservers()) {
2736 for (InstanceSet::Range r = memory->observers().all(); !r.empty();
2737 r.popFront()) {
2738 r.front()->instance().onMovingGrowMemory();
2739 }
2740 }
2741
2742 // It is safe to cast to uint32_t, as oldNumPages was within our
2743 // implementation limits of MaxMemory32Pages(), which is within uint32_t.
2744 return uint32_t(oldNumPages.value());
2745 }
2746
IsSharedWasmMemoryObject(JSObject * obj)2747 bool js::wasm::IsSharedWasmMemoryObject(JSObject* obj) {
2748 WasmMemoryObject* mobj = obj->maybeUnwrapIf<WasmMemoryObject>();
2749 return mobj && mobj->isShared();
2750 }
2751
2752 // ============================================================================
2753 // WebAssembly.Table class and methods
2754
2755 const JSClassOps WasmTableObject::classOps_ = {
2756 nullptr, // addProperty
2757 nullptr, // delProperty
2758 nullptr, // enumerate
2759 nullptr, // newEnumerate
2760 nullptr, // resolve
2761 nullptr, // mayResolve
2762 WasmTableObject::finalize, // finalize
2763 nullptr, // call
2764 nullptr, // hasInstance
2765 nullptr, // construct
2766 WasmTableObject::trace, // trace
2767 };
2768
2769 const JSClass WasmTableObject::class_ = {
2770 "WebAssembly.Table",
2771 JSCLASS_DELAY_METADATA_BUILDER |
2772 JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS) |
2773 JSCLASS_FOREGROUND_FINALIZE,
2774 &WasmTableObject::classOps_, &WasmTableObject::classSpec_};
2775
2776 const JSClass& WasmTableObject::protoClass_ = PlainObject::class_;
2777
2778 static constexpr char WasmTableName[] = "Table";
2779
2780 const ClassSpec WasmTableObject::classSpec_ = {
2781 CreateWasmConstructor<WasmTableObject, WasmTableName>,
2782 GenericCreatePrototype<WasmTableObject>,
2783 WasmTableObject::static_methods,
2784 nullptr,
2785 WasmTableObject::methods,
2786 WasmTableObject::properties,
2787 nullptr,
2788 ClassSpec::DontDefineConstructor};
2789
isNewborn() const2790 bool WasmTableObject::isNewborn() const {
2791 MOZ_ASSERT(is<WasmTableObject>());
2792 return getReservedSlot(TABLE_SLOT).isUndefined();
2793 }
2794
2795 /* static */
finalize(JSFreeOp * fop,JSObject * obj)2796 void WasmTableObject::finalize(JSFreeOp* fop, JSObject* obj) {
2797 WasmTableObject& tableObj = obj->as<WasmTableObject>();
2798 if (!tableObj.isNewborn()) {
2799 auto& table = tableObj.table();
2800 fop->release(obj, &table, table.gcMallocBytes(), MemoryUse::WasmTableTable);
2801 }
2802 }
2803
2804 /* static */
trace(JSTracer * trc,JSObject * obj)2805 void WasmTableObject::trace(JSTracer* trc, JSObject* obj) {
2806 WasmTableObject& tableObj = obj->as<WasmTableObject>();
2807 if (!tableObj.isNewborn()) {
2808 tableObj.table().tracePrivate(trc);
2809 }
2810 }
2811
2812 // Return the JS value to use when a parameter to a function requiring a table
2813 // value is omitted. An implementation of [1].
2814 //
2815 // [1]
2816 // https://webassembly.github.io/reference-types/js-api/index.html#defaultvalue
TableDefaultValue(wasm::RefType tableType)2817 static Value TableDefaultValue(wasm::RefType tableType) {
2818 return tableType.isExtern() ? UndefinedValue() : NullValue();
2819 }
2820
2821 /* static */
create(JSContext * cx,uint32_t initialLength,Maybe<uint32_t> maximumLength,wasm::RefType tableType,HandleObject proto)2822 WasmTableObject* WasmTableObject::create(JSContext* cx, uint32_t initialLength,
2823 Maybe<uint32_t> maximumLength,
2824 wasm::RefType tableType,
2825 HandleObject proto) {
2826 AutoSetNewObjectMetadata metadata(cx);
2827 RootedWasmTableObject obj(
2828 cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
2829 if (!obj) {
2830 return nullptr;
2831 }
2832
2833 MOZ_ASSERT(obj->isNewborn());
2834
2835 TableDesc td(tableType, initialLength, maximumLength, /*isAsmJS*/ false,
2836 /*importedOrExported=*/true);
2837
2838 SharedTable table = Table::create(cx, td, obj);
2839 if (!table) {
2840 ReportOutOfMemory(cx);
2841 return nullptr;
2842 }
2843
2844 size_t size = table->gcMallocBytes();
2845 InitReservedSlot(obj, TABLE_SLOT, table.forget().take(), size,
2846 MemoryUse::WasmTableTable);
2847
2848 MOZ_ASSERT(!obj->isNewborn());
2849 return obj;
2850 }
2851
2852 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)2853 bool WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) {
2854 CallArgs args = CallArgsFromVp(argc, vp);
2855
2856 if (!ThrowIfNotConstructing(cx, args, "Table")) {
2857 return false;
2858 }
2859
2860 if (!args.requireAtLeast(cx, "WebAssembly.Table", 1)) {
2861 return false;
2862 }
2863
2864 if (!args.get(0).isObject()) {
2865 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2866 JSMSG_WASM_BAD_DESC_ARG, "table");
2867 return false;
2868 }
2869
2870 RootedObject obj(cx, &args[0].toObject());
2871
2872 JSAtom* elementAtom = Atomize(cx, "element", strlen("element"));
2873 if (!elementAtom) {
2874 return false;
2875 }
2876 RootedId elementId(cx, AtomToId(elementAtom));
2877
2878 RootedValue elementVal(cx);
2879 if (!GetProperty(cx, obj, obj, elementId, &elementVal)) {
2880 return false;
2881 }
2882
2883 RootedString elementStr(cx, ToString(cx, elementVal));
2884 if (!elementStr) {
2885 return false;
2886 }
2887
2888 RootedLinearString elementLinearStr(cx, elementStr->ensureLinear(cx));
2889 if (!elementLinearStr) {
2890 return false;
2891 }
2892
2893 RefType tableType;
2894 if (StringEqualsLiteral(elementLinearStr, "anyfunc") ||
2895 StringEqualsLiteral(elementLinearStr, "funcref")) {
2896 tableType = RefType::func();
2897 } else if (StringEqualsLiteral(elementLinearStr, "externref")) {
2898 tableType = RefType::extern_();
2899 #ifdef ENABLE_WASM_GC
2900 } else if (StringEqualsLiteral(elementLinearStr, "eqref")) {
2901 if (!GcAvailable(cx)) {
2902 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2903 JSMSG_WASM_BAD_ELEMENT);
2904 return false;
2905 }
2906 tableType = RefType::eq();
2907 #endif
2908 } else {
2909 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2910 JSMSG_WASM_BAD_ELEMENT_GENERALIZED);
2911 return false;
2912 }
2913
2914 Limits limits;
2915 if (!GetLimits(cx, obj, MaxTableLimitField, "Table", &limits,
2916 Shareable::False)) {
2917 return false;
2918 }
2919
2920 if (limits.initial > MaxTableLength) {
2921 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2922 JSMSG_WASM_TABLE_IMP_LIMIT);
2923 return false;
2924 }
2925
2926 RootedObject proto(cx);
2927 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmTable,
2928 &proto)) {
2929 return false;
2930 }
2931 if (!proto) {
2932 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmTable);
2933 }
2934
2935 // The rest of the runtime expects table limits to be within a 32-bit range.
2936 static_assert(MaxTableLimitField <= UINT32_MAX, "invariant");
2937 uint32_t initialLength = uint32_t(limits.initial);
2938 Maybe<uint32_t> maximumLength;
2939 if (limits.maximum) {
2940 maximumLength = Some(uint32_t(*limits.maximum));
2941 }
2942
2943 RootedWasmTableObject table(
2944 cx, WasmTableObject::create(cx, initialLength, maximumLength, tableType,
2945 proto));
2946 if (!table) {
2947 return false;
2948 }
2949
2950 // Initialize the table to a default value
2951 RootedValue initValue(
2952 cx, args.length() < 2 ? TableDefaultValue(tableType) : args[1]);
2953
2954 // Skip initializing the table if the fill value is null, as that is the
2955 // default value.
2956 if (!initValue.isNull() &&
2957 !table->fillRange(cx, 0, initialLength, initValue)) {
2958 return false;
2959 }
2960 #ifdef DEBUG
2961 // Assert that null is the default value of a new table.
2962 if (initValue.isNull()) {
2963 table->assertRangeNull(0, initialLength);
2964 }
2965 #endif
2966
2967 args.rval().setObject(*table);
2968 return true;
2969 }
2970
IsTable(HandleValue v)2971 static bool IsTable(HandleValue v) {
2972 return v.isObject() && v.toObject().is<WasmTableObject>();
2973 }
2974
2975 /* static */
lengthGetterImpl(JSContext * cx,const CallArgs & args)2976 bool WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args) {
2977 args.rval().setNumber(
2978 args.thisv().toObject().as<WasmTableObject>().table().length());
2979 return true;
2980 }
2981
2982 /* static */
lengthGetter(JSContext * cx,unsigned argc,Value * vp)2983 bool WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp) {
2984 CallArgs args = CallArgsFromVp(argc, vp);
2985 return CallNonGenericMethod<IsTable, lengthGetterImpl>(cx, args);
2986 }
2987
2988 const JSPropertySpec WasmTableObject::properties[] = {
2989 JS_PSG("length", WasmTableObject::lengthGetter, JSPROP_ENUMERATE),
2990 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Table", JSPROP_READONLY),
2991 JS_PS_END};
2992
ToTableIndex(JSContext * cx,HandleValue v,const Table & table,const char * noun,uint32_t * index)2993 static bool ToTableIndex(JSContext* cx, HandleValue v, const Table& table,
2994 const char* noun, uint32_t* index) {
2995 if (!EnforceRangeU32(cx, v, "Table", noun, index)) {
2996 return false;
2997 }
2998
2999 if (*index >= table.length()) {
3000 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3001 JSMSG_WASM_BAD_RANGE, "Table", noun);
3002 return false;
3003 }
3004
3005 return true;
3006 }
3007
3008 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
3009 /* static */
typeImpl(JSContext * cx,const CallArgs & args)3010 bool WasmTableObject::typeImpl(JSContext* cx, const CallArgs& args) {
3011 Rooted<IdValueVector> props(cx, IdValueVector(cx));
3012 Table& table = args.thisv().toObject().as<WasmTableObject>().table();
3013
3014 const char* elementValue;
3015 switch (table.repr()) {
3016 case TableRepr::Func:
3017 elementValue = "funcref";
3018 break;
3019 case TableRepr::Ref:
3020 elementValue = "externref";
3021 break;
3022 default:
3023 MOZ_CRASH("Should not happen");
3024 }
3025 JSString* elementString = UTF8CharsToString(cx, elementValue);
3026 if (!elementString) {
3027 return false;
3028 }
3029 if (!props.append(IdValuePair(NameToId(cx->names().element),
3030 StringValue(elementString)))) {
3031 return false;
3032 }
3033
3034 if (table.maximum().isSome()) {
3035 if (!props.append(IdValuePair(NameToId(cx->names().maximum),
3036 Int32Value(table.maximum().value())))) {
3037 return false;
3038 }
3039 }
3040
3041 if (!props.append(IdValuePair(NameToId(cx->names().minimum),
3042 Int32Value(table.length())))) {
3043 return false;
3044 }
3045
3046 JSObject* tableType = NewPlainObjectWithProperties(
3047 cx, props.begin(), props.length(), GenericObject);
3048 if (!tableType) {
3049 return false;
3050 }
3051 args.rval().setObject(*tableType);
3052 return true;
3053 }
3054
3055 /* static */
type(JSContext * cx,unsigned argc,Value * vp)3056 bool WasmTableObject::type(JSContext* cx, unsigned argc, Value* vp) {
3057 CallArgs args = CallArgsFromVp(argc, vp);
3058 return CallNonGenericMethod<IsTable, typeImpl>(cx, args);
3059 }
3060 #endif
3061
3062 /* static */
getImpl(JSContext * cx,const CallArgs & args)3063 bool WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) {
3064 RootedWasmTableObject tableObj(
3065 cx, &args.thisv().toObject().as<WasmTableObject>());
3066 const Table& table = tableObj->table();
3067
3068 if (!args.requireAtLeast(cx, "WebAssembly.Table.get", 1)) {
3069 return false;
3070 }
3071
3072 uint32_t index;
3073 if (!ToTableIndex(cx, args.get(0), table, "get index", &index)) {
3074 return false;
3075 }
3076
3077 switch (table.repr()) {
3078 case TableRepr::Func: {
3079 MOZ_RELEASE_ASSERT(!table.isAsmJS());
3080 RootedFunction fun(cx);
3081 if (!table.getFuncRef(cx, index, &fun)) {
3082 return false;
3083 }
3084 args.rval().setObjectOrNull(fun);
3085 break;
3086 }
3087 case TableRepr::Ref: {
3088 args.rval().set(UnboxAnyRef(table.getAnyRef(index)));
3089 break;
3090 }
3091 }
3092 return true;
3093 }
3094
3095 /* static */
get(JSContext * cx,unsigned argc,Value * vp)3096 bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) {
3097 CallArgs args = CallArgsFromVp(argc, vp);
3098 return CallNonGenericMethod<IsTable, getImpl>(cx, args);
3099 }
3100
3101 /* static */
setImpl(JSContext * cx,const CallArgs & args)3102 bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) {
3103 RootedWasmTableObject tableObj(
3104 cx, &args.thisv().toObject().as<WasmTableObject>());
3105 Table& table = tableObj->table();
3106
3107 if (!args.requireAtLeast(cx, "WebAssembly.Table.set", 1)) {
3108 return false;
3109 }
3110
3111 uint32_t index;
3112 if (!ToTableIndex(cx, args.get(0), table, "set index", &index)) {
3113 return false;
3114 }
3115
3116 RootedValue fillValue(
3117 cx, args.length() < 2 ? TableDefaultValue(table.elemType()) : args[1]);
3118 if (!tableObj->fillRange(cx, index, 1, fillValue)) {
3119 return false;
3120 }
3121
3122 args.rval().setUndefined();
3123 return true;
3124 }
3125
3126 /* static */
set(JSContext * cx,unsigned argc,Value * vp)3127 bool WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp) {
3128 CallArgs args = CallArgsFromVp(argc, vp);
3129 return CallNonGenericMethod<IsTable, setImpl>(cx, args);
3130 }
3131
3132 /* static */
growImpl(JSContext * cx,const CallArgs & args)3133 bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) {
3134 RootedWasmTableObject tableObj(
3135 cx, &args.thisv().toObject().as<WasmTableObject>());
3136 Table& table = tableObj->table();
3137
3138 if (!args.requireAtLeast(cx, "WebAssembly.Table.grow", 1)) {
3139 return false;
3140 }
3141
3142 uint32_t delta;
3143 if (!EnforceRangeU32(cx, args.get(0), "Table", "grow delta", &delta)) {
3144 return false;
3145 }
3146
3147 uint32_t oldLength = table.grow(delta);
3148
3149 if (oldLength == uint32_t(-1)) {
3150 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
3151 "table");
3152 return false;
3153 }
3154
3155 // Fill the grown range of the table
3156 RootedValue fillValue(
3157 cx, args.length() < 2 ? TableDefaultValue(table.elemType()) : args[1]);
3158
3159 // Skip filling the grown range of the table if the fill value is null, as
3160 // that is the default value.
3161 if (!fillValue.isNull() &&
3162 !tableObj->fillRange(cx, oldLength, delta, fillValue)) {
3163 return false;
3164 }
3165 #ifdef DEBUG
3166 // Assert that null is the default value of the grown range.
3167 if (fillValue.isNull()) {
3168 tableObj->assertRangeNull(oldLength, delta);
3169 }
3170 #endif
3171
3172 args.rval().setInt32(oldLength);
3173 return true;
3174 }
3175
3176 /* static */
grow(JSContext * cx,unsigned argc,Value * vp)3177 bool WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp) {
3178 CallArgs args = CallArgsFromVp(argc, vp);
3179 return CallNonGenericMethod<IsTable, growImpl>(cx, args);
3180 }
3181
3182 const JSFunctionSpec WasmTableObject::methods[] = {
3183 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
3184 JS_FN("type", WasmTableObject::type, 0, JSPROP_ENUMERATE),
3185 #endif
3186 JS_FN("get", WasmTableObject::get, 1, JSPROP_ENUMERATE),
3187 JS_FN("set", WasmTableObject::set, 2, JSPROP_ENUMERATE),
3188 JS_FN("grow", WasmTableObject::grow, 1, JSPROP_ENUMERATE), JS_FS_END};
3189
3190 const JSFunctionSpec WasmTableObject::static_methods[] = {JS_FS_END};
3191
table() const3192 Table& WasmTableObject::table() const {
3193 return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
3194 }
3195
fillRange(JSContext * cx,uint32_t index,uint32_t length,HandleValue value) const3196 bool WasmTableObject::fillRange(JSContext* cx, uint32_t index, uint32_t length,
3197 HandleValue value) const {
3198 Table& tab = table();
3199
3200 // All consumers are required to either bounds check or statically be in
3201 // bounds
3202 MOZ_ASSERT(uint64_t(index) + uint64_t(length) <= tab.length());
3203
3204 RootedFunction fun(cx);
3205 RootedAnyRef any(cx, AnyRef::null());
3206 if (!CheckRefType(cx, tab.elemType(), value, &fun, &any)) {
3207 return false;
3208 }
3209 switch (tab.repr()) {
3210 case TableRepr::Func:
3211 MOZ_RELEASE_ASSERT(!tab.isAsmJS());
3212 tab.fillFuncRef(index, length, FuncRef::fromJSFunction(fun), cx);
3213 break;
3214 case TableRepr::Ref:
3215 tab.fillAnyRef(index, length, any);
3216 break;
3217 }
3218 return true;
3219 }
3220
3221 #ifdef DEBUG
assertRangeNull(uint32_t index,uint32_t length) const3222 void WasmTableObject::assertRangeNull(uint32_t index, uint32_t length) const {
3223 Table& tab = table();
3224 switch (tab.repr()) {
3225 case TableRepr::Func:
3226 for (uint32_t i = index; i < index + length; i++) {
3227 MOZ_ASSERT(tab.getFuncRef(i).code == nullptr);
3228 }
3229 break;
3230 case TableRepr::Ref:
3231 for (uint32_t i = index; i < index + length; i++) {
3232 MOZ_ASSERT(tab.getAnyRef(i).isNull());
3233 }
3234 break;
3235 }
3236 }
3237 #endif
3238
3239 // ============================================================================
3240 // WebAssembly.global class and methods
3241
3242 const JSClassOps WasmGlobalObject::classOps_ = {
3243 nullptr, // addProperty
3244 nullptr, // delProperty
3245 nullptr, // enumerate
3246 nullptr, // newEnumerate
3247 nullptr, // resolve
3248 nullptr, // mayResolve
3249 WasmGlobalObject::finalize, // finalize
3250 nullptr, // call
3251 nullptr, // hasInstance
3252 nullptr, // construct
3253 WasmGlobalObject::trace, // trace
3254 };
3255
3256 const JSClass WasmGlobalObject::class_ = {
3257 "WebAssembly.Global",
3258 JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) |
3259 JSCLASS_BACKGROUND_FINALIZE,
3260 &WasmGlobalObject::classOps_, &WasmGlobalObject::classSpec_};
3261
3262 const JSClass& WasmGlobalObject::protoClass_ = PlainObject::class_;
3263
3264 static constexpr char WasmGlobalName[] = "Global";
3265
3266 const ClassSpec WasmGlobalObject::classSpec_ = {
3267 CreateWasmConstructor<WasmGlobalObject, WasmGlobalName>,
3268 GenericCreatePrototype<WasmGlobalObject>,
3269 WasmGlobalObject::static_methods,
3270 nullptr,
3271 WasmGlobalObject::methods,
3272 WasmGlobalObject::properties,
3273 nullptr,
3274 ClassSpec::DontDefineConstructor};
3275
3276 /* static */
trace(JSTracer * trc,JSObject * obj)3277 void WasmGlobalObject::trace(JSTracer* trc, JSObject* obj) {
3278 WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
3279 if (global->isNewborn()) {
3280 // This can happen while we're allocating the object, in which case
3281 // every single slot of the object is not defined yet. In particular,
3282 // there's nothing to trace yet.
3283 return;
3284 }
3285 global->val().get().trace(trc);
3286 }
3287
3288 /* static */
finalize(JSFreeOp * fop,JSObject * obj)3289 void WasmGlobalObject::finalize(JSFreeOp* fop, JSObject* obj) {
3290 WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
3291 if (!global->isNewborn()) {
3292 fop->delete_(obj, &global->val(), MemoryUse::WasmGlobalCell);
3293 }
3294 }
3295
3296 /* static */
create(JSContext * cx,HandleVal value,bool isMutable,HandleObject proto)3297 WasmGlobalObject* WasmGlobalObject::create(JSContext* cx, HandleVal value,
3298 bool isMutable, HandleObject proto) {
3299 AutoSetNewObjectMetadata metadata(cx);
3300 RootedWasmGlobalObject obj(
3301 cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
3302 if (!obj) {
3303 return nullptr;
3304 }
3305
3306 MOZ_ASSERT(obj->isNewborn());
3307 MOZ_ASSERT(obj->isTenured(), "assumed by global.set post barriers");
3308
3309 GCPtrVal* val = js_new<GCPtrVal>(Val(value.get().type()));
3310 if (!val) {
3311 ReportOutOfMemory(cx);
3312 return nullptr;
3313 }
3314 obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
3315 InitReservedSlot(obj, VAL_SLOT, val, MemoryUse::WasmGlobalCell);
3316
3317 // It's simpler to initialize the cell after the object has been created,
3318 // to avoid needing to root the cell before the object creation.
3319 obj->val() = value.get();
3320
3321 MOZ_ASSERT(!obj->isNewborn());
3322
3323 return obj;
3324 }
3325
3326 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)3327 bool WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp) {
3328 CallArgs args = CallArgsFromVp(argc, vp);
3329
3330 if (!ThrowIfNotConstructing(cx, args, "Global")) {
3331 return false;
3332 }
3333
3334 if (!args.requireAtLeast(cx, "WebAssembly.Global", 1)) {
3335 return false;
3336 }
3337
3338 if (!args.get(0).isObject()) {
3339 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3340 JSMSG_WASM_BAD_DESC_ARG, "global");
3341 return false;
3342 }
3343
3344 RootedObject obj(cx, &args[0].toObject());
3345
3346 // Extract properties in lexicographic order per spec.
3347
3348 RootedValue mutableVal(cx);
3349 if (!JS_GetProperty(cx, obj, "mutable", &mutableVal)) {
3350 return false;
3351 }
3352
3353 RootedValue typeVal(cx);
3354 if (!JS_GetProperty(cx, obj, "value", &typeVal)) {
3355 return false;
3356 }
3357
3358 ValType globalType;
3359 if (!ToValType(cx, typeVal, &globalType)) {
3360 return false;
3361 }
3362
3363 bool isMutable = ToBoolean(mutableVal);
3364
3365 // Extract the initial value, or provide a suitable default.
3366 RootedVal globalVal(cx, globalType);
3367
3368 // Override with non-undefined value, if provided.
3369 RootedValue valueVal(cx, args.get(1));
3370 if (!valueVal.isUndefined() ||
3371 (args.length() >= 2 && globalType.isReference())) {
3372 if (!Val::fromJSValue(cx, globalType, valueVal, &globalVal)) {
3373 return false;
3374 }
3375 }
3376
3377 RootedObject proto(cx);
3378 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmGlobal,
3379 &proto)) {
3380 return false;
3381 }
3382 if (!proto) {
3383 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal);
3384 }
3385
3386 WasmGlobalObject* global =
3387 WasmGlobalObject::create(cx, globalVal, isMutable, proto);
3388 if (!global) {
3389 return false;
3390 }
3391
3392 args.rval().setObject(*global);
3393 return true;
3394 }
3395
IsGlobal(HandleValue v)3396 static bool IsGlobal(HandleValue v) {
3397 return v.isObject() && v.toObject().is<WasmGlobalObject>();
3398 }
3399
3400 /* static */
valueGetterImpl(JSContext * cx,const CallArgs & args)3401 bool WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) {
3402 const WasmGlobalObject& globalObj =
3403 args.thisv().toObject().as<WasmGlobalObject>();
3404 if (!globalObj.type().isExposable()) {
3405 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3406 JSMSG_WASM_BAD_VAL_TYPE);
3407 return false;
3408 }
3409 return globalObj.val().get().toJSValue(cx, args.rval());
3410 }
3411
3412 /* static */
valueGetter(JSContext * cx,unsigned argc,Value * vp)3413 bool WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp) {
3414 CallArgs args = CallArgsFromVp(argc, vp);
3415 return CallNonGenericMethod<IsGlobal, valueGetterImpl>(cx, args);
3416 }
3417
3418 /* static */
valueSetterImpl(JSContext * cx,const CallArgs & args)3419 bool WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args) {
3420 if (!args.requireAtLeast(cx, "WebAssembly.Global setter", 1)) {
3421 return false;
3422 }
3423
3424 RootedWasmGlobalObject global(
3425 cx, &args.thisv().toObject().as<WasmGlobalObject>());
3426 if (!global->isMutable()) {
3427 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3428 JSMSG_WASM_GLOBAL_IMMUTABLE);
3429 return false;
3430 }
3431
3432 RootedVal val(cx);
3433 if (!Val::fromJSValue(cx, global->type(), args.get(0), &val)) {
3434 return false;
3435 }
3436 global->val() = val.get();
3437
3438 args.rval().setUndefined();
3439 return true;
3440 }
3441
3442 /* static */
valueSetter(JSContext * cx,unsigned argc,Value * vp)3443 bool WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp) {
3444 CallArgs args = CallArgsFromVp(argc, vp);
3445 return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args);
3446 }
3447
3448 const JSPropertySpec WasmGlobalObject::properties[] = {
3449 JS_PSGS("value", WasmGlobalObject::valueGetter,
3450 WasmGlobalObject::valueSetter, JSPROP_ENUMERATE),
3451 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Global", JSPROP_READONLY),
3452 JS_PS_END};
3453
3454 const JSFunctionSpec WasmGlobalObject::methods[] = {
3455 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
3456 JS_FN("type", WasmGlobalObject::type, 0, JSPROP_ENUMERATE),
3457 #endif
3458 JS_FN(js_valueOf_str, WasmGlobalObject::valueGetter, 0, JSPROP_ENUMERATE),
3459 JS_FS_END};
3460
3461 const JSFunctionSpec WasmGlobalObject::static_methods[] = {JS_FS_END};
3462
isMutable() const3463 bool WasmGlobalObject::isMutable() const {
3464 return getReservedSlot(MUTABLE_SLOT).toBoolean();
3465 }
3466
type() const3467 ValType WasmGlobalObject::type() const { return val().get().type(); }
3468
val() const3469 GCPtrVal& WasmGlobalObject::val() const {
3470 return *reinterpret_cast<GCPtrVal*>(getReservedSlot(VAL_SLOT).toPrivate());
3471 }
3472
3473 #ifdef ENABLE_WASM_TYPE_REFLECTIONS
3474 /* static */
typeImpl(JSContext * cx,const CallArgs & args)3475 bool WasmGlobalObject::typeImpl(JSContext* cx, const CallArgs& args) {
3476 RootedWasmGlobalObject global(
3477 cx, &args.thisv().toObject().as<WasmGlobalObject>());
3478 Rooted<IdValueVector> props(cx, IdValueVector(cx));
3479
3480 if (!props.append(IdValuePair(NameToId(cx->names().mutable_),
3481 BooleanValue(global->isMutable())))) {
3482 return false;
3483 }
3484
3485 JSString* valueType = UTF8CharsToString(cx, ToString(global->type()).get());
3486 if (!valueType) {
3487 return false;
3488 }
3489 if (!props.append(
3490 IdValuePair(NameToId(cx->names().value), StringValue(valueType)))) {
3491 return false;
3492 }
3493
3494 JSObject* globalType = NewPlainObjectWithProperties(
3495 cx, props.begin(), props.length(), GenericObject);
3496 if (!globalType) {
3497 return false;
3498 }
3499 args.rval().setObject(*globalType);
3500 return true;
3501 }
3502
3503 /* static */
type(JSContext * cx,unsigned argc,Value * vp)3504 bool WasmGlobalObject::type(JSContext* cx, unsigned argc, Value* vp) {
3505 CallArgs args = CallArgsFromVp(argc, vp);
3506 return CallNonGenericMethod<IsGlobal, typeImpl>(cx, args);
3507 }
3508 #endif
3509
3510 // ============================================================================
3511 // WebAssembly.Exception class and methods
3512
3513 const JSClassOps WasmExceptionObject::classOps_ = {
3514 nullptr, // addProperty
3515 nullptr, // delProperty
3516 nullptr, // enumerate
3517 nullptr, // newEnumerate
3518 nullptr, // resolve
3519 nullptr, // mayResolve
3520 WasmExceptionObject::finalize, // finalize
3521 nullptr, // call
3522 nullptr, // hasInstance
3523 nullptr, // construct
3524 nullptr, // trace
3525 };
3526
3527 const JSClass WasmExceptionObject::class_ = {
3528 "WebAssembly.Exception",
3529 JSCLASS_HAS_RESERVED_SLOTS(WasmExceptionObject::RESERVED_SLOTS) |
3530 JSCLASS_FOREGROUND_FINALIZE,
3531 &WasmExceptionObject::classOps_, &WasmExceptionObject::classSpec_};
3532
3533 const JSClass& WasmExceptionObject::protoClass_ = PlainObject::class_;
3534
3535 static constexpr char WasmExceptionName[] = "Exception";
3536
3537 const ClassSpec WasmExceptionObject::classSpec_ = {
3538 CreateWasmConstructor<WasmExceptionObject, WasmExceptionName>,
3539 GenericCreatePrototype<WasmExceptionObject>,
3540 WasmExceptionObject::static_methods,
3541 nullptr,
3542 WasmExceptionObject::methods,
3543 WasmExceptionObject::properties,
3544 nullptr,
3545 ClassSpec::DontDefineConstructor};
3546
3547 /* static */
finalize(JSFreeOp * fop,JSObject * obj)3548 void WasmExceptionObject::finalize(JSFreeOp* fop, JSObject* obj) {
3549 WasmExceptionObject& exnObj = obj->as<WasmExceptionObject>();
3550 if (!exnObj.isNewborn()) {
3551 fop->release(obj, &exnObj.tag(), MemoryUse::WasmExceptionTag);
3552 fop->delete_(obj, &exnObj.valueTypes(), MemoryUse::WasmExceptionType);
3553 }
3554 }
3555
construct(JSContext * cx,unsigned argc,Value * vp)3556 bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) {
3557 CallArgs args = CallArgsFromVp(argc, vp);
3558
3559 if (!ThrowIfNotConstructing(cx, args, "Exception")) {
3560 return false;
3561 }
3562
3563 // FIXME: The JS API is not finalized and may specify a different behavior
3564 // here.
3565 // For now, we implement the same behavior as V8 and error when called.
3566 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3567 JSMSG_WASM_EXN_CONSTRUCTOR, "WebAssembly.Exception");
3568
3569 return false;
3570 }
3571
3572 /* static */
create(JSContext * cx,const ValTypeVector & type,HandleObject proto)3573 WasmExceptionObject* WasmExceptionObject::create(JSContext* cx,
3574 const ValTypeVector& type,
3575 HandleObject proto) {
3576 AutoSetNewObjectMetadata metadata(cx);
3577 RootedWasmExceptionObject obj(
3578 cx, NewObjectWithGivenProto<WasmExceptionObject>(cx, proto));
3579 if (!obj) {
3580 return nullptr;
3581 }
3582
3583 MOZ_ASSERT(obj->isNewborn());
3584
3585 SharedExceptionTag tag = SharedExceptionTag(cx->new_<ExceptionTag>());
3586 if (!tag) {
3587 ReportOutOfMemory(cx);
3588 return nullptr;
3589 }
3590
3591 InitReservedSlot(obj, TAG_SLOT, tag.forget().take(),
3592 MemoryUse::WasmExceptionTag);
3593
3594 wasm::ValTypeVector* newValueTypes = js_new<ValTypeVector>();
3595 for (auto t : type) {
3596 if (!newValueTypes->append(t)) {
3597 return nullptr;
3598 }
3599 }
3600 InitReservedSlot(obj, TYPE_SLOT, newValueTypes, MemoryUse::WasmExceptionType);
3601
3602 MOZ_ASSERT(!obj->isNewborn());
3603
3604 return obj;
3605 }
3606
isNewborn() const3607 bool WasmExceptionObject::isNewborn() const {
3608 MOZ_ASSERT(is<WasmExceptionObject>());
3609 return getReservedSlot(TYPE_SLOT).isUndefined();
3610 }
3611
3612 const JSPropertySpec WasmExceptionObject::properties[] = {
3613 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Exception", JSPROP_READONLY),
3614 JS_PS_END};
3615
3616 const JSFunctionSpec WasmExceptionObject::methods[] = {JS_FS_END};
3617
3618 const JSFunctionSpec WasmExceptionObject::static_methods[] = {JS_FS_END};
3619
valueTypes() const3620 wasm::ValTypeVector& WasmExceptionObject::valueTypes() const {
3621 return *(ValTypeVector*)getFixedSlot(TYPE_SLOT).toPrivate();
3622 };
3623
resultType() const3624 wasm::ResultType WasmExceptionObject::resultType() const {
3625 return wasm::ResultType::Vector(valueTypes());
3626 }
3627
tag() const3628 ExceptionTag& WasmExceptionObject::tag() const {
3629 return *(ExceptionTag*)getReservedSlot(TAG_SLOT).toPrivate();
3630 }
3631
3632 // ============================================================================
3633 // WebAssembly.RuntimeException class and methods
3634
3635 const JSClassOps WasmRuntimeExceptionObject::classOps_ = {
3636 nullptr, // addProperty
3637 nullptr, // delProperty
3638 nullptr, // enumerate
3639 nullptr, // newEnumerate
3640 nullptr, // resolve
3641 nullptr, // mayResolve
3642 WasmRuntimeExceptionObject::finalize, // finalize
3643 nullptr, // call
3644 nullptr, // hasInstance
3645 nullptr, // construct
3646 nullptr, // trace
3647 };
3648
3649 const JSClass WasmRuntimeExceptionObject::class_ = {
3650 "WebAssembly.RuntimeException",
3651 JSCLASS_HAS_RESERVED_SLOTS(WasmRuntimeExceptionObject::RESERVED_SLOTS) |
3652 JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE,
3653 &WasmRuntimeExceptionObject::classOps_,
3654 &WasmRuntimeExceptionObject::classSpec_};
3655
3656 const JSClass& WasmRuntimeExceptionObject::protoClass_ = PlainObject::class_;
3657
3658 static constexpr char WasmRuntimeExceptionName[] = "RuntimeException";
3659
3660 const ClassSpec WasmRuntimeExceptionObject::classSpec_ = {
3661 CreateWasmConstructor<WasmRuntimeExceptionObject, WasmRuntimeExceptionName>,
3662 GenericCreatePrototype<WasmRuntimeExceptionObject>,
3663 WasmRuntimeExceptionObject::static_methods,
3664 nullptr,
3665 WasmRuntimeExceptionObject::methods,
3666 WasmRuntimeExceptionObject::properties,
3667 nullptr,
3668 ClassSpec::DontDefineConstructor};
3669
3670 /* static */
finalize(JSFreeOp * fop,JSObject * obj)3671 void WasmRuntimeExceptionObject::finalize(JSFreeOp* fop, JSObject* obj) {
3672 WasmRuntimeExceptionObject& exnObj = obj->as<WasmRuntimeExceptionObject>();
3673 if (!exnObj.isNewborn()) {
3674 fop->release(obj, &exnObj.tag(), MemoryUse::WasmRuntimeExceptionTag);
3675 }
3676 }
3677
construct(JSContext * cx,unsigned argc,Value * vp)3678 bool WasmRuntimeExceptionObject::construct(JSContext* cx, unsigned argc,
3679 Value* vp) {
3680 CallArgs args = CallArgsFromVp(argc, vp);
3681
3682 if (!ThrowIfNotConstructing(cx, args, "RuntimeException")) {
3683 return false;
3684 }
3685
3686 // FIXME: When the JS API is finalized, it may be possible to construct
3687 // WebAssembly.RuntimeException instances from JS, but not for now.
3688 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3689 JSMSG_WASM_EXN_CONSTRUCTOR,
3690 "WebAssembly.RuntimeException");
3691
3692 return false;
3693 }
3694
3695 /* static */
create(JSContext * cx,wasm::SharedExceptionTag tag,HandleArrayBufferObject values,HandleArrayObject refs)3696 WasmRuntimeExceptionObject* WasmRuntimeExceptionObject::create(
3697 JSContext* cx, wasm::SharedExceptionTag tag, HandleArrayBufferObject values,
3698 HandleArrayObject refs) {
3699 RootedObject proto(
3700 cx, &cx->global()->getPrototype(JSProto_WasmRuntimeException).toObject());
3701
3702 AutoSetNewObjectMetadata metadata(cx);
3703 RootedWasmRuntimeExceptionObject obj(
3704 cx, NewObjectWithGivenProto<WasmRuntimeExceptionObject>(cx, proto));
3705 if (!obj) {
3706 return nullptr;
3707 }
3708
3709 MOZ_ASSERT(obj->isNewborn());
3710 InitReservedSlot(obj, TAG_SLOT, tag.forget().take(),
3711 MemoryUse::WasmRuntimeExceptionTag);
3712
3713 obj->initFixedSlot(VALUES_SLOT, ObjectValue(*values));
3714 obj->initFixedSlot(REFS_SLOT, ObjectValue(*refs));
3715
3716 MOZ_ASSERT(!obj->isNewborn());
3717
3718 return obj;
3719 }
3720
isNewborn() const3721 bool WasmRuntimeExceptionObject::isNewborn() const {
3722 MOZ_ASSERT(is<WasmRuntimeExceptionObject>());
3723 return getReservedSlot(REFS_SLOT).isUndefined();
3724 }
3725
3726 const JSPropertySpec WasmRuntimeExceptionObject::properties[] = {
3727 JS_STRING_SYM_PS(toStringTag, "WebAssembly.RuntimeException",
3728 JSPROP_READONLY),
3729 JS_PS_END};
3730
3731 const JSFunctionSpec WasmRuntimeExceptionObject::methods[] = {JS_FS_END};
3732
3733 const JSFunctionSpec WasmRuntimeExceptionObject::static_methods[] = {JS_FS_END};
3734
tag() const3735 ExceptionTag& WasmRuntimeExceptionObject::tag() const {
3736 return *(ExceptionTag*)getReservedSlot(TAG_SLOT).toPrivate();
3737 }
3738
refs() const3739 ArrayObject& WasmRuntimeExceptionObject::refs() const {
3740 return getReservedSlot(REFS_SLOT).toObject().as<ArrayObject>();
3741 }
3742
3743 // ============================================================================
3744 // WebAssembly class and static methods
3745
WebAssembly_toSource(JSContext * cx,unsigned argc,Value * vp)3746 static bool WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp) {
3747 CallArgs args = CallArgsFromVp(argc, vp);
3748 args.rval().setString(cx->names().WebAssembly);
3749 return true;
3750 }
3751
RejectWithPendingException(JSContext * cx,Handle<PromiseObject * > promise)3752 static bool RejectWithPendingException(JSContext* cx,
3753 Handle<PromiseObject*> promise) {
3754 if (!cx->isExceptionPending()) {
3755 return false;
3756 }
3757
3758 RootedValue rejectionValue(cx);
3759 if (!GetAndClearException(cx, &rejectionValue)) {
3760 return false;
3761 }
3762
3763 return PromiseObject::reject(cx, promise, rejectionValue);
3764 }
3765
Reject(JSContext * cx,const CompileArgs & args,Handle<PromiseObject * > promise,const UniqueChars & error)3766 static bool Reject(JSContext* cx, const CompileArgs& args,
3767 Handle<PromiseObject*> promise, const UniqueChars& error) {
3768 if (!error) {
3769 ReportOutOfMemory(cx);
3770 return RejectWithPendingException(cx, promise);
3771 }
3772
3773 RootedObject stack(cx, promise->allocationSite());
3774 RootedString filename(
3775 cx, JS_NewStringCopyZ(cx, args.scriptedCaller.filename.get()));
3776 if (!filename) {
3777 return false;
3778 }
3779
3780 unsigned line = args.scriptedCaller.line;
3781
3782 // Ideally we'd report a JSMSG_WASM_COMPILE_ERROR here, but there's no easy
3783 // way to create an ErrorObject for an arbitrary error code with multiple
3784 // replacements.
3785 UniqueChars str(JS_smprintf("wasm validation error: %s", error.get()));
3786 if (!str) {
3787 return false;
3788 }
3789
3790 size_t len = strlen(str.get());
3791 RootedString message(cx, NewStringCopyN<CanGC>(cx, str.get(), len));
3792 if (!message) {
3793 return false;
3794 }
3795
3796 // There's no error |cause| available here.
3797 auto cause = JS::NothingHandleValue;
3798
3799 RootedObject errorObj(
3800 cx, ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, 0,
3801 line, 0, nullptr, message, cause));
3802 if (!errorObj) {
3803 return false;
3804 }
3805
3806 RootedValue rejectionValue(cx, ObjectValue(*errorObj));
3807 return PromiseObject::reject(cx, promise, rejectionValue);
3808 }
3809
LogAsync(JSContext * cx,const char * funcName,const Module & module)3810 static void LogAsync(JSContext* cx, const char* funcName,
3811 const Module& module) {
3812 Log(cx, "async %s succeeded%s", funcName,
3813 module.loggingDeserialized() ? " (loaded from cache)" : "");
3814 }
3815
3816 enum class Ret { Pair, Instance };
3817
3818 class AsyncInstantiateTask : public OffThreadPromiseTask {
3819 SharedModule module_;
3820 PersistentRooted<ImportValues> imports_;
3821 Ret ret_;
3822
3823 public:
AsyncInstantiateTask(JSContext * cx,const Module & module,Ret ret,Handle<PromiseObject * > promise)3824 AsyncInstantiateTask(JSContext* cx, const Module& module, Ret ret,
3825 Handle<PromiseObject*> promise)
3826 : OffThreadPromiseTask(cx, promise),
3827 module_(&module),
3828 imports_(cx),
3829 ret_(ret) {}
3830
imports()3831 ImportValues& imports() { return imports_.get(); }
3832
resolve(JSContext * cx,Handle<PromiseObject * > promise)3833 bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
3834 RootedObject instanceProto(
3835 cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
3836
3837 RootedWasmInstanceObject instanceObj(cx);
3838 if (!module_->instantiate(cx, imports_.get(), instanceProto,
3839 &instanceObj)) {
3840 return RejectWithPendingException(cx, promise);
3841 }
3842
3843 RootedValue resolutionValue(cx);
3844 if (ret_ == Ret::Instance) {
3845 resolutionValue = ObjectValue(*instanceObj);
3846 } else {
3847 RootedObject resultObj(cx, JS_NewPlainObject(cx));
3848 if (!resultObj) {
3849 return RejectWithPendingException(cx, promise);
3850 }
3851
3852 RootedObject moduleProto(
3853 cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
3854 RootedObject moduleObj(
3855 cx, WasmModuleObject::create(cx, *module_, moduleProto));
3856 if (!moduleObj) {
3857 return RejectWithPendingException(cx, promise);
3858 }
3859
3860 RootedValue val(cx, ObjectValue(*moduleObj));
3861 if (!JS_DefineProperty(cx, resultObj, "module", val, JSPROP_ENUMERATE)) {
3862 return RejectWithPendingException(cx, promise);
3863 }
3864
3865 val = ObjectValue(*instanceObj);
3866 if (!JS_DefineProperty(cx, resultObj, "instance", val,
3867 JSPROP_ENUMERATE)) {
3868 return RejectWithPendingException(cx, promise);
3869 }
3870
3871 resolutionValue = ObjectValue(*resultObj);
3872 }
3873
3874 if (!PromiseObject::resolve(cx, promise, resolutionValue)) {
3875 return RejectWithPendingException(cx, promise);
3876 }
3877
3878 LogAsync(cx, "instantiate", *module_);
3879 return true;
3880 }
3881 };
3882
AsyncInstantiate(JSContext * cx,const Module & module,HandleObject importObj,Ret ret,Handle<PromiseObject * > promise)3883 static bool AsyncInstantiate(JSContext* cx, const Module& module,
3884 HandleObject importObj, Ret ret,
3885 Handle<PromiseObject*> promise) {
3886 auto task = js::MakeUnique<AsyncInstantiateTask>(cx, module, ret, promise);
3887 if (!task || !task->init(cx)) {
3888 return false;
3889 }
3890
3891 if (!GetImports(cx, module, importObj, &task->imports())) {
3892 return RejectWithPendingException(cx, promise);
3893 }
3894
3895 task.release()->dispatchResolveAndDestroy();
3896 return true;
3897 }
3898
ResolveCompile(JSContext * cx,const Module & module,Handle<PromiseObject * > promise)3899 static bool ResolveCompile(JSContext* cx, const Module& module,
3900 Handle<PromiseObject*> promise) {
3901 RootedObject proto(
3902 cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
3903 RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
3904 if (!moduleObj) {
3905 return RejectWithPendingException(cx, promise);
3906 }
3907
3908 RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
3909 if (!PromiseObject::resolve(cx, promise, resolutionValue)) {
3910 return RejectWithPendingException(cx, promise);
3911 }
3912
3913 LogAsync(cx, "compile", module);
3914 return true;
3915 }
3916
3917 struct CompileBufferTask : PromiseHelperTask {
3918 MutableBytes bytecode;
3919 SharedCompileArgs compileArgs;
3920 UniqueChars error;
3921 UniqueCharsVector warnings;
3922 SharedModule module;
3923 bool instantiate;
3924 PersistentRootedObject importObj;
3925
CompileBufferTaskCompileBufferTask3926 CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise,
3927 HandleObject importObj)
3928 : PromiseHelperTask(cx, promise),
3929 instantiate(true),
3930 importObj(cx, importObj) {}
3931
CompileBufferTaskCompileBufferTask3932 CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise)
3933 : PromiseHelperTask(cx, promise), instantiate(false) {}
3934
initCompileBufferTask3935 bool init(JSContext* cx, HandleValue maybeOptions, const char* introducer) {
3936 compileArgs = InitCompileArgs(cx, maybeOptions, introducer);
3937 if (!compileArgs) {
3938 return false;
3939 }
3940 return PromiseHelperTask::init(cx);
3941 }
3942
executeCompileBufferTask3943 void execute() override {
3944 module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings, nullptr);
3945 }
3946
resolveCompileBufferTask3947 bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
3948 if (!module) {
3949 return Reject(cx, *compileArgs, promise, error);
3950 }
3951 if (!ReportCompileWarnings(cx, warnings)) {
3952 return false;
3953 }
3954 if (instantiate) {
3955 return AsyncInstantiate(cx, *module, importObj, Ret::Pair, promise);
3956 }
3957 return ResolveCompile(cx, *module, promise);
3958 }
3959 };
3960
RejectWithPendingException(JSContext * cx,Handle<PromiseObject * > promise,CallArgs & callArgs)3961 static bool RejectWithPendingException(JSContext* cx,
3962 Handle<PromiseObject*> promise,
3963 CallArgs& callArgs) {
3964 if (!RejectWithPendingException(cx, promise)) {
3965 return false;
3966 }
3967
3968 callArgs.rval().setObject(*promise);
3969 return true;
3970 }
3971
EnsurePromiseSupport(JSContext * cx)3972 static bool EnsurePromiseSupport(JSContext* cx) {
3973 if (!cx->runtime()->offThreadPromiseState.ref().initialized()) {
3974 JS_ReportErrorASCII(
3975 cx, "WebAssembly Promise APIs not supported in this runtime.");
3976 return false;
3977 }
3978 return true;
3979 }
3980
GetBufferSource(JSContext * cx,CallArgs callArgs,const char * name,MutableBytes * bytecode)3981 static bool GetBufferSource(JSContext* cx, CallArgs callArgs, const char* name,
3982 MutableBytes* bytecode) {
3983 if (!callArgs.requireAtLeast(cx, name, 1)) {
3984 return false;
3985 }
3986
3987 if (!callArgs[0].isObject()) {
3988 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3989 JSMSG_WASM_BAD_BUF_ARG);
3990 return false;
3991 }
3992
3993 return GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
3994 bytecode);
3995 }
3996
WebAssembly_compile(JSContext * cx,unsigned argc,Value * vp)3997 static bool WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp) {
3998 if (!EnsurePromiseSupport(cx)) {
3999 return false;
4000 }
4001
4002 Log(cx, "async compile() started");
4003
4004 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
4005 if (!promise) {
4006 return false;
4007 }
4008
4009 CallArgs callArgs = CallArgsFromVp(argc, vp);
4010
4011 auto task = cx->make_unique<CompileBufferTask>(cx, promise);
4012 if (!task || !task->init(cx, callArgs.get(1), "WebAssembly.compile")) {
4013 return false;
4014 }
4015
4016 if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode)) {
4017 return RejectWithPendingException(cx, promise, callArgs);
4018 }
4019
4020 if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) {
4021 return false;
4022 }
4023
4024 callArgs.rval().setObject(*promise);
4025 return true;
4026 }
4027
GetInstantiateArgs(JSContext * cx,CallArgs callArgs,MutableHandleObject firstArg,MutableHandleObject importObj)4028 static bool GetInstantiateArgs(JSContext* cx, CallArgs callArgs,
4029 MutableHandleObject firstArg,
4030 MutableHandleObject importObj) {
4031 if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1)) {
4032 return false;
4033 }
4034
4035 if (!callArgs[0].isObject()) {
4036 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
4037 JSMSG_WASM_BAD_BUF_MOD_ARG);
4038 return false;
4039 }
4040
4041 firstArg.set(&callArgs[0].toObject());
4042
4043 return GetImportArg(cx, callArgs, importObj);
4044 }
4045
WebAssembly_instantiate(JSContext * cx,unsigned argc,Value * vp)4046 static bool WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp) {
4047 if (!EnsurePromiseSupport(cx)) {
4048 return false;
4049 }
4050
4051 Log(cx, "async instantiate() started");
4052
4053 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
4054 if (!promise) {
4055 return false;
4056 }
4057
4058 CallArgs callArgs = CallArgsFromVp(argc, vp);
4059
4060 RootedObject firstArg(cx);
4061 RootedObject importObj(cx);
4062 if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj)) {
4063 return RejectWithPendingException(cx, promise, callArgs);
4064 }
4065
4066 const Module* module;
4067 if (IsModuleObject(firstArg, &module)) {
4068 if (!AsyncInstantiate(cx, *module, importObj, Ret::Instance, promise)) {
4069 return false;
4070 }
4071 } else {
4072 auto task = cx->make_unique<CompileBufferTask>(cx, promise, importObj);
4073 if (!task || !task->init(cx, callArgs.get(2), "WebAssembly.instantiate")) {
4074 return false;
4075 }
4076
4077 if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG,
4078 &task->bytecode)) {
4079 return RejectWithPendingException(cx, promise, callArgs);
4080 }
4081
4082 if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) {
4083 return false;
4084 }
4085 }
4086
4087 callArgs.rval().setObject(*promise);
4088 return true;
4089 }
4090
WebAssembly_validate(JSContext * cx,unsigned argc,Value * vp)4091 static bool WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp) {
4092 CallArgs callArgs = CallArgsFromVp(argc, vp);
4093
4094 MutableBytes bytecode;
4095 if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode)) {
4096 return false;
4097 }
4098
4099 FeatureOptions options;
4100 ParseCompileOptions(cx, callArgs.get(1), &options);
4101 UniqueChars error;
4102 bool validated = Validate(cx, *bytecode, options, &error);
4103
4104 // If the reason for validation failure was OOM (signalled by null error
4105 // message), report out-of-memory so that validate's return is always
4106 // correct.
4107 if (!validated && !error) {
4108 ReportOutOfMemory(cx);
4109 return false;
4110 }
4111
4112 if (error) {
4113 MOZ_ASSERT(!validated);
4114 Log(cx, "validate() failed with: %s", error.get());
4115 }
4116
4117 callArgs.rval().setBoolean(validated);
4118 return true;
4119 }
4120
EnsureStreamSupport(JSContext * cx)4121 static bool EnsureStreamSupport(JSContext* cx) {
4122 // This should match wasm::StreamingCompilationAvailable().
4123
4124 if (!EnsurePromiseSupport(cx)) {
4125 return false;
4126 }
4127
4128 if (!CanUseExtraThreads()) {
4129 JS_ReportErrorASCII(
4130 cx, "WebAssembly.compileStreaming not supported with --no-threads");
4131 return false;
4132 }
4133
4134 if (!cx->runtime()->consumeStreamCallback) {
4135 JS_ReportErrorASCII(cx,
4136 "WebAssembly streaming not supported in this runtime");
4137 return false;
4138 }
4139
4140 return true;
4141 }
4142
4143 // This value is chosen and asserted to be disjoint from any host error code.
4144 static const size_t StreamOOMCode = 0;
4145
RejectWithStreamErrorNumber(JSContext * cx,size_t errorCode,Handle<PromiseObject * > promise)4146 static bool RejectWithStreamErrorNumber(JSContext* cx, size_t errorCode,
4147 Handle<PromiseObject*> promise) {
4148 if (errorCode == StreamOOMCode) {
4149 ReportOutOfMemory(cx);
4150 return false;
4151 }
4152
4153 cx->runtime()->reportStreamErrorCallback(cx, errorCode);
4154 return RejectWithPendingException(cx, promise);
4155 }
4156
4157 class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer {
4158 // The stream progresses monotonically through these states; the helper
4159 // thread wait()s for streamState_ to reach Closed.
4160 enum StreamState { Env, Code, Tail, Closed };
4161 ExclusiveWaitableData<StreamState> streamState_;
4162
4163 // Immutable:
4164 const bool instantiate_;
4165 const PersistentRootedObject importObj_;
4166
4167 // Immutable after noteResponseURLs() which is called at most once before
4168 // first call on stream thread:
4169 const MutableCompileArgs compileArgs_;
4170
4171 // Immutable after Env state:
4172 Bytes envBytes_;
4173 SectionRange codeSection_;
4174
4175 // The code section vector is resized once during the Env state and filled
4176 // in chunk by chunk during the Code state, updating the end-pointer after
4177 // each chunk:
4178 Bytes codeBytes_;
4179 uint8_t* codeBytesEnd_;
4180 ExclusiveBytesPtr exclusiveCodeBytesEnd_;
4181
4182 // Immutable after Tail state:
4183 Bytes tailBytes_;
4184 ExclusiveStreamEndData exclusiveStreamEnd_;
4185
4186 // Written once before Closed state and read in Closed state on main thread:
4187 SharedModule module_;
4188 Maybe<size_t> streamError_;
4189 UniqueChars compileError_;
4190 UniqueCharsVector warnings_;
4191
4192 // Set on stream thread and read racily on helper thread to abort compilation:
4193 Atomic<bool> streamFailed_;
4194
4195 // Called on some thread before consumeChunk(), streamEnd(), streamError()):
4196
noteResponseURLs(const char * url,const char * sourceMapUrl)4197 void noteResponseURLs(const char* url, const char* sourceMapUrl) override {
4198 if (url) {
4199 compileArgs_->scriptedCaller.filename = DuplicateString(url);
4200 compileArgs_->scriptedCaller.filenameIsURL = true;
4201 }
4202 if (sourceMapUrl) {
4203 compileArgs_->sourceMapURL = DuplicateString(sourceMapUrl);
4204 }
4205 }
4206
4207 // Called on a stream thread:
4208
4209 // Until StartOffThreadPromiseHelperTask succeeds, we are responsible for
4210 // dispatching ourselves back to the JS thread.
4211 //
4212 // Warning: After this function returns, 'this' can be deleted at any time, so
4213 // the caller must immediately return from the stream callback.
setClosedAndDestroyBeforeHelperThreadStarted()4214 void setClosedAndDestroyBeforeHelperThreadStarted() {
4215 streamState_.lock().get() = Closed;
4216 dispatchResolveAndDestroy();
4217 }
4218
4219 // See setClosedAndDestroyBeforeHelperThreadStarted() comment.
rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber)4220 bool rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber) {
4221 MOZ_ASSERT(streamState_.lock() == Env);
4222 MOZ_ASSERT(!streamError_);
4223 streamError_ = Some(errorNumber);
4224 setClosedAndDestroyBeforeHelperThreadStarted();
4225 return false;
4226 }
4227
4228 // Once StartOffThreadPromiseHelperTask succeeds, the helper thread will
4229 // dispatchResolveAndDestroy() after execute() returns, but execute()
4230 // wait()s for state to be Closed.
4231 //
4232 // Warning: After this function returns, 'this' can be deleted at any time, so
4233 // the caller must immediately return from the stream callback.
setClosedAndDestroyAfterHelperThreadStarted()4234 void setClosedAndDestroyAfterHelperThreadStarted() {
4235 auto streamState = streamState_.lock();
4236 MOZ_ASSERT(streamState != Closed);
4237 streamState.get() = Closed;
4238 streamState.notify_one(/* stream closed */);
4239 }
4240
4241 // See setClosedAndDestroyAfterHelperThreadStarted() comment.
rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber)4242 bool rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber) {
4243 MOZ_ASSERT(!streamError_);
4244 streamError_ = Some(errorNumber);
4245 streamFailed_ = true;
4246 exclusiveCodeBytesEnd_.lock().notify_one();
4247 exclusiveStreamEnd_.lock().notify_one();
4248 setClosedAndDestroyAfterHelperThreadStarted();
4249 return false;
4250 }
4251
consumeChunk(const uint8_t * begin,size_t length)4252 bool consumeChunk(const uint8_t* begin, size_t length) override {
4253 switch (streamState_.lock().get()) {
4254 case Env: {
4255 if (!envBytes_.append(begin, length)) {
4256 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
4257 }
4258
4259 if (!StartsCodeSection(envBytes_.begin(), envBytes_.end(),
4260 &codeSection_)) {
4261 return true;
4262 }
4263
4264 uint32_t extraBytes = envBytes_.length() - codeSection_.start;
4265 if (extraBytes) {
4266 envBytes_.shrinkTo(codeSection_.start);
4267 }
4268
4269 if (codeSection_.size > MaxCodeSectionBytes) {
4270 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
4271 }
4272
4273 if (!codeBytes_.resize(codeSection_.size)) {
4274 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
4275 }
4276
4277 codeBytesEnd_ = codeBytes_.begin();
4278 exclusiveCodeBytesEnd_.lock().get() = codeBytesEnd_;
4279
4280 if (!StartOffThreadPromiseHelperTask(this)) {
4281 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
4282 }
4283
4284 // Set the state to Code iff StartOffThreadPromiseHelperTask()
4285 // succeeds so that the state tells us whether we are before or
4286 // after the helper thread started.
4287 streamState_.lock().get() = Code;
4288
4289 if (extraBytes) {
4290 return consumeChunk(begin + length - extraBytes, extraBytes);
4291 }
4292
4293 return true;
4294 }
4295 case Code: {
4296 size_t copyLength =
4297 std::min<size_t>(length, codeBytes_.end() - codeBytesEnd_);
4298 memcpy(codeBytesEnd_, begin, copyLength);
4299 codeBytesEnd_ += copyLength;
4300
4301 {
4302 auto codeStreamEnd = exclusiveCodeBytesEnd_.lock();
4303 codeStreamEnd.get() = codeBytesEnd_;
4304 codeStreamEnd.notify_one();
4305 }
4306
4307 if (codeBytesEnd_ != codeBytes_.end()) {
4308 return true;
4309 }
4310
4311 streamState_.lock().get() = Tail;
4312
4313 if (uint32_t extraBytes = length - copyLength) {
4314 return consumeChunk(begin + copyLength, extraBytes);
4315 }
4316
4317 return true;
4318 }
4319 case Tail: {
4320 if (!tailBytes_.append(begin, length)) {
4321 return rejectAndDestroyAfterHelperThreadStarted(StreamOOMCode);
4322 }
4323
4324 return true;
4325 }
4326 case Closed:
4327 MOZ_CRASH("consumeChunk() in Closed state");
4328 }
4329 MOZ_CRASH("unreachable");
4330 }
4331
streamEnd(JS::OptimizedEncodingListener * tier2Listener)4332 void streamEnd(JS::OptimizedEncodingListener* tier2Listener) override {
4333 switch (streamState_.lock().get()) {
4334 case Env: {
4335 SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
4336 if (!bytecode) {
4337 rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
4338 return;
4339 }
4340 module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_,
4341 &warnings_, nullptr);
4342 setClosedAndDestroyBeforeHelperThreadStarted();
4343 return;
4344 }
4345 case Code:
4346 case Tail:
4347 // Unlock exclusiveStreamEnd_ before locking streamState_.
4348 {
4349 auto streamEnd = exclusiveStreamEnd_.lock();
4350 MOZ_ASSERT(!streamEnd->reached);
4351 streamEnd->reached = true;
4352 streamEnd->tailBytes = &tailBytes_;
4353 streamEnd->tier2Listener = tier2Listener;
4354 streamEnd.notify_one();
4355 }
4356 setClosedAndDestroyAfterHelperThreadStarted();
4357 return;
4358 case Closed:
4359 MOZ_CRASH("streamEnd() in Closed state");
4360 }
4361 }
4362
streamError(size_t errorCode)4363 void streamError(size_t errorCode) override {
4364 MOZ_ASSERT(errorCode != StreamOOMCode);
4365 switch (streamState_.lock().get()) {
4366 case Env:
4367 rejectAndDestroyBeforeHelperThreadStarted(errorCode);
4368 return;
4369 case Tail:
4370 case Code:
4371 rejectAndDestroyAfterHelperThreadStarted(errorCode);
4372 return;
4373 case Closed:
4374 MOZ_CRASH("streamError() in Closed state");
4375 }
4376 }
4377
consumeOptimizedEncoding(const uint8_t * begin,size_t length)4378 void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override {
4379 module_ = Module::deserialize(begin, length);
4380
4381 MOZ_ASSERT(streamState_.lock().get() == Env);
4382 setClosedAndDestroyBeforeHelperThreadStarted();
4383 }
4384
4385 // Called on a helper thread:
4386
execute()4387 void execute() override {
4388 module_ = CompileStreaming(*compileArgs_, envBytes_, codeBytes_,
4389 exclusiveCodeBytesEnd_, exclusiveStreamEnd_,
4390 streamFailed_, &compileError_, &warnings_);
4391
4392 // When execute() returns, the CompileStreamTask will be dispatched
4393 // back to its JS thread to call resolve() and then be destroyed. We
4394 // can't let this happen until the stream has been closed lest
4395 // consumeChunk() or streamEnd() be called on a dead object.
4396 auto streamState = streamState_.lock();
4397 while (streamState != Closed) {
4398 streamState.wait(/* stream closed */);
4399 }
4400 }
4401
4402 // Called on a JS thread after streaming compilation completes/errors:
4403
resolve(JSContext * cx,Handle<PromiseObject * > promise)4404 bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
4405 MOZ_ASSERT(streamState_.lock() == Closed);
4406
4407 if (module_) {
4408 MOZ_ASSERT(!streamFailed_ && !streamError_ && !compileError_);
4409 if (!ReportCompileWarnings(cx, warnings_)) {
4410 return false;
4411 }
4412 if (instantiate_) {
4413 return AsyncInstantiate(cx, *module_, importObj_, Ret::Pair, promise);
4414 }
4415 return ResolveCompile(cx, *module_, promise);
4416 }
4417
4418 if (streamError_) {
4419 return RejectWithStreamErrorNumber(cx, *streamError_, promise);
4420 }
4421
4422 return Reject(cx, *compileArgs_, promise, compileError_);
4423 }
4424
4425 public:
CompileStreamTask(JSContext * cx,Handle<PromiseObject * > promise,CompileArgs & compileArgs,bool instantiate,HandleObject importObj)4426 CompileStreamTask(JSContext* cx, Handle<PromiseObject*> promise,
4427 CompileArgs& compileArgs, bool instantiate,
4428 HandleObject importObj)
4429 : PromiseHelperTask(cx, promise),
4430 streamState_(mutexid::WasmStreamStatus, Env),
4431 instantiate_(instantiate),
4432 importObj_(cx, importObj),
4433 compileArgs_(&compileArgs),
4434 codeSection_{},
4435 codeBytesEnd_(nullptr),
4436 exclusiveCodeBytesEnd_(mutexid::WasmCodeBytesEnd, nullptr),
4437 exclusiveStreamEnd_(mutexid::WasmStreamEnd),
4438 streamFailed_(false) {
4439 MOZ_ASSERT_IF(importObj_, instantiate_);
4440 }
4441 };
4442
4443 // A short-lived object that captures the arguments of a
4444 // WebAssembly.{compileStreaming,instantiateStreaming} while waiting for
4445 // the Promise<Response> to resolve to a (hopefully) Promise.
4446 class ResolveResponseClosure : public NativeObject {
4447 static const unsigned COMPILE_ARGS_SLOT = 0;
4448 static const unsigned PROMISE_OBJ_SLOT = 1;
4449 static const unsigned INSTANTIATE_SLOT = 2;
4450 static const unsigned IMPORT_OBJ_SLOT = 3;
4451 static const JSClassOps classOps_;
4452
finalize(JSFreeOp * fop,JSObject * obj)4453 static void finalize(JSFreeOp* fop, JSObject* obj) {
4454 auto& closure = obj->as<ResolveResponseClosure>();
4455 fop->release(obj, &closure.compileArgs(),
4456 MemoryUse::WasmResolveResponseClosure);
4457 }
4458
4459 public:
4460 static const unsigned RESERVED_SLOTS = 4;
4461 static const JSClass class_;
4462
create(JSContext * cx,const CompileArgs & args,HandleObject promise,bool instantiate,HandleObject importObj)4463 static ResolveResponseClosure* create(JSContext* cx, const CompileArgs& args,
4464 HandleObject promise, bool instantiate,
4465 HandleObject importObj) {
4466 MOZ_ASSERT_IF(importObj, instantiate);
4467
4468 AutoSetNewObjectMetadata metadata(cx);
4469 auto* obj = NewObjectWithGivenProto<ResolveResponseClosure>(cx, nullptr);
4470 if (!obj) {
4471 return nullptr;
4472 }
4473
4474 args.AddRef();
4475 InitReservedSlot(obj, COMPILE_ARGS_SLOT, const_cast<CompileArgs*>(&args),
4476 MemoryUse::WasmResolveResponseClosure);
4477 obj->setReservedSlot(PROMISE_OBJ_SLOT, ObjectValue(*promise));
4478 obj->setReservedSlot(INSTANTIATE_SLOT, BooleanValue(instantiate));
4479 obj->setReservedSlot(IMPORT_OBJ_SLOT, ObjectOrNullValue(importObj));
4480 return obj;
4481 }
4482
compileArgs() const4483 CompileArgs& compileArgs() const {
4484 return *(CompileArgs*)getReservedSlot(COMPILE_ARGS_SLOT).toPrivate();
4485 }
promise() const4486 PromiseObject& promise() const {
4487 return getReservedSlot(PROMISE_OBJ_SLOT).toObject().as<PromiseObject>();
4488 }
instantiate() const4489 bool instantiate() const {
4490 return getReservedSlot(INSTANTIATE_SLOT).toBoolean();
4491 }
importObj() const4492 JSObject* importObj() const {
4493 return getReservedSlot(IMPORT_OBJ_SLOT).toObjectOrNull();
4494 }
4495 };
4496
4497 const JSClassOps ResolveResponseClosure::classOps_ = {
4498 nullptr, // addProperty
4499 nullptr, // delProperty
4500 nullptr, // enumerate
4501 nullptr, // newEnumerate
4502 nullptr, // resolve
4503 nullptr, // mayResolve
4504 ResolveResponseClosure::finalize, // finalize
4505 nullptr, // call
4506 nullptr, // hasInstance
4507 nullptr, // construct
4508 nullptr, // trace
4509 };
4510
4511 const JSClass ResolveResponseClosure::class_ = {
4512 "WebAssembly ResolveResponseClosure",
4513 JSCLASS_DELAY_METADATA_BUILDER |
4514 JSCLASS_HAS_RESERVED_SLOTS(ResolveResponseClosure::RESERVED_SLOTS) |
4515 JSCLASS_FOREGROUND_FINALIZE,
4516 &ResolveResponseClosure::classOps_,
4517 };
4518
ToResolveResponseClosure(CallArgs args)4519 static ResolveResponseClosure* ToResolveResponseClosure(CallArgs args) {
4520 return &args.callee()
4521 .as<JSFunction>()
4522 .getExtendedSlot(0)
4523 .toObject()
4524 .as<ResolveResponseClosure>();
4525 }
4526
RejectWithErrorNumber(JSContext * cx,uint32_t errorNumber,Handle<PromiseObject * > promise)4527 static bool RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber,
4528 Handle<PromiseObject*> promise) {
4529 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
4530 return RejectWithPendingException(cx, promise);
4531 }
4532
ResolveResponse_OnFulfilled(JSContext * cx,unsigned argc,Value * vp)4533 static bool ResolveResponse_OnFulfilled(JSContext* cx, unsigned argc,
4534 Value* vp) {
4535 CallArgs callArgs = CallArgsFromVp(argc, vp);
4536
4537 Rooted<ResolveResponseClosure*> closure(cx,
4538 ToResolveResponseClosure(callArgs));
4539 Rooted<PromiseObject*> promise(cx, &closure->promise());
4540 CompileArgs& compileArgs = closure->compileArgs();
4541 bool instantiate = closure->instantiate();
4542 Rooted<JSObject*> importObj(cx, closure->importObj());
4543
4544 auto task = cx->make_unique<CompileStreamTask>(cx, promise, compileArgs,
4545 instantiate, importObj);
4546 if (!task || !task->init(cx)) {
4547 return false;
4548 }
4549
4550 if (!callArgs.get(0).isObject()) {
4551 return RejectWithErrorNumber(cx, JSMSG_WASM_BAD_RESPONSE_VALUE, promise);
4552 }
4553
4554 RootedObject response(cx, &callArgs.get(0).toObject());
4555 if (!cx->runtime()->consumeStreamCallback(cx, response, JS::MimeType::Wasm,
4556 task.get())) {
4557 return RejectWithPendingException(cx, promise);
4558 }
4559
4560 (void)task.release();
4561
4562 callArgs.rval().setUndefined();
4563 return true;
4564 }
4565
ResolveResponse_OnRejected(JSContext * cx,unsigned argc,Value * vp)4566 static bool ResolveResponse_OnRejected(JSContext* cx, unsigned argc,
4567 Value* vp) {
4568 CallArgs args = CallArgsFromVp(argc, vp);
4569
4570 Rooted<ResolveResponseClosure*> closure(cx, ToResolveResponseClosure(args));
4571 Rooted<PromiseObject*> promise(cx, &closure->promise());
4572
4573 if (!PromiseObject::reject(cx, promise, args.get(0))) {
4574 return false;
4575 }
4576
4577 args.rval().setUndefined();
4578 return true;
4579 }
4580
ResolveResponse(JSContext * cx,CallArgs callArgs,Handle<PromiseObject * > promise,bool instantiate=false,HandleObject importObj=nullptr)4581 static bool ResolveResponse(JSContext* cx, CallArgs callArgs,
4582 Handle<PromiseObject*> promise,
4583 bool instantiate = false,
4584 HandleObject importObj = nullptr) {
4585 MOZ_ASSERT_IF(importObj, instantiate);
4586
4587 const char* introducer = instantiate ? "WebAssembly.instantiateStreaming"
4588 : "WebAssembly.compileStreaming";
4589
4590 SharedCompileArgs compileArgs =
4591 InitCompileArgs(cx, callArgs.get(instantiate ? 2 : 1), introducer);
4592 if (!compileArgs) {
4593 return false;
4594 }
4595
4596 RootedObject closure(
4597 cx, ResolveResponseClosure::create(cx, *compileArgs, promise, instantiate,
4598 importObj));
4599 if (!closure) {
4600 return false;
4601 }
4602
4603 RootedFunction onResolved(
4604 cx, NewNativeFunction(cx, ResolveResponse_OnFulfilled, 1, nullptr,
4605 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
4606 if (!onResolved) {
4607 return false;
4608 }
4609
4610 RootedFunction onRejected(
4611 cx, NewNativeFunction(cx, ResolveResponse_OnRejected, 1, nullptr,
4612 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
4613 if (!onRejected) {
4614 return false;
4615 }
4616
4617 onResolved->setExtendedSlot(0, ObjectValue(*closure));
4618 onRejected->setExtendedSlot(0, ObjectValue(*closure));
4619
4620 RootedObject resolve(cx,
4621 PromiseObject::unforgeableResolve(cx, callArgs.get(0)));
4622 if (!resolve) {
4623 return false;
4624 }
4625
4626 return JS::AddPromiseReactions(cx, resolve, onResolved, onRejected);
4627 }
4628
WebAssembly_compileStreaming(JSContext * cx,unsigned argc,Value * vp)4629 static bool WebAssembly_compileStreaming(JSContext* cx, unsigned argc,
4630 Value* vp) {
4631 if (!EnsureStreamSupport(cx)) {
4632 return false;
4633 }
4634
4635 Log(cx, "async compileStreaming() started");
4636
4637 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
4638 if (!promise) {
4639 return false;
4640 }
4641
4642 CallArgs callArgs = CallArgsFromVp(argc, vp);
4643
4644 if (!ResolveResponse(cx, callArgs, promise)) {
4645 return RejectWithPendingException(cx, promise, callArgs);
4646 }
4647
4648 callArgs.rval().setObject(*promise);
4649 return true;
4650 }
4651
WebAssembly_instantiateStreaming(JSContext * cx,unsigned argc,Value * vp)4652 static bool WebAssembly_instantiateStreaming(JSContext* cx, unsigned argc,
4653 Value* vp) {
4654 if (!EnsureStreamSupport(cx)) {
4655 return false;
4656 }
4657
4658 Log(cx, "async instantiateStreaming() started");
4659
4660 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
4661 if (!promise) {
4662 return false;
4663 }
4664
4665 CallArgs callArgs = CallArgsFromVp(argc, vp);
4666
4667 RootedObject firstArg(cx);
4668 RootedObject importObj(cx);
4669 if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj)) {
4670 return RejectWithPendingException(cx, promise, callArgs);
4671 }
4672
4673 if (!ResolveResponse(cx, callArgs, promise, true, importObj)) {
4674 return RejectWithPendingException(cx, promise, callArgs);
4675 }
4676
4677 callArgs.rval().setObject(*promise);
4678 return true;
4679 }
4680
4681 static const JSFunctionSpec WebAssembly_static_methods[] = {
4682 JS_FN(js_toSource_str, WebAssembly_toSource, 0, 0),
4683 JS_FN("compile", WebAssembly_compile, 1, JSPROP_ENUMERATE),
4684 JS_FN("instantiate", WebAssembly_instantiate, 1, JSPROP_ENUMERATE),
4685 JS_FN("validate", WebAssembly_validate, 1, JSPROP_ENUMERATE),
4686 JS_FN("compileStreaming", WebAssembly_compileStreaming, 1,
4687 JSPROP_ENUMERATE),
4688 JS_FN("instantiateStreaming", WebAssembly_instantiateStreaming, 1,
4689 JSPROP_ENUMERATE),
4690 JS_FS_END};
4691
CreateWebAssemblyObject(JSContext * cx,JSProtoKey key)4692 static JSObject* CreateWebAssemblyObject(JSContext* cx, JSProtoKey key) {
4693 MOZ_RELEASE_ASSERT(HasSupport(cx));
4694
4695 Handle<GlobalObject*> global = cx->global();
4696 RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
4697 if (!proto) {
4698 return nullptr;
4699 }
4700 return NewTenuredObjectWithGivenProto(cx, &WasmNamespaceObject::class_,
4701 proto);
4702 }
4703
WebAssemblyClassFinish(JSContext * cx,HandleObject object,HandleObject proto)4704 static bool WebAssemblyClassFinish(JSContext* cx, HandleObject object,
4705 HandleObject proto) {
4706 Handle<WasmNamespaceObject*> wasm = object.as<WasmNamespaceObject>();
4707
4708 struct NameAndProtoKey {
4709 const char* const name;
4710 JSProtoKey key;
4711 };
4712
4713 constexpr NameAndProtoKey entries[] = {
4714 {"Module", JSProto_WasmModule},
4715 {"Instance", JSProto_WasmInstance},
4716 {"Memory", JSProto_WasmMemory},
4717 {"Table", JSProto_WasmTable},
4718 {"Global", JSProto_WasmGlobal},
4719 #ifdef ENABLE_WASM_EXCEPTIONS
4720 {"Exception", JSProto_WasmException},
4721 {"RuntimeException", JSProto_WasmRuntimeException},
4722 #endif
4723 {"CompileError", GetExceptionProtoKey(JSEXN_WASMCOMPILEERROR)},
4724 {"LinkError", GetExceptionProtoKey(JSEXN_WASMLINKERROR)},
4725 {"RuntimeError", GetExceptionProtoKey(JSEXN_WASMRUNTIMEERROR)},
4726 };
4727
4728 RootedValue ctorValue(cx);
4729 RootedId id(cx);
4730 for (const auto& entry : entries) {
4731 const char* name = entry.name;
4732 JSProtoKey key = entry.key;
4733
4734 JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, key);
4735 if (!ctor) {
4736 return false;
4737 }
4738 ctorValue.setObject(*ctor);
4739
4740 JSAtom* className = Atomize(cx, name, strlen(name));
4741 if (!className) {
4742 return false;
4743 }
4744 id.set(AtomToId(className));
4745
4746 if (!DefineDataProperty(cx, wasm, id, ctorValue, 0)) {
4747 return false;
4748 }
4749 }
4750
4751 return true;
4752 }
4753
4754 static const ClassSpec WebAssemblyClassSpec = {CreateWebAssemblyObject,
4755 nullptr,
4756 WebAssembly_static_methods,
4757 nullptr,
4758 nullptr,
4759 nullptr,
4760 WebAssemblyClassFinish};
4761
4762 const JSClass js::WasmNamespaceObject::class_ = {
4763 js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly),
4764 JS_NULL_CLASS_OPS, &WebAssemblyClassSpec};
4765
4766 // Sundry
4767
4768 #ifdef JS_64BIT
4769 # ifdef ENABLE_WASM_CRANELIFT
4770 // TODO (large ArrayBuffer): Cranelift needs to be updated to use more than the
4771 // low 32 bits of the boundsCheckLimit, so for now we limit its heap size to
4772 // something that satisfies the 32-bit invariants.
4773 //
4774 // The "-2" here accounts for the !huge-memory case in CreateSpecificWasmBuffer,
4775 // which is guarding against an overflow. Also see
4776 // WasmMemoryObject::boundsCheckLimit() for related assertions.
MaxMemory32Pages()4777 wasm::Pages wasm::MaxMemory32Pages() {
4778 size_t desired = MaxMemory32LimitField - 2;
4779 size_t actual = ArrayBufferObject::maxBufferByteLength() / PageSize;
4780 return wasm::Pages(std::min(desired, actual));
4781 }
4782
MaxMemory32BoundsCheckLimit()4783 size_t wasm::MaxMemory32BoundsCheckLimit() {
4784 return UINT32_MAX - 2 * PageSize + 1;
4785 }
4786 # else
MaxMemory32Pages()4787 wasm::Pages wasm::MaxMemory32Pages() {
4788 size_t desired = MaxMemory32LimitField;
4789 size_t actual = ArrayBufferObject::maxBufferByteLength() / PageSize;
4790 return wasm::Pages(std::min(desired, actual));
4791 }
4792
MaxMemory32BoundsCheckLimit()4793 size_t wasm::MaxMemory32BoundsCheckLimit() { return size_t(UINT32_MAX) + 1; }
4794 # endif
4795 #else
MaxMemory32Pages()4796 wasm::Pages wasm::MaxMemory32Pages() {
4797 MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() >= INT32_MAX / PageSize);
4798 return wasm::Pages(INT32_MAX / PageSize);
4799 }
4800
MaxMemory32BoundsCheckLimit()4801 size_t wasm::MaxMemory32BoundsCheckLimit() { return size_t(INT32_MAX) + 1; }
4802 #endif
4803