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 "builtin/TypedObject.h"
29 #include "gc/FreeOp.h"
30 #include "jit/AtomicOperations.h"
31 #include "jit/JitOptions.h"
32 #include "jit/Simulator.h"
33 #include "js/Printf.h"
34 #include "js/PropertySpec.h" // JS_{PS,FN}{,_END}
35 #if defined(JS_CODEGEN_X64) // Assembler::HasSSE41
36 # include "jit/x64/Assembler-x64.h"
37 # include "jit/x86-shared/Architecture-x86-shared.h"
38 # include "jit/x86-shared/Assembler-x86-shared.h"
39 #endif
40 #include "util/StringBuffer.h"
41 #include "util/Text.h"
42 #include "vm/ErrorObject.h"
43 #include "vm/FunctionFlags.h" // js::FunctionFlags
44 #include "vm/GlobalObject.h" // js::GlobalObject
45 #include "vm/Interpreter.h"
46 #include "vm/PlainObject.h" // js::PlainObject
47 #include "vm/PromiseObject.h" // js::PromiseObject
48 #include "vm/StringType.h"
49 #include "vm/Warnings.h" // js::WarnNumberASCII
50 #include "wasm/WasmBaselineCompile.h"
51 #include "wasm/WasmCompile.h"
52 #include "wasm/WasmCraneliftCompile.h"
53 #include "wasm/WasmInstance.h"
54 #include "wasm/WasmIonCompile.h"
55 #include "wasm/WasmModule.h"
56 #include "wasm/WasmProcess.h"
57 #include "wasm/WasmSignalHandlers.h"
58 #include "wasm/WasmStubs.h"
59 #include "wasm/WasmValidate.h"
60
61 #include "vm/ArrayBufferObject-inl.h"
62 #include "vm/JSObject-inl.h"
63 #include "vm/NativeObject-inl.h"
64
65 using namespace js;
66 using namespace js::jit;
67 using namespace js::wasm;
68
69 using mozilla::CheckedInt;
70 using mozilla::MakeSpan;
71 using mozilla::Nothing;
72 using mozilla::RangedPtr;
73
74 extern mozilla::Atomic<bool> fuzzingSafe;
75
WasmMultiValueFlag(JSContext * cx)76 static inline bool WasmMultiValueFlag(JSContext* cx) {
77 #ifdef ENABLE_WASM_MULTI_VALUE
78 return cx->options().wasmMultiValue();
79 #else
80 return false;
81 #endif
82 }
83
WasmSimdFlag(JSContext * cx)84 static inline bool WasmSimdFlag(JSContext* cx) {
85 #ifdef ENABLE_WASM_SIMD
86 return cx->options().wasmSimd() && js::jit::JitSupportsWasmSimd();
87 #else
88 return false;
89 #endif
90 }
91
92 // Compiler availability predicates. These must be kept in sync with the
93 // feature predicates in the next section below.
94 //
95 // These can't call the feature predicates since the feature predicates call
96 // back to these predicates. So there will be a small amount of duplicated
97 // logic here, but as compilers reach feature parity that duplication will go
98 // away.
99 //
100 // There's a static precedence order between the optimizing compilers. This
101 // order currently ranks Cranelift over Ion on all platforms because Cranelift
102 // is disabled by default on all platforms: anyone who has enabled Cranelift
103 // will wish to use it instead of Ion.
104 //
105 // The precedence order is implemented by guards in IonAvailable() and
106 // CraneliftAvailable(). We expect that it will become more complex as the
107 // default settings change. But it should remain static.
108
BaselineAvailable(JSContext * cx)109 bool wasm::BaselineAvailable(JSContext* cx) {
110 // Baseline supports every feature supported by any compiler.
111 return cx->options().wasmBaseline() && BaselinePlatformSupport();
112 }
113
IonAvailable(JSContext * cx)114 bool wasm::IonAvailable(JSContext* cx) {
115 if (!cx->options().wasmIon() || !IonPlatformSupport()) {
116 return false;
117 }
118 bool isDisabled = false;
119 MOZ_ALWAYS_TRUE(IonDisabledByFeatures(cx, &isDisabled));
120 return !isDisabled && !CraneliftAvailable(cx);
121 }
122
123 template <size_t ArrayLength>
Append(JSStringBuilder * reason,const char (& s)[ArrayLength],char * sep)124 static inline bool Append(JSStringBuilder* reason, const char (&s)[ArrayLength],
125 char* sep) {
126 if ((*sep && !reason->append(*sep)) || !reason->append(s)) {
127 return false;
128 }
129 *sep = ',';
130 return true;
131 }
132
IonDisabledByFeatures(JSContext * cx,bool * isDisabled,JSStringBuilder * reason)133 bool wasm::IonDisabledByFeatures(JSContext* cx, bool* isDisabled,
134 JSStringBuilder* reason) {
135 // Ion has no debugging support, no gc support.
136 bool debug = cx->realm() && cx->realm()->debuggerObservesAsmJS();
137 bool gc = cx->options().wasmGc();
138 if (reason) {
139 char sep = 0;
140 if (debug && !Append(reason, "debug", &sep)) {
141 return false;
142 }
143 if (gc && !Append(reason, "gc", &sep)) {
144 return false;
145 }
146 }
147 *isDisabled = debug || gc;
148 return true;
149 }
150
CraneliftAvailable(JSContext * cx)151 bool wasm::CraneliftAvailable(JSContext* cx) {
152 if (!cx->options().wasmCranelift() || !CraneliftPlatformSupport()) {
153 return false;
154 }
155 bool isDisabled = false;
156 MOZ_ALWAYS_TRUE(CraneliftDisabledByFeatures(cx, &isDisabled));
157 return !isDisabled;
158 }
159
CraneliftDisabledByFeatures(JSContext * cx,bool * isDisabled,JSStringBuilder * reason)160 bool wasm::CraneliftDisabledByFeatures(JSContext* cx, bool* isDisabled,
161 JSStringBuilder* reason) {
162 // Cranelift has no debugging support, no gc support, no multi-value support,
163 // no threads, no simd, and on ARM64, no reference types.
164 bool debug = cx->realm() && cx->realm()->debuggerObservesAsmJS();
165 bool gc = cx->options().wasmGc();
166 bool multiValue = WasmMultiValueFlag(cx);
167 bool threads =
168 cx->realm() &&
169 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled();
170 #if defined(JS_CODEGEN_ARM64)
171 bool reftypesOnArm64 = cx->options().wasmReftypes();
172 #else
173 // On other platforms, assume reftypes has been implemented.
174 bool reftypesOnArm64 = false;
175 #endif
176 bool simd = WasmSimdFlag(cx);
177 if (reason) {
178 char sep = 0;
179 if (debug && !Append(reason, "debug", &sep)) {
180 return false;
181 }
182 if (gc && !Append(reason, "gc", &sep)) {
183 return false;
184 }
185 if (multiValue && !Append(reason, "multi-value", &sep)) {
186 return false;
187 }
188 if (threads && !Append(reason, "threads", &sep)) {
189 return false;
190 }
191 if (reftypesOnArm64 && !Append(reason, "reftypes", &sep)) {
192 return false;
193 }
194 if (simd && !Append(reason, "simd", &sep)) {
195 return false;
196 }
197 }
198 *isDisabled = debug || gc || multiValue || threads || reftypesOnArm64 || simd;
199 return true;
200 }
201
202 // Feature predicates. These must be kept in sync with the predicates in the
203 // section above.
204 //
205 // The meaning of these predicates is tricky: A predicate is true for a feature
206 // if the feature is enabled and/or compiled-in *and* we have *at least one*
207 // compiler that can support the feature. Subsequent compiler selection must
208 // ensure that only compilers that actually support the feature are used.
209
ReftypesAvailable(JSContext * cx)210 bool wasm::ReftypesAvailable(JSContext* cx) {
211 // All compilers support reference types, except Cranelift on ARM64.
212 #ifdef ENABLE_WASM_REFTYPES
213 return cx->options().wasmReftypes() &&
214 (BaselineAvailable(cx) || IonAvailable(cx) || CraneliftAvailable(cx));
215 #else
216 return false;
217 #endif
218 }
219
GcTypesAvailable(JSContext * cx)220 bool wasm::GcTypesAvailable(JSContext* cx) {
221 // Cranelift and Ion do not support GC.
222 return cx->options().wasmGc() && BaselineAvailable(cx);
223 }
224
MultiValuesAvailable(JSContext * cx)225 bool wasm::MultiValuesAvailable(JSContext* cx) {
226 // Cranelift does not support multi-value.
227 return WasmMultiValueFlag(cx) && (BaselineAvailable(cx) || IonAvailable(cx));
228 }
229
SimdAvailable(JSContext * cx)230 bool wasm::SimdAvailable(JSContext* cx) {
231 // Cranelift does not support SIMD.
232 return WasmSimdFlag(cx) && (BaselineAvailable(cx) || IonAvailable(cx));
233 }
234
ThreadsAvailable(JSContext * cx)235 bool wasm::ThreadsAvailable(JSContext* cx) {
236 // Cranelift does not support atomics.
237 return cx->realm() &&
238 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled() &&
239 (BaselineAvailable(cx) || IonAvailable(cx));
240 }
241
HasPlatformSupport(JSContext * cx)242 bool wasm::HasPlatformSupport(JSContext* cx) {
243 #if !MOZ_LITTLE_ENDIAN() || defined(JS_CODEGEN_NONE)
244 return false;
245 #endif
246
247 if (gc::SystemPageSize() > wasm::PageSize) {
248 return false;
249 }
250
251 if (!JitOptions.supportsFloatingPoint) {
252 return false;
253 }
254
255 if (!JitOptions.supportsUnalignedAccesses) {
256 return false;
257 }
258
259 if (!wasm::EnsureFullSignalHandlers(cx)) {
260 return false;
261 }
262
263 // Wasm threads require 8-byte lock-free atomics.
264 if (!jit::AtomicOperations::isLockfree8()) {
265 return false;
266 }
267
268 #ifdef JS_SIMULATOR
269 if (!Simulator::supportsAtomics()) {
270 return false;
271 }
272 #endif
273
274 // Test only whether the compilers are supported on the hardware, not whether
275 // they are enabled.
276 return BaselinePlatformSupport() || IonPlatformSupport() ||
277 CraneliftPlatformSupport();
278 }
279
HasSupport(JSContext * cx)280 bool wasm::HasSupport(JSContext* cx) {
281 // If the general wasm pref is on, it's on for everything.
282 bool prefEnabled = cx->options().wasm();
283 // If the general pref is off, check trusted principals.
284 if (MOZ_UNLIKELY(!prefEnabled)) {
285 prefEnabled = cx->options().wasmForTrustedPrinciples() && cx->realm() &&
286 cx->realm()->principals() &&
287 cx->realm()->principals()->isSystemOrAddonPrincipal();
288 }
289 return prefEnabled && HasPlatformSupport(cx) &&
290 (BaselineAvailable(cx) || IonAvailable(cx) || CraneliftAvailable(cx));
291 }
292
StreamingCompilationAvailable(JSContext * cx)293 bool wasm::StreamingCompilationAvailable(JSContext* cx) {
294 // This should match EnsureStreamSupport().
295 return HasSupport(cx) &&
296 cx->runtime()->offThreadPromiseState.ref().initialized() &&
297 CanUseExtraThreads() && cx->runtime()->consumeStreamCallback &&
298 cx->runtime()->reportStreamErrorCallback;
299 }
300
CodeCachingAvailable(JSContext * cx)301 bool wasm::CodeCachingAvailable(JSContext* cx) {
302 // At the moment, we require Ion support for code caching. The main reason
303 // for this is that wasm::CompileAndSerialize() does not have access to
304 // information about which optimizing compiler it should use. See comments in
305 // CompileAndSerialize(), below.
306 return StreamingCompilationAvailable(cx) && IonAvailable(cx);
307 }
308
CheckRefType(JSContext * cx,RefType::Kind targetTypeKind,HandleValue v,MutableHandleFunction fnval,MutableHandleAnyRef refval)309 bool wasm::CheckRefType(JSContext* cx, RefType::Kind targetTypeKind,
310 HandleValue v, MutableHandleFunction fnval,
311 MutableHandleAnyRef refval) {
312 switch (targetTypeKind) {
313 case RefType::Func:
314 if (!CheckFuncRefValue(cx, v, fnval)) {
315 return false;
316 }
317 break;
318 case RefType::Any:
319 if (!BoxAnyRef(cx, v, refval)) {
320 return false;
321 }
322 break;
323 case RefType::TypeIndex:
324 MOZ_CRASH("temporarily unsupported Ref type");
325 }
326 return true;
327 }
328
ToWebAssemblyValue(JSContext * cx,ValType targetType,HandleValue v,MutableHandleVal val)329 static bool ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v,
330 MutableHandleVal val) {
331 switch (targetType.kind()) {
332 case ValType::I32: {
333 int32_t i32;
334 if (!ToInt32(cx, v, &i32)) {
335 return false;
336 }
337 val.set(Val(uint32_t(i32)));
338 return true;
339 }
340 case ValType::F32: {
341 double d;
342 if (!ToNumber(cx, v, &d)) {
343 return false;
344 }
345 val.set(Val(float(d)));
346 return true;
347 }
348 case ValType::F64: {
349 double d;
350 if (!ToNumber(cx, v, &d)) {
351 return false;
352 }
353 val.set(Val(d));
354 return true;
355 }
356 case ValType::I64: {
357 BigInt* bigint = ToBigInt(cx, v);
358 if (!bigint) {
359 return false;
360 }
361 val.set(Val(BigInt::toUint64(bigint)));
362 return true;
363 }
364 case ValType::Ref: {
365 RootedFunction fun(cx);
366 RootedAnyRef any(cx, AnyRef::null());
367 if (!CheckRefType(cx, targetType.refTypeKind(), v, &fun, &any)) {
368 return false;
369 }
370 switch (targetType.refTypeKind()) {
371 case RefType::Func:
372 val.set(Val(RefType::func(), FuncRef::fromJSFunction(fun)));
373 return true;
374 case RefType::Any:
375 val.set(Val(targetType.refType(), any));
376 return true;
377 case RefType::TypeIndex:
378 break;
379 }
380 break;
381 }
382 case ValType::V128: {
383 break;
384 }
385 }
386 MOZ_CRASH("unexpected import value type, caller must guard");
387 }
388
ToJSValue(JSContext * cx,const Val & val,MutableHandleValue out)389 static bool ToJSValue(JSContext* cx, const Val& val, MutableHandleValue out) {
390 switch (val.type().kind()) {
391 case ValType::I32:
392 out.setInt32(val.i32());
393 return true;
394 case ValType::F32:
395 out.setDouble(JS::CanonicalizeNaN(double(val.f32())));
396 return true;
397 case ValType::F64:
398 out.setDouble(JS::CanonicalizeNaN(val.f64()));
399 return true;
400 case ValType::I64: {
401 BigInt* bi = BigInt::createFromInt64(cx, val.i64());
402 if (!bi) {
403 return false;
404 }
405 out.setBigInt(bi);
406 return true;
407 }
408 case ValType::Ref:
409 switch (val.type().refTypeKind()) {
410 case RefType::Func:
411 out.set(UnboxFuncRef(FuncRef::fromAnyRefUnchecked(val.ref())));
412 return true;
413 case RefType::Any:
414 out.set(UnboxAnyRef(val.ref()));
415 return true;
416 case RefType::TypeIndex:
417 break;
418 }
419 break;
420 case ValType::V128:
421 break;
422 }
423 MOZ_CRASH("unexpected type when translating to a JS value");
424 }
425
426 // ============================================================================
427 // Imports
428
ThrowBadImportArg(JSContext * cx)429 static bool ThrowBadImportArg(JSContext* cx) {
430 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
431 JSMSG_WASM_BAD_IMPORT_ARG);
432 return false;
433 }
434
ThrowBadImportType(JSContext * cx,const char * field,const char * str)435 static bool ThrowBadImportType(JSContext* cx, const char* field,
436 const char* str) {
437 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
438 JSMSG_WASM_BAD_IMPORT_TYPE, field, str);
439 return false;
440 }
441
GetProperty(JSContext * cx,HandleObject obj,const char * chars,MutableHandleValue v)442 static bool GetProperty(JSContext* cx, HandleObject obj, const char* chars,
443 MutableHandleValue v) {
444 JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
445 if (!atom) {
446 return false;
447 }
448
449 RootedId id(cx, AtomToId(atom));
450 return GetProperty(cx, obj, obj, id, v);
451 }
452
GetImports(JSContext * cx,const Module & module,HandleObject importObj,ImportValues * imports)453 bool js::wasm::GetImports(JSContext* cx, const Module& module,
454 HandleObject importObj, ImportValues* imports) {
455 if (!module.imports().empty() && !importObj) {
456 return ThrowBadImportArg(cx);
457 }
458
459 const Metadata& metadata = module.metadata();
460
461 uint32_t globalIndex = 0;
462 const GlobalDescVector& globals = metadata.globals;
463 uint32_t tableIndex = 0;
464 const TableDescVector& tables = metadata.tables;
465 for (const Import& import : module.imports()) {
466 RootedValue v(cx);
467 if (!GetProperty(cx, importObj, import.module.get(), &v)) {
468 return false;
469 }
470
471 if (!v.isObject()) {
472 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
473 JSMSG_WASM_BAD_IMPORT_FIELD,
474 import.module.get());
475 return false;
476 }
477
478 RootedObject obj(cx, &v.toObject());
479 if (!GetProperty(cx, obj, import.field.get(), &v)) {
480 return false;
481 }
482
483 switch (import.kind) {
484 case DefinitionKind::Function: {
485 if (!IsFunctionObject(v)) {
486 return ThrowBadImportType(cx, import.field.get(), "Function");
487 }
488
489 if (!imports->funcs.append(&v.toObject().as<JSFunction>())) {
490 return false;
491 }
492
493 break;
494 }
495 case DefinitionKind::Table: {
496 const uint32_t index = tableIndex++;
497 if (!v.isObject() || !v.toObject().is<WasmTableObject>()) {
498 return ThrowBadImportType(cx, import.field.get(), "Table");
499 }
500
501 RootedWasmTableObject obj(cx, &v.toObject().as<WasmTableObject>());
502 if (obj->table().kind() != tables[index].kind) {
503 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
504 JSMSG_WASM_BAD_TBL_TYPE_LINK);
505 return false;
506 }
507
508 if (!imports->tables.append(obj)) {
509 return false;
510 }
511 break;
512 }
513 case DefinitionKind::Memory: {
514 if (!v.isObject() || !v.toObject().is<WasmMemoryObject>()) {
515 return ThrowBadImportType(cx, import.field.get(), "Memory");
516 }
517
518 MOZ_ASSERT(!imports->memory);
519 imports->memory = &v.toObject().as<WasmMemoryObject>();
520 break;
521 }
522 case DefinitionKind::Global: {
523 const uint32_t index = globalIndex++;
524 const GlobalDesc& global = globals[index];
525 MOZ_ASSERT(global.importIndex() == index);
526
527 RootedVal val(cx);
528 if (v.isObject() && v.toObject().is<WasmGlobalObject>()) {
529 RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
530
531 if (obj->isMutable() != global.isMutable()) {
532 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
533 JSMSG_WASM_BAD_GLOB_MUT_LINK);
534 return false;
535 }
536 if (obj->type() != global.type()) {
537 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
538 JSMSG_WASM_BAD_GLOB_TYPE_LINK);
539 return false;
540 }
541
542 if (imports->globalObjs.length() <= index &&
543 !imports->globalObjs.resize(index + 1)) {
544 ReportOutOfMemory(cx);
545 return false;
546 }
547 imports->globalObjs[index] = obj;
548 obj->val(&val);
549 } else {
550 if (IsNumberType(global.type())) {
551 if (global.type() == ValType::I64 && !v.isBigInt()) {
552 return ThrowBadImportType(cx, import.field.get(), "BigInt");
553 }
554 if (global.type() != ValType::I64 && !v.isNumber()) {
555 return ThrowBadImportType(cx, import.field.get(), "Number");
556 }
557 } else {
558 MOZ_ASSERT(global.type().isReference());
559 if (!global.type().isAnyRef() && !v.isObjectOrNull()) {
560 return ThrowBadImportType(cx, import.field.get(),
561 "Object-or-null value required for "
562 "non-externref reference type");
563 }
564 }
565
566 if (global.isMutable()) {
567 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
568 JSMSG_WASM_BAD_GLOB_MUT_LINK);
569 return false;
570 }
571
572 if (global.type() == ValType::V128) {
573 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
574 JSMSG_WASM_BAD_VAL_TYPE);
575 return false;
576 }
577
578 if (!ToWebAssemblyValue(cx, global.type(), v, &val)) {
579 return false;
580 }
581 }
582
583 if (!imports->globalValues.append(val)) {
584 return false;
585 }
586
587 break;
588 }
589 }
590 }
591
592 MOZ_ASSERT(globalIndex == globals.length() ||
593 !globals[globalIndex].isImport());
594
595 return true;
596 }
597
DescribeScriptedCaller(JSContext * cx,ScriptedCaller * caller,const char * introducer)598 static bool DescribeScriptedCaller(JSContext* cx, ScriptedCaller* caller,
599 const char* introducer) {
600 // Note: JS::DescribeScriptedCaller returns whether a scripted caller was
601 // found, not whether an error was thrown. This wrapper function converts
602 // back to the more ordinary false-if-error form.
603
604 JS::AutoFilename af;
605 if (JS::DescribeScriptedCaller(cx, &af, &caller->line)) {
606 caller->filename =
607 FormatIntroducedFilename(cx, af.get(), caller->line, introducer);
608 if (!caller->filename) {
609 return false;
610 }
611 }
612
613 return true;
614 }
615
616 // ============================================================================
617 // Testing / Fuzzing support
618
Eval(JSContext * cx,Handle<TypedArrayObject * > code,HandleObject importObj,MutableHandleWasmInstanceObject instanceObj)619 bool wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code,
620 HandleObject importObj,
621 MutableHandleWasmInstanceObject instanceObj) {
622 if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly)) {
623 return false;
624 }
625
626 MutableBytes bytecode = cx->new_<ShareableBytes>();
627 if (!bytecode) {
628 return false;
629 }
630
631 if (!bytecode->append((uint8_t*)code->dataPointerEither().unwrap(),
632 code->byteLength())) {
633 ReportOutOfMemory(cx);
634 return false;
635 }
636
637 ScriptedCaller scriptedCaller;
638 if (!DescribeScriptedCaller(cx, &scriptedCaller, "wasm_eval")) {
639 return false;
640 }
641
642 SharedCompileArgs compileArgs =
643 CompileArgs::build(cx, std::move(scriptedCaller));
644 if (!compileArgs) {
645 return false;
646 }
647
648 UniqueChars error;
649 UniqueCharsVector warnings;
650 SharedModule module =
651 CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
652 if (!module) {
653 if (error) {
654 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
655 JSMSG_WASM_COMPILE_ERROR, error.get());
656 return false;
657 }
658 ReportOutOfMemory(cx);
659 return false;
660 }
661
662 Rooted<ImportValues> imports(cx);
663 if (!GetImports(cx, *module, importObj, imports.address())) {
664 return false;
665 }
666
667 return module->instantiate(cx, imports.get(), nullptr, instanceObj);
668 }
669
670 struct MOZ_STACK_CLASS SerializeListener : JS::OptimizedEncodingListener {
671 // MOZ_STACK_CLASS means these can be nops.
AddRefSerializeListener672 MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override { return 0; }
ReleaseSerializeListener673 MozExternalRefCountType MOZ_XPCOM_ABI Release() override { return 0; }
674
675 DebugOnly<bool> called = false;
676 Bytes* serialized;
SerializeListenerSerializeListener677 explicit SerializeListener(Bytes* serialized) : serialized(serialized) {}
678
storeOptimizedEncodingSerializeListener679 void storeOptimizedEncoding(JS::UniqueOptimizedEncodingBytes bytes) override {
680 MOZ_ASSERT(!called);
681 called = true;
682 if (serialized->resize(bytes->length())) {
683 memcpy(serialized->begin(), bytes->begin(), bytes->length());
684 }
685 }
686 };
687
CompileAndSerialize(const ShareableBytes & bytecode,Bytes * serialized)688 bool wasm::CompileAndSerialize(const ShareableBytes& bytecode,
689 Bytes* serialized) {
690 MutableCompileArgs compileArgs = js_new<CompileArgs>(ScriptedCaller());
691 if (!compileArgs) {
692 return false;
693 }
694
695 // The caller has ensured CodeCachingAvailable(). Moreover, we want to ensure
696 // we go straight to tier-2 so that we synchronously call
697 // JS::OptimizedEncodingListener::storeOptimizedEncoding().
698 compileArgs->baselineEnabled = false;
699
700 // We always pick Ion here, and we depend on CodeCachingAvailable() having
701 // determined that Ion is available, see comments at CodeCachingAvailable().
702 // To do better, we need to pass information about which compiler that should
703 // be used into CompileAndSerialize().
704 compileArgs->ionEnabled = true;
705
706 // The caller must ensure that huge memory support is configured the same in
707 // the receiving process of this serialized module.
708 compileArgs->hugeMemory = wasm::IsHugeMemoryEnabled();
709
710 SerializeListener listener(serialized);
711
712 UniqueChars error;
713 UniqueCharsVector warnings;
714 SharedModule module =
715 CompileBuffer(*compileArgs, bytecode, &error, &warnings, &listener);
716 if (!module) {
717 fprintf(stderr, "Compilation error: %s\n", error ? error.get() : "oom");
718 return false;
719 }
720
721 MOZ_ASSERT(module->code().hasTier(Tier::Serialized));
722 MOZ_ASSERT(listener.called);
723 return !listener.serialized->empty();
724 }
725
DeserializeModule(JSContext * cx,const Bytes & serialized,MutableHandleObject moduleObj)726 bool wasm::DeserializeModule(JSContext* cx, const Bytes& serialized,
727 MutableHandleObject moduleObj) {
728 MutableModule module =
729 Module::deserialize(serialized.begin(), serialized.length());
730 if (!module) {
731 ReportOutOfMemory(cx);
732 return false;
733 }
734
735 moduleObj.set(module->createObject(cx));
736 return !!moduleObj;
737 }
738
739 // ============================================================================
740 // Common functions
741
742 // '[EnforceRange] unsigned long' types are coerced with
743 // ConvertToInt(v, 32, 'unsigned')
744 // defined in Web IDL Section 3.2.4.9.
EnforceRangeU32(JSContext * cx,HandleValue v,const char * kind,const char * noun,uint32_t * u32)745 static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
746 const char* noun, uint32_t* u32) {
747 // Step 4.
748 double x;
749 if (!ToNumber(cx, v, &x)) {
750 return false;
751 }
752
753 // Step 5.
754 if (mozilla::IsNegativeZero(x)) {
755 x = 0.0;
756 }
757
758 // Step 6.1.
759 if (!mozilla::IsFinite(x)) {
760 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
761 JSMSG_WASM_BAD_UINT32, kind, noun);
762 return false;
763 }
764
765 // Step 6.2.
766 x = JS::ToInteger(x);
767
768 // Step 6.3.
769 if (x < 0 || x > double(UINT32_MAX)) {
770 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
771 JSMSG_WASM_BAD_UINT32, kind, noun);
772 return false;
773 }
774
775 *u32 = uint32_t(x);
776 MOZ_ASSERT(double(*u32) == x);
777 return true;
778 }
779
GetLimits(JSContext * cx,HandleObject obj,uint32_t maxInitial,uint32_t maxMaximum,const char * kind,Limits * limits,Shareable allowShared)780 static bool GetLimits(JSContext* cx, HandleObject obj, uint32_t maxInitial,
781 uint32_t maxMaximum, const char* kind, Limits* limits,
782 Shareable allowShared) {
783 JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
784 if (!initialAtom) {
785 return false;
786 }
787 RootedId initialId(cx, AtomToId(initialAtom));
788
789 RootedValue initialVal(cx);
790 if (!GetProperty(cx, obj, obj, initialId, &initialVal)) {
791 return false;
792 }
793
794 if (!EnforceRangeU32(cx, initialVal, kind, "initial size",
795 &limits->initial)) {
796 return false;
797 }
798
799 if (limits->initial > maxInitial) {
800 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
801 kind, "initial size");
802 return false;
803 }
804
805 JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
806 if (!maximumAtom) {
807 return false;
808 }
809 RootedId maximumId(cx, AtomToId(maximumAtom));
810
811 RootedValue maxVal(cx);
812 if (!GetProperty(cx, obj, obj, maximumId, &maxVal)) {
813 return false;
814 }
815
816 // maxVal does not have a default value.
817 if (!maxVal.isUndefined()) {
818 limits->maximum.emplace();
819 if (!EnforceRangeU32(cx, maxVal, kind, "maximum size",
820 limits->maximum.ptr())) {
821 return false;
822 }
823
824 if (*limits->maximum > maxMaximum || limits->initial > *limits->maximum) {
825 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
826 JSMSG_WASM_BAD_RANGE, kind, "maximum size");
827 return false;
828 }
829 }
830
831 limits->shared = Shareable::False;
832
833 if (allowShared == Shareable::True) {
834 JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
835 if (!sharedAtom) {
836 return false;
837 }
838 RootedId sharedId(cx, AtomToId(sharedAtom));
839
840 RootedValue sharedVal(cx);
841 if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
842 return false;
843 }
844
845 // shared's default value is false, which is already the value set above.
846 if (!sharedVal.isUndefined()) {
847 limits->shared =
848 ToBoolean(sharedVal) ? Shareable::True : Shareable::False;
849
850 if (limits->shared == Shareable::True) {
851 if (maxVal.isUndefined()) {
852 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
853 JSMSG_WASM_MISSING_MAXIMUM, kind);
854 return false;
855 }
856
857 if (!cx->realm()
858 ->creationOptions()
859 .getSharedMemoryAndAtomicsEnabled()) {
860 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
861 JSMSG_WASM_NO_SHMEM_LINK);
862 return false;
863 }
864 }
865 }
866 }
867
868 return true;
869 }
870
871 template <class Class, const char* name>
CreateWasmConstructor(JSContext * cx,JSProtoKey key)872 static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) {
873 RootedAtom className(cx, Atomize(cx, name, strlen(name)));
874 if (!className) {
875 return nullptr;
876 }
877
878 return NewNativeConstructor(cx, Class::construct, 1, className);
879 }
880
881 // ============================================================================
882 // WebAssembly.Module class and methods
883
884 const JSClassOps WasmModuleObject::classOps_ = {
885 nullptr, // addProperty
886 nullptr, // delProperty
887 nullptr, // enumerate
888 nullptr, // newEnumerate
889 nullptr, // resolve
890 nullptr, // mayResolve
891 WasmModuleObject::finalize, // finalize
892 nullptr, // call
893 nullptr, // hasInstance
894 nullptr, // construct
895 nullptr, // trace
896 };
897
898 const JSClass WasmModuleObject::class_ = {
899 "WebAssembly.Module",
900 JSCLASS_DELAY_METADATA_BUILDER |
901 JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
902 JSCLASS_FOREGROUND_FINALIZE,
903 &WasmModuleObject::classOps_,
904 &WasmModuleObject::classSpec_,
905 };
906
907 const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_;
908
909 static constexpr char WasmModuleName[] = "Module";
910
911 const ClassSpec WasmModuleObject::classSpec_ = {
912 CreateWasmConstructor<WasmModuleObject, WasmModuleName>,
913 GenericCreatePrototype<WasmModuleObject>,
914 WasmModuleObject::static_methods,
915 nullptr,
916 WasmModuleObject::methods,
917 WasmModuleObject::properties,
918 nullptr,
919 ClassSpec::DontDefineConstructor};
920
921 const JSPropertySpec WasmModuleObject::properties[] = {
922 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Module", JSPROP_READONLY),
923 JS_PS_END};
924
925 const JSFunctionSpec WasmModuleObject::methods[] = {JS_FS_END};
926
927 const JSFunctionSpec WasmModuleObject::static_methods[] = {
928 JS_FN("imports", WasmModuleObject::imports, 1, JSPROP_ENUMERATE),
929 JS_FN("exports", WasmModuleObject::exports, 1, JSPROP_ENUMERATE),
930 JS_FN("customSections", WasmModuleObject::customSections, 2,
931 JSPROP_ENUMERATE),
932 JS_FS_END};
933
934 /* static */
finalize(JSFreeOp * fop,JSObject * obj)935 void WasmModuleObject::finalize(JSFreeOp* fop, JSObject* obj) {
936 const Module& module = obj->as<WasmModuleObject>().module();
937 obj->zone()->decJitMemory(module.codeLength(module.code().stableTier()));
938 fop->release(obj, &module, module.gcMallocBytesExcludingCode(),
939 MemoryUse::WasmModule);
940 }
941
IsModuleObject(JSObject * obj,const Module ** module)942 static bool IsModuleObject(JSObject* obj, const Module** module) {
943 WasmModuleObject* mobj = obj->maybeUnwrapIf<WasmModuleObject>();
944 if (!mobj) {
945 return false;
946 }
947
948 *module = &mobj->module();
949 return true;
950 }
951
GetModuleArg(JSContext * cx,CallArgs args,uint32_t numRequired,const char * name,const Module ** module)952 static bool GetModuleArg(JSContext* cx, CallArgs args, uint32_t numRequired,
953 const char* name, const Module** module) {
954 if (!args.requireAtLeast(cx, name, numRequired)) {
955 return false;
956 }
957
958 if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
959 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
960 JSMSG_WASM_BAD_MOD_ARG);
961 return false;
962 }
963
964 return true;
965 }
966
967 struct KindNames {
968 RootedPropertyName kind;
969 RootedPropertyName table;
970 RootedPropertyName memory;
971 RootedPropertyName signature;
972
KindNamesKindNames973 explicit KindNames(JSContext* cx)
974 : kind(cx), table(cx), memory(cx), signature(cx) {}
975 };
976
InitKindNames(JSContext * cx,KindNames * names)977 static bool InitKindNames(JSContext* cx, KindNames* names) {
978 JSAtom* kind = Atomize(cx, "kind", strlen("kind"));
979 if (!kind) {
980 return false;
981 }
982 names->kind = kind->asPropertyName();
983
984 JSAtom* table = Atomize(cx, "table", strlen("table"));
985 if (!table) {
986 return false;
987 }
988 names->table = table->asPropertyName();
989
990 JSAtom* memory = Atomize(cx, "memory", strlen("memory"));
991 if (!memory) {
992 return false;
993 }
994 names->memory = memory->asPropertyName();
995
996 JSAtom* signature = Atomize(cx, "signature", strlen("signature"));
997 if (!signature) {
998 return false;
999 }
1000 names->signature = signature->asPropertyName();
1001
1002 return true;
1003 }
1004
KindToString(JSContext * cx,const KindNames & names,DefinitionKind kind)1005 static JSString* KindToString(JSContext* cx, const KindNames& names,
1006 DefinitionKind kind) {
1007 switch (kind) {
1008 case DefinitionKind::Function:
1009 return cx->names().function;
1010 case DefinitionKind::Table:
1011 return names.table;
1012 case DefinitionKind::Memory:
1013 return names.memory;
1014 case DefinitionKind::Global:
1015 return cx->names().global;
1016 }
1017
1018 MOZ_CRASH("invalid kind");
1019 }
1020
FuncTypeToString(JSContext * cx,const FuncType & funcType)1021 static JSString* FuncTypeToString(JSContext* cx, const FuncType& funcType) {
1022 JSStringBuilder buf(cx);
1023 if (!buf.append('(')) {
1024 return nullptr;
1025 }
1026
1027 bool first = true;
1028 for (ValType arg : funcType.args()) {
1029 if (!first && !buf.append(", ", strlen(", "))) {
1030 return nullptr;
1031 }
1032
1033 const char* argStr = ToCString(arg);
1034 if (!buf.append(argStr, strlen(argStr))) {
1035 return nullptr;
1036 }
1037
1038 first = false;
1039 }
1040
1041 if (!buf.append(") -> (", strlen(") -> ("))) {
1042 return nullptr;
1043 }
1044
1045 first = true;
1046 for (ValType result : funcType.results()) {
1047 if (!first && !buf.append(", ", strlen(", "))) {
1048 return nullptr;
1049 }
1050
1051 const char* resultStr = ToCString(result);
1052 if (!buf.append(resultStr, strlen(resultStr))) {
1053 return nullptr;
1054 }
1055
1056 first = false;
1057 }
1058
1059 if (!buf.append(')')) {
1060 return nullptr;
1061 }
1062
1063 return buf.finishString();
1064 }
1065
UTF8CharsToString(JSContext * cx,const char * chars)1066 static JSString* UTF8CharsToString(JSContext* cx, const char* chars) {
1067 return NewStringCopyUTF8Z<CanGC>(cx,
1068 JS::ConstUTF8CharsZ(chars, strlen(chars)));
1069 }
1070
1071 /* static */
imports(JSContext * cx,unsigned argc,Value * vp)1072 bool WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp) {
1073 CallArgs args = CallArgsFromVp(argc, vp);
1074
1075 const Module* module;
1076 if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.imports", &module)) {
1077 return false;
1078 }
1079
1080 KindNames names(cx);
1081 if (!InitKindNames(cx, &names)) {
1082 return false;
1083 }
1084
1085 RootedValueVector elems(cx);
1086 if (!elems.reserve(module->imports().length())) {
1087 return false;
1088 }
1089
1090 const FuncImportVector& funcImports =
1091 module->metadata(module->code().stableTier()).funcImports;
1092
1093 size_t numFuncImport = 0;
1094 for (const Import& import : module->imports()) {
1095 Rooted<IdValueVector> props(cx, IdValueVector(cx));
1096 if (!props.reserve(3)) {
1097 return false;
1098 }
1099
1100 JSString* moduleStr = UTF8CharsToString(cx, import.module.get());
1101 if (!moduleStr) {
1102 return false;
1103 }
1104 props.infallibleAppend(
1105 IdValuePair(NameToId(cx->names().module), StringValue(moduleStr)));
1106
1107 JSString* nameStr = UTF8CharsToString(cx, import.field.get());
1108 if (!nameStr) {
1109 return false;
1110 }
1111 props.infallibleAppend(
1112 IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
1113
1114 JSString* kindStr = KindToString(cx, names, import.kind);
1115 if (!kindStr) {
1116 return false;
1117 }
1118 props.infallibleAppend(
1119 IdValuePair(NameToId(names.kind), StringValue(kindStr)));
1120
1121 if (fuzzingSafe && import.kind == DefinitionKind::Function) {
1122 JSString* ftStr =
1123 FuncTypeToString(cx, funcImports[numFuncImport++].funcType());
1124 if (!ftStr) {
1125 return false;
1126 }
1127 if (!props.append(
1128 IdValuePair(NameToId(names.signature), StringValue(ftStr)))) {
1129 return false;
1130 }
1131 }
1132
1133 JSObject* obj = ObjectGroup::newPlainObject(cx, props.begin(),
1134 props.length(), GenericObject);
1135 if (!obj) {
1136 return false;
1137 }
1138
1139 elems.infallibleAppend(ObjectValue(*obj));
1140 }
1141
1142 JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
1143 if (!arr) {
1144 return false;
1145 }
1146
1147 args.rval().setObject(*arr);
1148 return true;
1149 }
1150
1151 /* static */
exports(JSContext * cx,unsigned argc,Value * vp)1152 bool WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp) {
1153 CallArgs args = CallArgsFromVp(argc, vp);
1154
1155 const Module* module;
1156 if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.exports", &module)) {
1157 return false;
1158 }
1159
1160 KindNames names(cx);
1161 if (!InitKindNames(cx, &names)) {
1162 return false;
1163 }
1164
1165 RootedValueVector elems(cx);
1166 if (!elems.reserve(module->exports().length())) {
1167 return false;
1168 }
1169
1170 const FuncExportVector& funcExports =
1171 module->metadata(module->code().stableTier()).funcExports;
1172
1173 size_t numFuncExport = 0;
1174 for (const Export& exp : module->exports()) {
1175 Rooted<IdValueVector> props(cx, IdValueVector(cx));
1176 if (!props.reserve(2)) {
1177 return false;
1178 }
1179
1180 JSString* nameStr = UTF8CharsToString(cx, exp.fieldName());
1181 if (!nameStr) {
1182 return false;
1183 }
1184 props.infallibleAppend(
1185 IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
1186
1187 JSString* kindStr = KindToString(cx, names, exp.kind());
1188 if (!kindStr) {
1189 return false;
1190 }
1191 props.infallibleAppend(
1192 IdValuePair(NameToId(names.kind), StringValue(kindStr)));
1193
1194 if (fuzzingSafe && exp.kind() == DefinitionKind::Function) {
1195 JSString* ftStr =
1196 FuncTypeToString(cx, funcExports[numFuncExport++].funcType());
1197 if (!ftStr) {
1198 return false;
1199 }
1200 if (!props.append(
1201 IdValuePair(NameToId(names.signature), StringValue(ftStr)))) {
1202 return false;
1203 }
1204 }
1205
1206 JSObject* obj = ObjectGroup::newPlainObject(cx, props.begin(),
1207 props.length(), GenericObject);
1208 if (!obj) {
1209 return false;
1210 }
1211
1212 elems.infallibleAppend(ObjectValue(*obj));
1213 }
1214
1215 JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
1216 if (!arr) {
1217 return false;
1218 }
1219
1220 args.rval().setObject(*arr);
1221 return true;
1222 }
1223
1224 /* static */
customSections(JSContext * cx,unsigned argc,Value * vp)1225 bool WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp) {
1226 CallArgs args = CallArgsFromVp(argc, vp);
1227
1228 const Module* module;
1229 if (!GetModuleArg(cx, args, 2, "WebAssembly.Module.customSections",
1230 &module)) {
1231 return false;
1232 }
1233
1234 Vector<char, 8> name(cx);
1235 {
1236 RootedString str(cx, ToString(cx, args.get(1)));
1237 if (!str) {
1238 return false;
1239 }
1240
1241 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
1242 if (!linear) {
1243 return false;
1244 }
1245
1246 if (!name.initLengthUninitialized(
1247 JS::GetDeflatedUTF8StringLength(linear))) {
1248 return false;
1249 }
1250
1251 mozilla::Unused << JS::DeflateStringToUTF8Buffer(
1252 linear, MakeSpan(name.begin(), name.length()));
1253 }
1254
1255 RootedValueVector elems(cx);
1256 RootedArrayBufferObject buf(cx);
1257 for (const CustomSection& cs : module->customSections()) {
1258 if (name.length() != cs.name.length()) {
1259 continue;
1260 }
1261 if (memcmp(name.begin(), cs.name.begin(), name.length())) {
1262 continue;
1263 }
1264
1265 buf = ArrayBufferObject::createZeroed(cx, cs.payload->length());
1266 if (!buf) {
1267 return false;
1268 }
1269
1270 memcpy(buf->dataPointer(), cs.payload->begin(), cs.payload->length());
1271 if (!elems.append(ObjectValue(*buf))) {
1272 return false;
1273 }
1274 }
1275
1276 JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
1277 if (!arr) {
1278 return false;
1279 }
1280
1281 args.rval().setObject(*arr);
1282 return true;
1283 }
1284
1285 /* static */
create(JSContext * cx,const Module & module,HandleObject proto)1286 WasmModuleObject* WasmModuleObject::create(JSContext* cx, const Module& module,
1287 HandleObject proto) {
1288 AutoSetNewObjectMetadata metadata(cx);
1289 auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
1290 if (!obj) {
1291 return nullptr;
1292 }
1293
1294 // This accounts for module allocation size (excluding code which is handled
1295 // separately - see below). This assumes that the size of associated data
1296 // doesn't change for the life of the WasmModuleObject. The size is counted
1297 // once per WasmModuleObject referencing a Module.
1298 InitReservedSlot(obj, MODULE_SLOT, const_cast<Module*>(&module),
1299 module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule);
1300 module.AddRef();
1301
1302 // Bug 1569888: We account for the first tier here; the second tier, if
1303 // different, also needs to be accounted for.
1304 cx->zone()->incJitMemory(module.codeLength(module.code().stableTier()));
1305 return obj;
1306 }
1307
GetBufferSource(JSContext * cx,JSObject * obj,unsigned errorNumber,MutableBytes * bytecode)1308 static bool GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber,
1309 MutableBytes* bytecode) {
1310 *bytecode = cx->new_<ShareableBytes>();
1311 if (!*bytecode) {
1312 return false;
1313 }
1314
1315 JSObject* unwrapped = CheckedUnwrapStatic(obj);
1316
1317 SharedMem<uint8_t*> dataPointer;
1318 size_t byteLength;
1319 if (!unwrapped || !IsBufferSource(unwrapped, &dataPointer, &byteLength)) {
1320 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
1321 return false;
1322 }
1323
1324 if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) {
1325 ReportOutOfMemory(cx);
1326 return false;
1327 }
1328
1329 return true;
1330 }
1331
InitCompileArgs(JSContext * cx,const char * introducer)1332 static SharedCompileArgs InitCompileArgs(JSContext* cx,
1333 const char* introducer) {
1334 ScriptedCaller scriptedCaller;
1335 if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) {
1336 return nullptr;
1337 }
1338
1339 return CompileArgs::build(cx, std::move(scriptedCaller));
1340 }
1341
ReportCompileWarnings(JSContext * cx,const UniqueCharsVector & warnings)1342 static bool ReportCompileWarnings(JSContext* cx,
1343 const UniqueCharsVector& warnings) {
1344 // Avoid spamming the console.
1345 size_t numWarnings = std::min<size_t>(warnings.length(), 3);
1346
1347 for (size_t i = 0; i < numWarnings; i++) {
1348 if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, warnings[i].get())) {
1349 return false;
1350 }
1351 }
1352
1353 if (warnings.length() > numWarnings) {
1354 if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING,
1355 "other warnings suppressed")) {
1356 return false;
1357 }
1358 }
1359
1360 return true;
1361 }
1362
1363 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)1364 bool WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp) {
1365 CallArgs callArgs = CallArgsFromVp(argc, vp);
1366
1367 Log(cx, "sync new Module() started");
1368
1369 if (!ThrowIfNotConstructing(cx, callArgs, "Module")) {
1370 return false;
1371 }
1372
1373 if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1)) {
1374 return false;
1375 }
1376
1377 if (!callArgs[0].isObject()) {
1378 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1379 JSMSG_WASM_BAD_BUF_ARG);
1380 return false;
1381 }
1382
1383 MutableBytes bytecode;
1384 if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
1385 &bytecode)) {
1386 return false;
1387 }
1388
1389 SharedCompileArgs compileArgs = InitCompileArgs(cx, "WebAssembly.Module");
1390 if (!compileArgs) {
1391 return false;
1392 }
1393
1394 UniqueChars error;
1395 UniqueCharsVector warnings;
1396 SharedModule module =
1397 CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
1398 if (!module) {
1399 if (error) {
1400 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1401 JSMSG_WASM_COMPILE_ERROR, error.get());
1402 return false;
1403 }
1404 ReportOutOfMemory(cx);
1405 return false;
1406 }
1407
1408 if (!ReportCompileWarnings(cx, warnings)) {
1409 return false;
1410 }
1411
1412 RootedObject proto(cx);
1413 if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, JSProto_WasmModule,
1414 &proto)) {
1415 return false;
1416 }
1417 if (!proto) {
1418 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmModule);
1419 }
1420
1421 RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
1422 if (!moduleObj) {
1423 return false;
1424 }
1425
1426 Log(cx, "sync new Module() succeded");
1427
1428 callArgs.rval().setObject(*moduleObj);
1429 return true;
1430 }
1431
module() const1432 const Module& WasmModuleObject::module() const {
1433 MOZ_ASSERT(is<WasmModuleObject>());
1434 return *(const Module*)getReservedSlot(MODULE_SLOT).toPrivate();
1435 }
1436
1437 // ============================================================================
1438 // WebAssembly.Instance class and methods
1439
1440 const JSClassOps WasmInstanceObject::classOps_ = {
1441 nullptr, // addProperty
1442 nullptr, // delProperty
1443 nullptr, // enumerate
1444 nullptr, // newEnumerate
1445 nullptr, // resolve
1446 nullptr, // mayResolve
1447 WasmInstanceObject::finalize, // finalize
1448 nullptr, // call
1449 nullptr, // hasInstance
1450 nullptr, // construct
1451 WasmInstanceObject::trace, // trace
1452 };
1453
1454 const JSClass WasmInstanceObject::class_ = {
1455 "WebAssembly.Instance",
1456 JSCLASS_DELAY_METADATA_BUILDER |
1457 JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
1458 JSCLASS_FOREGROUND_FINALIZE,
1459 &WasmInstanceObject::classOps_,
1460 &WasmInstanceObject::classSpec_,
1461 };
1462
1463 const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_;
1464
1465 static constexpr char WasmInstanceName[] = "Instance";
1466
1467 const ClassSpec WasmInstanceObject::classSpec_ = {
1468 CreateWasmConstructor<WasmInstanceObject, WasmInstanceName>,
1469 GenericCreatePrototype<WasmInstanceObject>,
1470 WasmInstanceObject::static_methods,
1471 nullptr,
1472 WasmInstanceObject::methods,
1473 WasmInstanceObject::properties,
1474 nullptr,
1475 ClassSpec::DontDefineConstructor};
1476
IsInstance(HandleValue v)1477 static bool IsInstance(HandleValue v) {
1478 return v.isObject() && v.toObject().is<WasmInstanceObject>();
1479 }
1480
1481 /* static */
exportsGetterImpl(JSContext * cx,const CallArgs & args)1482 bool WasmInstanceObject::exportsGetterImpl(JSContext* cx,
1483 const CallArgs& args) {
1484 args.rval().setObject(
1485 args.thisv().toObject().as<WasmInstanceObject>().exportsObj());
1486 return true;
1487 }
1488
1489 /* static */
exportsGetter(JSContext * cx,unsigned argc,Value * vp)1490 bool WasmInstanceObject::exportsGetter(JSContext* cx, unsigned argc,
1491 Value* vp) {
1492 CallArgs args = CallArgsFromVp(argc, vp);
1493 return CallNonGenericMethod<IsInstance, exportsGetterImpl>(cx, args);
1494 }
1495
1496 const JSPropertySpec WasmInstanceObject::properties[] = {
1497 JS_PSG("exports", WasmInstanceObject::exportsGetter, JSPROP_ENUMERATE),
1498 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Instance", JSPROP_READONLY),
1499 JS_PS_END};
1500
1501 const JSFunctionSpec WasmInstanceObject::methods[] = {JS_FS_END};
1502
1503 const JSFunctionSpec WasmInstanceObject::static_methods[] = {JS_FS_END};
1504
isNewborn() const1505 bool WasmInstanceObject::isNewborn() const {
1506 MOZ_ASSERT(is<WasmInstanceObject>());
1507 return getReservedSlot(INSTANCE_SLOT).isUndefined();
1508 }
1509
1510 /* static */
finalize(JSFreeOp * fop,JSObject * obj)1511 void WasmInstanceObject::finalize(JSFreeOp* fop, JSObject* obj) {
1512 WasmInstanceObject& instance = obj->as<WasmInstanceObject>();
1513 fop->delete_(obj, &instance.exports(), MemoryUse::WasmInstanceExports);
1514 fop->delete_(obj, &instance.scopes(), MemoryUse::WasmInstanceScopes);
1515 fop->delete_(obj, &instance.indirectGlobals(),
1516 MemoryUse::WasmInstanceGlobals);
1517 if (!instance.isNewborn()) {
1518 if (instance.instance().debugEnabled()) {
1519 instance.instance().debug().finalize(fop);
1520 }
1521 fop->delete_(obj, &instance.instance(), MemoryUse::WasmInstanceInstance);
1522 }
1523 }
1524
1525 /* static */
trace(JSTracer * trc,JSObject * obj)1526 void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) {
1527 WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
1528 instanceObj.exports().trace(trc);
1529 instanceObj.indirectGlobals().trace(trc);
1530 if (!instanceObj.isNewborn()) {
1531 instanceObj.instance().tracePrivate(trc);
1532 }
1533 }
1534
1535 /* static */
create(JSContext * cx,SharedCode code,const DataSegmentVector & dataSegments,const ElemSegmentVector & elemSegments,UniqueTlsData tlsData,HandleWasmMemoryObject memory,SharedTableVector && tables,StructTypeDescrVector && structTypeDescrs,const JSFunctionVector & funcImports,const GlobalDescVector & globals,const ValVector & globalImportValues,const WasmGlobalObjectVector & globalObjs,HandleObject proto,UniqueDebugState maybeDebug)1536 WasmInstanceObject* WasmInstanceObject::create(
1537 JSContext* cx, SharedCode code, const DataSegmentVector& dataSegments,
1538 const ElemSegmentVector& elemSegments, UniqueTlsData tlsData,
1539 HandleWasmMemoryObject memory, SharedTableVector&& tables,
1540 StructTypeDescrVector&& structTypeDescrs,
1541 const JSFunctionVector& funcImports, const GlobalDescVector& globals,
1542 const ValVector& globalImportValues,
1543 const WasmGlobalObjectVector& globalObjs, HandleObject proto,
1544 UniqueDebugState maybeDebug) {
1545 UniquePtr<ExportMap> exports = js::MakeUnique<ExportMap>(cx->zone());
1546 if (!exports) {
1547 ReportOutOfMemory(cx);
1548 return nullptr;
1549 }
1550
1551 UniquePtr<ScopeMap> scopes = js::MakeUnique<ScopeMap>(cx->zone(), cx->zone());
1552 if (!scopes) {
1553 ReportOutOfMemory(cx);
1554 return nullptr;
1555 }
1556
1557 uint32_t indirectGlobals = 0;
1558
1559 for (uint32_t i = 0; i < globalObjs.length(); i++) {
1560 if (globalObjs[i] && globals[i].isIndirect()) {
1561 indirectGlobals++;
1562 }
1563 }
1564
1565 Rooted<UniquePtr<GlobalObjectVector>> indirectGlobalObjs(
1566 cx, js::MakeUnique<GlobalObjectVector>(cx->zone()));
1567 if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals)) {
1568 ReportOutOfMemory(cx);
1569 return nullptr;
1570 }
1571
1572 {
1573 uint32_t next = 0;
1574 for (uint32_t i = 0; i < globalObjs.length(); i++) {
1575 if (globalObjs[i] && globals[i].isIndirect()) {
1576 (*indirectGlobalObjs)[next++] = globalObjs[i];
1577 }
1578 }
1579 }
1580
1581 Instance* instance = nullptr;
1582 RootedWasmInstanceObject obj(cx);
1583
1584 {
1585 // We must delay creating metadata for this object until after all its
1586 // slots have been initialized. We must also create the metadata before
1587 // calling Instance::init as that may allocate new objects.
1588 AutoSetNewObjectMetadata metadata(cx);
1589 obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto);
1590 if (!obj) {
1591 return nullptr;
1592 }
1593
1594 MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
1595
1596 // Finalization assumes these slots are always initialized:
1597 InitReservedSlot(obj, EXPORTS_SLOT, exports.release(),
1598 MemoryUse::WasmInstanceExports);
1599
1600 InitReservedSlot(obj, SCOPES_SLOT, scopes.release(),
1601 MemoryUse::WasmInstanceScopes);
1602
1603 InitReservedSlot(obj, GLOBALS_SLOT, indirectGlobalObjs.release(),
1604 MemoryUse::WasmInstanceGlobals);
1605
1606 obj->initReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());
1607
1608 // The INSTANCE_SLOT may not be initialized if Instance allocation fails,
1609 // leading to an observable "newborn" state in tracing/finalization.
1610 MOZ_ASSERT(obj->isNewborn());
1611
1612 // Root the Instance via WasmInstanceObject before any possible GC.
1613 instance = cx->new_<Instance>(
1614 cx, obj, code, std::move(tlsData), memory, std::move(tables),
1615 std::move(structTypeDescrs), std::move(maybeDebug));
1616 if (!instance) {
1617 return nullptr;
1618 }
1619
1620 InitReservedSlot(obj, INSTANCE_SLOT, instance,
1621 MemoryUse::WasmInstanceInstance);
1622 MOZ_ASSERT(!obj->isNewborn());
1623 }
1624
1625 if (!instance->init(cx, funcImports, globalImportValues, globalObjs,
1626 dataSegments, elemSegments)) {
1627 return nullptr;
1628 }
1629
1630 return obj;
1631 }
1632
initExportsObj(JSObject & exportsObj)1633 void WasmInstanceObject::initExportsObj(JSObject& exportsObj) {
1634 MOZ_ASSERT(getReservedSlot(EXPORTS_OBJ_SLOT).isUndefined());
1635 setReservedSlot(EXPORTS_OBJ_SLOT, ObjectValue(exportsObj));
1636 }
1637
GetImportArg(JSContext * cx,CallArgs callArgs,MutableHandleObject importObj)1638 static bool GetImportArg(JSContext* cx, CallArgs callArgs,
1639 MutableHandleObject importObj) {
1640 if (!callArgs.get(1).isUndefined()) {
1641 if (!callArgs[1].isObject()) {
1642 return ThrowBadImportArg(cx);
1643 }
1644 importObj.set(&callArgs[1].toObject());
1645 }
1646 return true;
1647 }
1648
1649 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)1650 bool WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp) {
1651 CallArgs args = CallArgsFromVp(argc, vp);
1652
1653 Log(cx, "sync new Instance() started");
1654
1655 if (!ThrowIfNotConstructing(cx, args, "Instance")) {
1656 return false;
1657 }
1658
1659 if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1)) {
1660 return false;
1661 }
1662
1663 const Module* module;
1664 if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
1665 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1666 JSMSG_WASM_BAD_MOD_ARG);
1667 return false;
1668 }
1669
1670 RootedObject importObj(cx);
1671 if (!GetImportArg(cx, args, &importObj)) {
1672 return false;
1673 }
1674
1675 RootedObject instanceProto(cx);
1676 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmInstance,
1677 &instanceProto)) {
1678 return false;
1679 }
1680 if (!instanceProto) {
1681 instanceProto =
1682 GlobalObject::getOrCreatePrototype(cx, JSProto_WasmInstance);
1683 }
1684
1685 Rooted<ImportValues> imports(cx);
1686 if (!GetImports(cx, *module, importObj, imports.address())) {
1687 return false;
1688 }
1689
1690 RootedWasmInstanceObject instanceObj(cx);
1691 if (!module->instantiate(cx, imports.get(), instanceProto, &instanceObj)) {
1692 return false;
1693 }
1694
1695 Log(cx, "sync new Instance() succeeded");
1696
1697 args.rval().setObject(*instanceObj);
1698 return true;
1699 }
1700
instance() const1701 Instance& WasmInstanceObject::instance() const {
1702 MOZ_ASSERT(!isNewborn());
1703 return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate();
1704 }
1705
exportsObj() const1706 JSObject& WasmInstanceObject::exportsObj() const {
1707 return getReservedSlot(EXPORTS_OBJ_SLOT).toObject();
1708 }
1709
exports() const1710 WasmInstanceObject::ExportMap& WasmInstanceObject::exports() const {
1711 return *(ExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate();
1712 }
1713
scopes() const1714 WasmInstanceObject::ScopeMap& WasmInstanceObject::scopes() const {
1715 return *(ScopeMap*)getReservedSlot(SCOPES_SLOT).toPrivate();
1716 }
1717
indirectGlobals() const1718 WasmInstanceObject::GlobalObjectVector& WasmInstanceObject::indirectGlobals()
1719 const {
1720 return *(GlobalObjectVector*)getReservedSlot(GLOBALS_SLOT).toPrivate();
1721 }
1722
WasmCall(JSContext * cx,unsigned argc,Value * vp)1723 static bool WasmCall(JSContext* cx, unsigned argc, Value* vp) {
1724 CallArgs args = CallArgsFromVp(argc, vp);
1725 RootedFunction callee(cx, &args.callee().as<JSFunction>());
1726
1727 Instance& instance = ExportedFunctionToInstance(callee);
1728 uint32_t funcIndex = ExportedFunctionToFuncIndex(callee);
1729 return instance.callExport(cx, funcIndex, args);
1730 }
1731
1732 /* static */
getExportedFunction(JSContext * cx,HandleWasmInstanceObject instanceObj,uint32_t funcIndex,MutableHandleFunction fun)1733 bool WasmInstanceObject::getExportedFunction(
1734 JSContext* cx, HandleWasmInstanceObject instanceObj, uint32_t funcIndex,
1735 MutableHandleFunction fun) {
1736 if (ExportMap::Ptr p = instanceObj->exports().lookup(funcIndex)) {
1737 fun.set(p->value());
1738 return true;
1739 }
1740
1741 const Instance& instance = instanceObj->instance();
1742 const FuncExport& funcExport =
1743 instance.metadata(instance.code().bestTier()).lookupFuncExport(funcIndex);
1744 unsigned numArgs = funcExport.funcType().args().length();
1745
1746 if (instance.isAsmJS()) {
1747 // asm.js needs to act like a normal JS function which means having the
1748 // name from the original source and being callable as a constructor.
1749 RootedAtom name(cx, instance.getFuncDisplayAtom(cx, funcIndex));
1750 if (!name) {
1751 return false;
1752 }
1753 fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name,
1754 gc::AllocKind::FUNCTION_EXTENDED,
1755 SingletonObject, FunctionFlags::ASMJS_CTOR));
1756 if (!fun) {
1757 return false;
1758 }
1759
1760 // asm.js does not support jit entries.
1761 fun->setWasmFuncIndex(funcIndex);
1762 } else {
1763 RootedAtom name(cx, NumberToAtom(cx, funcIndex));
1764 if (!name) {
1765 return false;
1766 }
1767
1768 fun.set(NewNativeFunction(cx, WasmCall, numArgs, name,
1769 gc::AllocKind::FUNCTION_EXTENDED, SingletonObject,
1770 FunctionFlags::WASM));
1771 if (!fun) {
1772 return false;
1773 }
1774
1775 // Some applications eagerly access all table elements which currently
1776 // triggers worst-case behavior for lazy stubs, since each will allocate a
1777 // separate 4kb code page. Most eagerly-accessed functions are not called,
1778 // so instead wait until Instance::callExport() to create the entry stubs.
1779 if (funcExport.hasEagerStubs() && funcExport.canHaveJitEntry()) {
1780 fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex));
1781 } else {
1782 fun->setWasmFuncIndex(funcIndex);
1783 }
1784 }
1785
1786 fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT,
1787 ObjectValue(*instanceObj));
1788
1789 void* tlsData = instanceObj->instance().tlsData();
1790 fun->setExtendedSlot(FunctionExtended::WASM_TLSDATA_SLOT,
1791 PrivateValue(tlsData));
1792
1793 if (!instanceObj->exports().putNew(funcIndex, fun)) {
1794 ReportOutOfMemory(cx);
1795 return false;
1796 }
1797
1798 return true;
1799 }
1800
getExportedFunctionCodeRange(JSFunction * fun,Tier tier)1801 const CodeRange& WasmInstanceObject::getExportedFunctionCodeRange(
1802 JSFunction* fun, Tier tier) {
1803 uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
1804 MOZ_ASSERT(exports().lookup(funcIndex)->value() == fun);
1805 const MetadataTier& metadata = instance().metadata(tier);
1806 return metadata.codeRange(metadata.lookupFuncExport(funcIndex));
1807 }
1808
1809 /* static */
getScope(JSContext * cx,HandleWasmInstanceObject instanceObj)1810 WasmInstanceScope* WasmInstanceObject::getScope(
1811 JSContext* cx, HandleWasmInstanceObject instanceObj) {
1812 if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined()) {
1813 return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT)
1814 .toGCThing();
1815 }
1816
1817 Rooted<WasmInstanceScope*> instanceScope(
1818 cx, WasmInstanceScope::create(cx, instanceObj));
1819 if (!instanceScope) {
1820 return nullptr;
1821 }
1822
1823 instanceObj->setReservedSlot(INSTANCE_SCOPE_SLOT,
1824 PrivateGCThingValue(instanceScope));
1825
1826 return instanceScope;
1827 }
1828
1829 /* static */
getFunctionScope(JSContext * cx,HandleWasmInstanceObject instanceObj,uint32_t funcIndex)1830 WasmFunctionScope* WasmInstanceObject::getFunctionScope(
1831 JSContext* cx, HandleWasmInstanceObject instanceObj, uint32_t funcIndex) {
1832 if (ScopeMap::Ptr p = instanceObj->scopes().lookup(funcIndex)) {
1833 return p->value();
1834 }
1835
1836 Rooted<WasmInstanceScope*> instanceScope(
1837 cx, WasmInstanceObject::getScope(cx, instanceObj));
1838 if (!instanceScope) {
1839 return nullptr;
1840 }
1841
1842 Rooted<WasmFunctionScope*> funcScope(
1843 cx, WasmFunctionScope::create(cx, instanceScope, funcIndex));
1844 if (!funcScope) {
1845 return nullptr;
1846 }
1847
1848 if (!instanceObj->scopes().putNew(funcIndex, funcScope)) {
1849 ReportOutOfMemory(cx);
1850 return nullptr;
1851 }
1852
1853 return funcScope;
1854 }
1855
IsWasmExportedFunction(JSFunction * fun)1856 bool wasm::IsWasmExportedFunction(JSFunction* fun) {
1857 return fun->kind() == FunctionFlags::Wasm;
1858 }
1859
CheckFuncRefValue(JSContext * cx,HandleValue v,MutableHandleFunction fun)1860 bool wasm::CheckFuncRefValue(JSContext* cx, HandleValue v,
1861 MutableHandleFunction fun) {
1862 if (v.isNull()) {
1863 MOZ_ASSERT(!fun);
1864 return true;
1865 }
1866
1867 if (v.isObject()) {
1868 JSObject& obj = v.toObject();
1869 if (obj.is<JSFunction>()) {
1870 JSFunction* f = &obj.as<JSFunction>();
1871 if (IsWasmExportedFunction(f)) {
1872 fun.set(f);
1873 return true;
1874 }
1875 }
1876 }
1877
1878 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1879 JSMSG_WASM_BAD_FUNCREF_VALUE);
1880 return false;
1881 }
1882
ExportedFunctionToInstance(JSFunction * fun)1883 Instance& wasm::ExportedFunctionToInstance(JSFunction* fun) {
1884 return ExportedFunctionToInstanceObject(fun)->instance();
1885 }
1886
ExportedFunctionToInstanceObject(JSFunction * fun)1887 WasmInstanceObject* wasm::ExportedFunctionToInstanceObject(JSFunction* fun) {
1888 MOZ_ASSERT(fun->kind() == FunctionFlags::Wasm ||
1889 fun->kind() == FunctionFlags::AsmJS);
1890 const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT);
1891 return &v.toObject().as<WasmInstanceObject>();
1892 }
1893
ExportedFunctionToFuncIndex(JSFunction * fun)1894 uint32_t wasm::ExportedFunctionToFuncIndex(JSFunction* fun) {
1895 Instance& instance = ExportedFunctionToInstanceObject(fun)->instance();
1896 return instance.code().getFuncIndex(fun);
1897 }
1898
1899 // ============================================================================
1900 // WebAssembly.Memory class and methods
1901
1902 const JSClassOps WasmMemoryObject::classOps_ = {
1903 nullptr, // addProperty
1904 nullptr, // delProperty
1905 nullptr, // enumerate
1906 nullptr, // newEnumerate
1907 nullptr, // resolve
1908 nullptr, // mayResolve
1909 WasmMemoryObject::finalize, // finalize
1910 nullptr, // call
1911 nullptr, // hasInstance
1912 nullptr, // construct
1913 nullptr, // trace
1914 };
1915
1916 const JSClass WasmMemoryObject::class_ = {
1917 "WebAssembly.Memory",
1918 JSCLASS_DELAY_METADATA_BUILDER |
1919 JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
1920 JSCLASS_FOREGROUND_FINALIZE,
1921 &WasmMemoryObject::classOps_, &WasmMemoryObject::classSpec_};
1922
1923 const JSClass& WasmMemoryObject::protoClass_ = PlainObject::class_;
1924
1925 static constexpr char WasmMemoryName[] = "Memory";
1926
1927 const ClassSpec WasmMemoryObject::classSpec_ = {
1928 CreateWasmConstructor<WasmMemoryObject, WasmMemoryName>,
1929 GenericCreatePrototype<WasmMemoryObject>,
1930 WasmMemoryObject::static_methods,
1931 nullptr,
1932 WasmMemoryObject::methods,
1933 WasmMemoryObject::properties,
1934 nullptr,
1935 ClassSpec::DontDefineConstructor};
1936
1937 /* static */
finalize(JSFreeOp * fop,JSObject * obj)1938 void WasmMemoryObject::finalize(JSFreeOp* fop, JSObject* obj) {
1939 WasmMemoryObject& memory = obj->as<WasmMemoryObject>();
1940 if (memory.hasObservers()) {
1941 fop->delete_(obj, &memory.observers(), MemoryUse::WasmMemoryObservers);
1942 }
1943 }
1944
1945 /* static */
create(JSContext * cx,HandleArrayBufferObjectMaybeShared buffer,HandleObject proto)1946 WasmMemoryObject* WasmMemoryObject::create(
1947 JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
1948 HandleObject proto) {
1949 AutoSetNewObjectMetadata metadata(cx);
1950 auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto);
1951 if (!obj) {
1952 return nullptr;
1953 }
1954
1955 obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer));
1956 MOZ_ASSERT(!obj->hasObservers());
1957 return obj;
1958 }
1959
1960 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)1961 bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
1962 CallArgs args = CallArgsFromVp(argc, vp);
1963
1964 if (!ThrowIfNotConstructing(cx, args, "Memory")) {
1965 return false;
1966 }
1967
1968 if (!args.requireAtLeast(cx, "WebAssembly.Memory", 1)) {
1969 return false;
1970 }
1971
1972 if (!args.get(0).isObject()) {
1973 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
1974 JSMSG_WASM_BAD_DESC_ARG, "memory");
1975 return false;
1976 }
1977
1978 RootedObject obj(cx, &args[0].toObject());
1979 Limits limits;
1980 if (!GetLimits(cx, obj, MaxMemoryInitialPages, MaxMemoryMaximumPages,
1981 "Memory", &limits, Shareable::True)) {
1982 return false;
1983 }
1984
1985 ConvertMemoryPagesToBytes(&limits);
1986
1987 RootedArrayBufferObjectMaybeShared buffer(cx);
1988 if (!CreateWasmBuffer(cx, limits, &buffer)) {
1989 return false;
1990 }
1991
1992 RootedObject proto(cx);
1993 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmMemory,
1994 &proto)) {
1995 return false;
1996 }
1997 if (!proto) {
1998 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmMemory);
1999 }
2000
2001 RootedWasmMemoryObject memoryObj(cx,
2002 WasmMemoryObject::create(cx, buffer, proto));
2003 if (!memoryObj) {
2004 return false;
2005 }
2006
2007 args.rval().setObject(*memoryObj);
2008 return true;
2009 }
2010
IsMemory(HandleValue v)2011 static bool IsMemory(HandleValue v) {
2012 return v.isObject() && v.toObject().is<WasmMemoryObject>();
2013 }
2014
2015 /* static */
bufferGetterImpl(JSContext * cx,const CallArgs & args)2016 bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) {
2017 RootedWasmMemoryObject memoryObj(
2018 cx, &args.thisv().toObject().as<WasmMemoryObject>());
2019 RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer());
2020
2021 if (memoryObj->isShared()) {
2022 uint32_t memoryLength = memoryObj->volatileMemoryLength();
2023 MOZ_ASSERT(memoryLength >= buffer->byteLength());
2024
2025 if (memoryLength > buffer->byteLength()) {
2026 RootedSharedArrayBufferObject newBuffer(
2027 cx, SharedArrayBufferObject::New(
2028 cx, memoryObj->sharedArrayRawBuffer(), memoryLength));
2029 if (!newBuffer) {
2030 return false;
2031 }
2032 // OK to addReference after we try to allocate because the memoryObj
2033 // keeps the rawBuffer alive.
2034 if (!memoryObj->sharedArrayRawBuffer()->addReference()) {
2035 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2036 JSMSG_SC_SAB_REFCNT_OFLO);
2037 return false;
2038 }
2039 buffer = newBuffer;
2040 memoryObj->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuffer));
2041 }
2042 }
2043
2044 args.rval().setObject(*buffer);
2045 return true;
2046 }
2047
2048 /* static */
bufferGetter(JSContext * cx,unsigned argc,Value * vp)2049 bool WasmMemoryObject::bufferGetter(JSContext* cx, unsigned argc, Value* vp) {
2050 CallArgs args = CallArgsFromVp(argc, vp);
2051 return CallNonGenericMethod<IsMemory, bufferGetterImpl>(cx, args);
2052 }
2053
2054 const JSPropertySpec WasmMemoryObject::properties[] = {
2055 JS_PSG("buffer", WasmMemoryObject::bufferGetter, JSPROP_ENUMERATE),
2056 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Memory", JSPROP_READONLY),
2057 JS_PS_END};
2058
2059 /* static */
growImpl(JSContext * cx,const CallArgs & args)2060 bool WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args) {
2061 RootedWasmMemoryObject memory(
2062 cx, &args.thisv().toObject().as<WasmMemoryObject>());
2063
2064 if (!args.requireAtLeast(cx, "WebAssembly.Memory.grow", 1)) {
2065 return false;
2066 }
2067
2068 uint32_t delta;
2069 if (!EnforceRangeU32(cx, args.get(0), "Memory", "grow delta", &delta)) {
2070 return false;
2071 }
2072
2073 uint32_t ret = grow(memory, delta, cx);
2074
2075 if (ret == uint32_t(-1)) {
2076 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
2077 "memory");
2078 return false;
2079 }
2080
2081 args.rval().setInt32(ret);
2082 return true;
2083 }
2084
2085 /* static */
grow(JSContext * cx,unsigned argc,Value * vp)2086 bool WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp) {
2087 CallArgs args = CallArgsFromVp(argc, vp);
2088 return CallNonGenericMethod<IsMemory, growImpl>(cx, args);
2089 }
2090
2091 const JSFunctionSpec WasmMemoryObject::methods[] = {
2092 JS_FN("grow", WasmMemoryObject::grow, 1, JSPROP_ENUMERATE), JS_FS_END};
2093
2094 const JSFunctionSpec WasmMemoryObject::static_methods[] = {JS_FS_END};
2095
buffer() const2096 ArrayBufferObjectMaybeShared& WasmMemoryObject::buffer() const {
2097 return getReservedSlot(BUFFER_SLOT)
2098 .toObject()
2099 .as<ArrayBufferObjectMaybeShared>();
2100 }
2101
sharedArrayRawBuffer() const2102 SharedArrayRawBuffer* WasmMemoryObject::sharedArrayRawBuffer() const {
2103 MOZ_ASSERT(isShared());
2104 return buffer().as<SharedArrayBufferObject>().rawBufferObject();
2105 }
2106
volatileMemoryLength() const2107 uint32_t WasmMemoryObject::volatileMemoryLength() const {
2108 if (isShared()) {
2109 return sharedArrayRawBuffer()->volatileByteLength();
2110 }
2111 return buffer().byteLength();
2112 }
2113
isShared() const2114 bool WasmMemoryObject::isShared() const {
2115 return buffer().is<SharedArrayBufferObject>();
2116 }
2117
hasObservers() const2118 bool WasmMemoryObject::hasObservers() const {
2119 return !getReservedSlot(OBSERVERS_SLOT).isUndefined();
2120 }
2121
observers() const2122 WasmMemoryObject::InstanceSet& WasmMemoryObject::observers() const {
2123 MOZ_ASSERT(hasObservers());
2124 return *reinterpret_cast<InstanceSet*>(
2125 getReservedSlot(OBSERVERS_SLOT).toPrivate());
2126 }
2127
getOrCreateObservers(JSContext * cx)2128 WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers(
2129 JSContext* cx) {
2130 if (!hasObservers()) {
2131 auto observers = MakeUnique<InstanceSet>(cx->zone(), cx->zone());
2132 if (!observers) {
2133 ReportOutOfMemory(cx);
2134 return nullptr;
2135 }
2136
2137 InitReservedSlot(this, OBSERVERS_SLOT, observers.release(),
2138 MemoryUse::WasmMemoryObservers);
2139 }
2140
2141 return &observers();
2142 }
2143
isHuge() const2144 bool WasmMemoryObject::isHuge() const {
2145 #ifdef WASM_SUPPORTS_HUGE_MEMORY
2146 static_assert(ArrayBufferObject::MaxBufferByteLength < HugeMappedSize,
2147 "Non-huge buffer may be confused as huge");
2148 return buffer().wasmMappedSize() >= HugeMappedSize;
2149 #else
2150 return false;
2151 #endif
2152 }
2153
movingGrowable() const2154 bool WasmMemoryObject::movingGrowable() const {
2155 return !isHuge() && !buffer().wasmMaxSize();
2156 }
2157
boundsCheckLimit() const2158 uint32_t WasmMemoryObject::boundsCheckLimit() const {
2159 if (!buffer().isWasm() || isHuge()) {
2160 return buffer().byteLength();
2161 }
2162 size_t mappedSize = buffer().wasmMappedSize();
2163 MOZ_ASSERT(mappedSize <= UINT32_MAX);
2164 MOZ_ASSERT(mappedSize >= wasm::GuardSize);
2165 MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize));
2166 return mappedSize - wasm::GuardSize;
2167 }
2168
addMovingGrowObserver(JSContext * cx,WasmInstanceObject * instance)2169 bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx,
2170 WasmInstanceObject* instance) {
2171 MOZ_ASSERT(movingGrowable());
2172
2173 InstanceSet* observers = getOrCreateObservers(cx);
2174 if (!observers) {
2175 return false;
2176 }
2177
2178 if (!observers->putNew(instance)) {
2179 ReportOutOfMemory(cx);
2180 return false;
2181 }
2182
2183 return true;
2184 }
2185
2186 /* static */
growShared(HandleWasmMemoryObject memory,uint32_t delta)2187 uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
2188 uint32_t delta) {
2189 SharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer();
2190 SharedArrayRawBuffer::Lock lock(rawBuf);
2191
2192 MOZ_ASSERT(rawBuf->volatileByteLength() % PageSize == 0);
2193 uint32_t oldNumPages = rawBuf->volatileByteLength() / PageSize;
2194
2195 CheckedInt<uint32_t> newSize = oldNumPages;
2196 newSize += delta;
2197 newSize *= PageSize;
2198 if (!newSize.isValid()) {
2199 return -1;
2200 }
2201
2202 if (newSize.value() > rawBuf->maxSize()) {
2203 return -1;
2204 }
2205
2206 if (!rawBuf->wasmGrowToSizeInPlace(lock, newSize.value())) {
2207 return -1;
2208 }
2209
2210 // New buffer objects will be created lazily in all agents (including in
2211 // this agent) by bufferGetterImpl, above, so no more work to do here.
2212
2213 return oldNumPages;
2214 }
2215
2216 /* static */
grow(HandleWasmMemoryObject memory,uint32_t delta,JSContext * cx)2217 uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
2218 JSContext* cx) {
2219 if (memory->isShared()) {
2220 return growShared(memory, delta);
2221 }
2222
2223 RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>());
2224
2225 MOZ_ASSERT(oldBuf->byteLength() % PageSize == 0);
2226 uint32_t oldNumPages = oldBuf->byteLength() / PageSize;
2227
2228 CheckedInt<uint32_t> newSize = oldNumPages;
2229 newSize += delta;
2230 newSize *= PageSize;
2231 if (!newSize.isValid()) {
2232 return -1;
2233 }
2234
2235 RootedArrayBufferObject newBuf(cx);
2236
2237 if (memory->movingGrowable()) {
2238 MOZ_ASSERT(!memory->isHuge());
2239 if (!ArrayBufferObject::wasmMovingGrowToSize(newSize.value(), oldBuf,
2240 &newBuf, cx)) {
2241 return -1;
2242 }
2243 } else {
2244 if (Maybe<uint32_t> maxSize = oldBuf->wasmMaxSize()) {
2245 if (newSize.value() > maxSize.value()) {
2246 return -1;
2247 }
2248 }
2249
2250 if (!ArrayBufferObject::wasmGrowToSizeInPlace(newSize.value(), oldBuf,
2251 &newBuf, cx)) {
2252 return -1;
2253 }
2254 }
2255
2256 memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));
2257
2258 // Only notify moving-grow-observers after the BUFFER_SLOT has been updated
2259 // since observers will call buffer().
2260 if (memory->hasObservers()) {
2261 for (InstanceSet::Range r = memory->observers().all(); !r.empty();
2262 r.popFront()) {
2263 r.front()->instance().onMovingGrowMemory();
2264 }
2265 }
2266
2267 return oldNumPages;
2268 }
2269
IsSharedWasmMemoryObject(JSObject * obj)2270 bool js::wasm::IsSharedWasmMemoryObject(JSObject* obj) {
2271 WasmMemoryObject* mobj = obj->maybeUnwrapIf<WasmMemoryObject>();
2272 return mobj && mobj->isShared();
2273 }
2274
2275 // ============================================================================
2276 // WebAssembly.Table class and methods
2277
2278 const JSClassOps WasmTableObject::classOps_ = {
2279 nullptr, // addProperty
2280 nullptr, // delProperty
2281 nullptr, // enumerate
2282 nullptr, // newEnumerate
2283 nullptr, // resolve
2284 nullptr, // mayResolve
2285 WasmTableObject::finalize, // finalize
2286 nullptr, // call
2287 nullptr, // hasInstance
2288 nullptr, // construct
2289 WasmTableObject::trace, // trace
2290 };
2291
2292 const JSClass WasmTableObject::class_ = {
2293 "WebAssembly.Table",
2294 JSCLASS_DELAY_METADATA_BUILDER |
2295 JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS) |
2296 JSCLASS_FOREGROUND_FINALIZE,
2297 &WasmTableObject::classOps_, &WasmTableObject::classSpec_};
2298
2299 const JSClass& WasmTableObject::protoClass_ = PlainObject::class_;
2300
2301 static constexpr char WasmTableName[] = "Table";
2302
2303 const ClassSpec WasmTableObject::classSpec_ = {
2304 CreateWasmConstructor<WasmTableObject, WasmTableName>,
2305 GenericCreatePrototype<WasmTableObject>,
2306 WasmTableObject::static_methods,
2307 nullptr,
2308 WasmTableObject::methods,
2309 WasmTableObject::properties,
2310 nullptr,
2311 ClassSpec::DontDefineConstructor};
2312
isNewborn() const2313 bool WasmTableObject::isNewborn() const {
2314 MOZ_ASSERT(is<WasmTableObject>());
2315 return getReservedSlot(TABLE_SLOT).isUndefined();
2316 }
2317
2318 /* static */
finalize(JSFreeOp * fop,JSObject * obj)2319 void WasmTableObject::finalize(JSFreeOp* fop, JSObject* obj) {
2320 WasmTableObject& tableObj = obj->as<WasmTableObject>();
2321 if (!tableObj.isNewborn()) {
2322 auto& table = tableObj.table();
2323 fop->release(obj, &table, table.gcMallocBytes(), MemoryUse::WasmTableTable);
2324 }
2325 }
2326
2327 /* static */
trace(JSTracer * trc,JSObject * obj)2328 void WasmTableObject::trace(JSTracer* trc, JSObject* obj) {
2329 WasmTableObject& tableObj = obj->as<WasmTableObject>();
2330 if (!tableObj.isNewborn()) {
2331 tableObj.table().tracePrivate(trc);
2332 }
2333 }
2334
2335 /* static */
create(JSContext * cx,const Limits & limits,TableKind tableKind,HandleObject proto)2336 WasmTableObject* WasmTableObject::create(JSContext* cx, const Limits& limits,
2337 TableKind tableKind,
2338 HandleObject proto) {
2339 AutoSetNewObjectMetadata metadata(cx);
2340 RootedWasmTableObject obj(
2341 cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
2342 if (!obj) {
2343 return nullptr;
2344 }
2345
2346 MOZ_ASSERT(obj->isNewborn());
2347
2348 TableDesc td(tableKind, limits, /*importedOrExported=*/true);
2349
2350 SharedTable table = Table::create(cx, td, obj);
2351 if (!table) {
2352 return nullptr;
2353 }
2354
2355 size_t size = table->gcMallocBytes();
2356 InitReservedSlot(obj, TABLE_SLOT, table.forget().take(), size,
2357 MemoryUse::WasmTableTable);
2358
2359 MOZ_ASSERT(!obj->isNewborn());
2360 return obj;
2361 }
2362
2363 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)2364 bool WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) {
2365 CallArgs args = CallArgsFromVp(argc, vp);
2366
2367 if (!ThrowIfNotConstructing(cx, args, "Table")) {
2368 return false;
2369 }
2370
2371 if (!args.requireAtLeast(cx, "WebAssembly.Table", 1)) {
2372 return false;
2373 }
2374
2375 if (!args.get(0).isObject()) {
2376 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2377 JSMSG_WASM_BAD_DESC_ARG, "table");
2378 return false;
2379 }
2380
2381 RootedObject obj(cx, &args[0].toObject());
2382
2383 JSAtom* elementAtom = Atomize(cx, "element", strlen("element"));
2384 if (!elementAtom) {
2385 return false;
2386 }
2387 RootedId elementId(cx, AtomToId(elementAtom));
2388
2389 RootedValue elementVal(cx);
2390 if (!GetProperty(cx, obj, obj, elementId, &elementVal)) {
2391 return false;
2392 }
2393
2394 RootedString elementStr(cx, ToString(cx, elementVal));
2395 if (!elementStr) {
2396 return false;
2397 }
2398
2399 RootedLinearString elementLinearStr(cx, elementStr->ensureLinear(cx));
2400 if (!elementLinearStr) {
2401 return false;
2402 }
2403
2404 TableKind tableKind;
2405 if (StringEqualsLiteral(elementLinearStr, "anyfunc") ||
2406 StringEqualsLiteral(elementLinearStr, "funcref")) {
2407 tableKind = TableKind::FuncRef;
2408 #ifdef ENABLE_WASM_REFTYPES
2409 } else if (StringEqualsLiteral(elementLinearStr, "externref")) {
2410 if (!ReftypesAvailable(cx)) {
2411 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2412 JSMSG_WASM_BAD_ELEMENT);
2413 return false;
2414 }
2415 tableKind = TableKind::AnyRef;
2416 #endif
2417 } else {
2418 #ifdef ENABLE_WASM_REFTYPES
2419 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2420 JSMSG_WASM_BAD_ELEMENT_GENERALIZED);
2421 #else
2422 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2423 JSMSG_WASM_BAD_ELEMENT);
2424 #endif
2425 return false;
2426 }
2427
2428 Limits limits;
2429 if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableLength, "Table",
2430 &limits, Shareable::False)) {
2431 return false;
2432 }
2433
2434 RootedObject proto(cx);
2435 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmTable,
2436 &proto)) {
2437 return false;
2438 }
2439 if (!proto) {
2440 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmTable);
2441 }
2442
2443 RootedWasmTableObject table(
2444 cx, WasmTableObject::create(cx, limits, tableKind, proto));
2445 if (!table) {
2446 return false;
2447 }
2448
2449 args.rval().setObject(*table);
2450 return true;
2451 }
2452
IsTable(HandleValue v)2453 static bool IsTable(HandleValue v) {
2454 return v.isObject() && v.toObject().is<WasmTableObject>();
2455 }
2456
2457 /* static */
lengthGetterImpl(JSContext * cx,const CallArgs & args)2458 bool WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args) {
2459 args.rval().setNumber(
2460 args.thisv().toObject().as<WasmTableObject>().table().length());
2461 return true;
2462 }
2463
2464 /* static */
lengthGetter(JSContext * cx,unsigned argc,Value * vp)2465 bool WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp) {
2466 CallArgs args = CallArgsFromVp(argc, vp);
2467 return CallNonGenericMethod<IsTable, lengthGetterImpl>(cx, args);
2468 }
2469
2470 const JSPropertySpec WasmTableObject::properties[] = {
2471 JS_PSG("length", WasmTableObject::lengthGetter, JSPROP_ENUMERATE),
2472 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Table", JSPROP_READONLY),
2473 JS_PS_END};
2474
ToTableIndex(JSContext * cx,HandleValue v,const Table & table,const char * noun,uint32_t * index)2475 static bool ToTableIndex(JSContext* cx, HandleValue v, const Table& table,
2476 const char* noun, uint32_t* index) {
2477 if (!EnforceRangeU32(cx, v, "Table", noun, index)) {
2478 return false;
2479 }
2480
2481 if (*index >= table.length()) {
2482 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2483 JSMSG_WASM_BAD_RANGE, "Table", noun);
2484 return false;
2485 }
2486
2487 return true;
2488 }
2489
2490 /* static */
getImpl(JSContext * cx,const CallArgs & args)2491 bool WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) {
2492 RootedWasmTableObject tableObj(
2493 cx, &args.thisv().toObject().as<WasmTableObject>());
2494 const Table& table = tableObj->table();
2495
2496 if (!args.requireAtLeast(cx, "WebAssembly.Table.get", 1)) {
2497 return false;
2498 }
2499
2500 uint32_t index;
2501 if (!ToTableIndex(cx, args.get(0), table, "get index", &index)) {
2502 return false;
2503 }
2504
2505 switch (table.repr()) {
2506 case TableRepr::Func: {
2507 MOZ_RELEASE_ASSERT(table.kind() == TableKind::FuncRef);
2508 RootedFunction fun(cx);
2509 if (!table.getFuncRef(cx, index, &fun)) {
2510 return false;
2511 }
2512 args.rval().setObjectOrNull(fun);
2513 break;
2514 }
2515 case TableRepr::Ref: {
2516 args.rval().set(UnboxAnyRef(table.getAnyRef(index)));
2517 break;
2518 }
2519 }
2520 return true;
2521 }
2522
2523 /* static */
get(JSContext * cx,unsigned argc,Value * vp)2524 bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) {
2525 CallArgs args = CallArgsFromVp(argc, vp);
2526 return CallNonGenericMethod<IsTable, getImpl>(cx, args);
2527 }
2528
2529 /* static */
setImpl(JSContext * cx,const CallArgs & args)2530 bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) {
2531 RootedWasmTableObject tableObj(
2532 cx, &args.thisv().toObject().as<WasmTableObject>());
2533 Table& table = tableObj->table();
2534
2535 if (!args.requireAtLeast(cx, "WebAssembly.Table.set", 2)) {
2536 return false;
2537 }
2538
2539 uint32_t index;
2540 if (!ToTableIndex(cx, args.get(0), table, "set index", &index)) {
2541 return false;
2542 }
2543
2544 MOZ_ASSERT(index < MaxTableLength);
2545 static_assert(MaxTableLength < UINT32_MAX, "Invariant");
2546
2547 RootedValue fillValue(cx, args[1]);
2548 RootedFunction fun(cx);
2549 RootedAnyRef any(cx, AnyRef::null());
2550 if (!CheckRefType(cx, ToElemValType(table.kind()).refTypeKind(), fillValue,
2551 &fun, &any)) {
2552 return false;
2553 }
2554 switch (table.kind()) {
2555 case TableKind::AsmJS:
2556 MOZ_CRASH("Should not happen");
2557 case TableKind::FuncRef:
2558 table.fillFuncRef(index, 1, FuncRef::fromJSFunction(fun), cx);
2559 break;
2560 case TableKind::AnyRef:
2561 table.fillAnyRef(index, 1, any);
2562 break;
2563 }
2564
2565 args.rval().setUndefined();
2566 return true;
2567 }
2568
2569 /* static */
set(JSContext * cx,unsigned argc,Value * vp)2570 bool WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp) {
2571 CallArgs args = CallArgsFromVp(argc, vp);
2572 return CallNonGenericMethod<IsTable, setImpl>(cx, args);
2573 }
2574
2575 /* static */
growImpl(JSContext * cx,const CallArgs & args)2576 bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) {
2577 RootedWasmTableObject tableObj(
2578 cx, &args.thisv().toObject().as<WasmTableObject>());
2579 Table& table = tableObj->table();
2580
2581 if (!args.requireAtLeast(cx, "WebAssembly.Table.grow", 1)) {
2582 return false;
2583 }
2584
2585 uint32_t delta;
2586 if (!EnforceRangeU32(cx, args.get(0), "Table", "grow delta", &delta)) {
2587 return false;
2588 }
2589
2590 uint32_t oldLength = table.grow(delta);
2591
2592 if (oldLength == uint32_t(-1)) {
2593 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
2594 "table");
2595 return false;
2596 }
2597
2598 RootedValue fillValue(cx);
2599 fillValue.setNull();
2600 if (args.length() > 1) {
2601 fillValue = args[1];
2602 }
2603
2604 MOZ_ASSERT(delta <= MaxTableLength); // grow() should ensure this
2605 MOZ_ASSERT(oldLength <= MaxTableLength - delta); // ditto
2606
2607 static_assert(MaxTableLength < UINT32_MAX, "Invariant");
2608
2609 if (!fillValue.isNull()) {
2610 RootedFunction fun(cx);
2611 RootedAnyRef any(cx, AnyRef::null());
2612 if (!CheckRefType(cx, ToElemValType(table.kind()).refTypeKind(), fillValue,
2613 &fun, &any)) {
2614 return false;
2615 }
2616 switch (table.repr()) {
2617 case TableRepr::Func:
2618 MOZ_ASSERT(table.kind() == TableKind::FuncRef);
2619 table.fillFuncRef(oldLength, delta, FuncRef::fromJSFunction(fun), cx);
2620 break;
2621 case TableRepr::Ref:
2622 table.fillAnyRef(oldLength, delta, any);
2623 break;
2624 }
2625 }
2626
2627 #ifdef DEBUG
2628 if (fillValue.isNull()) {
2629 switch (table.repr()) {
2630 case TableRepr::Func:
2631 for (uint32_t index = oldLength; index < oldLength + delta; index++) {
2632 MOZ_ASSERT(table.getFuncRef(index).code == nullptr);
2633 }
2634 break;
2635 case TableRepr::Ref:
2636 for (uint32_t index = oldLength; index < oldLength + delta; index++) {
2637 MOZ_ASSERT(table.getAnyRef(index).isNull());
2638 }
2639 break;
2640 }
2641 }
2642 #endif
2643
2644 args.rval().setInt32(oldLength);
2645 return true;
2646 }
2647
2648 /* static */
grow(JSContext * cx,unsigned argc,Value * vp)2649 bool WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp) {
2650 CallArgs args = CallArgsFromVp(argc, vp);
2651 return CallNonGenericMethod<IsTable, growImpl>(cx, args);
2652 }
2653
2654 const JSFunctionSpec WasmTableObject::methods[] = {
2655 JS_FN("get", WasmTableObject::get, 1, JSPROP_ENUMERATE),
2656 JS_FN("set", WasmTableObject::set, 2, JSPROP_ENUMERATE),
2657 JS_FN("grow", WasmTableObject::grow, 1, JSPROP_ENUMERATE), JS_FS_END};
2658
2659 const JSFunctionSpec WasmTableObject::static_methods[] = {JS_FS_END};
2660
table() const2661 Table& WasmTableObject::table() const {
2662 return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
2663 }
2664
2665 // ============================================================================
2666 // WebAssembly.global class and methods
2667
2668 const JSClassOps WasmGlobalObject::classOps_ = {
2669 nullptr, // addProperty
2670 nullptr, // delProperty
2671 nullptr, // enumerate
2672 nullptr, // newEnumerate
2673 nullptr, // resolve
2674 nullptr, // mayResolve
2675 WasmGlobalObject::finalize, // finalize
2676 nullptr, // call
2677 nullptr, // hasInstance
2678 nullptr, // construct
2679 WasmGlobalObject::trace, // trace
2680 };
2681
2682 const JSClass WasmGlobalObject::class_ = {
2683 "WebAssembly.Global",
2684 JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) |
2685 JSCLASS_BACKGROUND_FINALIZE,
2686 &WasmGlobalObject::classOps_, &WasmGlobalObject::classSpec_};
2687
2688 const JSClass& WasmGlobalObject::protoClass_ = PlainObject::class_;
2689
2690 static constexpr char WasmGlobalName[] = "Global";
2691
2692 const ClassSpec WasmGlobalObject::classSpec_ = {
2693 CreateWasmConstructor<WasmGlobalObject, WasmGlobalName>,
2694 GenericCreatePrototype<WasmGlobalObject>,
2695 WasmGlobalObject::static_methods,
2696 nullptr,
2697 WasmGlobalObject::methods,
2698 WasmGlobalObject::properties,
2699 nullptr,
2700 ClassSpec::DontDefineConstructor};
2701
2702 /* static */
trace(JSTracer * trc,JSObject * obj)2703 void WasmGlobalObject::trace(JSTracer* trc, JSObject* obj) {
2704 WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
2705 if (global->isNewborn()) {
2706 // This can happen while we're allocating the object, in which case
2707 // every single slot of the object is not defined yet. In particular,
2708 // there's nothing to trace yet.
2709 return;
2710 }
2711 switch (global->type().kind()) {
2712 case ValType::I32:
2713 case ValType::F32:
2714 case ValType::I64:
2715 case ValType::F64:
2716 case ValType::V128:
2717 break;
2718 case ValType::Ref:
2719 switch (global->type().refTypeKind()) {
2720 case RefType::Func:
2721 case RefType::Any:
2722 if (!global->cell()->ref.isNull()) {
2723 // TODO/AnyRef-boxing: With boxed immediates and strings, the write
2724 // barrier is going to have to be more complicated.
2725 ASSERT_ANYREF_IS_JSOBJECT;
2726 TraceManuallyBarrieredEdge(trc,
2727 global->cell()->ref.asJSObjectAddress(),
2728 "wasm reference-typed global");
2729 }
2730 break;
2731 case RefType::TypeIndex:
2732 MOZ_CRASH("Ref NYI");
2733 }
2734 break;
2735 }
2736 }
2737
2738 /* static */
finalize(JSFreeOp * fop,JSObject * obj)2739 void WasmGlobalObject::finalize(JSFreeOp* fop, JSObject* obj) {
2740 WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
2741 if (!global->isNewborn()) {
2742 fop->delete_(obj, global->cell(), MemoryUse::WasmGlobalCell);
2743 }
2744 }
2745
2746 /* static */
create(JSContext * cx,HandleVal hval,bool isMutable,HandleObject proto)2747 WasmGlobalObject* WasmGlobalObject::create(JSContext* cx, HandleVal hval,
2748 bool isMutable, HandleObject proto) {
2749 AutoSetNewObjectMetadata metadata(cx);
2750 RootedWasmGlobalObject obj(
2751 cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
2752 if (!obj) {
2753 return nullptr;
2754 }
2755
2756 MOZ_ASSERT(obj->isNewborn());
2757 MOZ_ASSERT(obj->isTenured(), "assumed by global.set post barriers");
2758
2759 // It's simpler to initialize the cell after the object has been created,
2760 // to avoid needing to root the cell before the object creation.
2761
2762 Cell* cell = js_new<Cell>();
2763 if (!cell) {
2764 ReportOutOfMemory(cx);
2765 return nullptr;
2766 }
2767
2768 const Val& val = hval.get();
2769 switch (val.type().kind()) {
2770 case ValType::I32:
2771 cell->i32 = val.i32();
2772 break;
2773 case ValType::I64:
2774 cell->i64 = val.i64();
2775 break;
2776 case ValType::F32:
2777 cell->f32 = val.f32();
2778 break;
2779 case ValType::F64:
2780 cell->f64 = val.f64();
2781 break;
2782 case ValType::Ref:
2783 switch (val.type().refTypeKind()) {
2784 case RefType::Func:
2785 case RefType::Any:
2786 MOZ_ASSERT(cell->ref.isNull(), "no prebarriers needed");
2787 cell->ref = val.ref();
2788 if (!cell->ref.isNull()) {
2789 // TODO/AnyRef-boxing: With boxed immediates and strings, the write
2790 // barrier is going to have to be more complicated.
2791 ASSERT_ANYREF_IS_JSOBJECT;
2792 JSObject::writeBarrierPost(cell->ref.asJSObjectAddress(), nullptr,
2793 cell->ref.asJSObject());
2794 }
2795 break;
2796 case RefType::TypeIndex:
2797 MOZ_CRASH("Ref NYI");
2798 }
2799 break;
2800 case ValType::V128:
2801 cell->v128 = val.v128();
2802 break;
2803 }
2804 obj->initReservedSlot(TYPE_SLOT,
2805 Int32Value(int32_t(val.type().bitsUnsafe())));
2806 obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
2807 InitReservedSlot(obj, CELL_SLOT, cell, MemoryUse::WasmGlobalCell);
2808
2809 MOZ_ASSERT(!obj->isNewborn());
2810
2811 return obj;
2812 }
2813
2814 /* static */
construct(JSContext * cx,unsigned argc,Value * vp)2815 bool WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp) {
2816 CallArgs args = CallArgsFromVp(argc, vp);
2817
2818 if (!ThrowIfNotConstructing(cx, args, "Global")) {
2819 return false;
2820 }
2821
2822 if (!args.requireAtLeast(cx, "WebAssembly.Global", 1)) {
2823 return false;
2824 }
2825
2826 if (!args.get(0).isObject()) {
2827 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2828 JSMSG_WASM_BAD_DESC_ARG, "global");
2829 return false;
2830 }
2831
2832 RootedObject obj(cx, &args[0].toObject());
2833
2834 // Extract properties in lexicographic order per spec.
2835
2836 RootedValue mutableVal(cx);
2837 if (!JS_GetProperty(cx, obj, "mutable", &mutableVal)) {
2838 return false;
2839 }
2840
2841 RootedValue typeVal(cx);
2842 if (!JS_GetProperty(cx, obj, "value", &typeVal)) {
2843 return false;
2844 }
2845
2846 RootedString typeStr(cx, ToString(cx, typeVal));
2847 if (!typeStr) {
2848 return false;
2849 }
2850
2851 RootedLinearString typeLinearStr(cx, typeStr->ensureLinear(cx));
2852 if (!typeLinearStr) {
2853 return false;
2854 }
2855
2856 ValType globalType;
2857 if (StringEqualsLiteral(typeLinearStr, "i32")) {
2858 globalType = ValType::I32;
2859 } else if (args.length() == 1 && StringEqualsLiteral(typeLinearStr, "i64")) {
2860 // For the time being, i64 is allowed only if there is not an
2861 // initializing value.
2862 globalType = ValType::I64;
2863 } else if (StringEqualsLiteral(typeLinearStr, "f32")) {
2864 globalType = ValType::F32;
2865 } else if (StringEqualsLiteral(typeLinearStr, "f64")) {
2866 globalType = ValType::F64;
2867 } else if (StringEqualsLiteral(typeLinearStr, "i64")) {
2868 globalType = ValType::I64;
2869 #ifdef ENABLE_WASM_SIMD
2870 } else if (SimdAvailable(cx) && StringEqualsLiteral(typeLinearStr, "v128")) {
2871 globalType = ValType::V128;
2872 #endif
2873 #ifdef ENABLE_WASM_REFTYPES
2874 } else if (ReftypesAvailable(cx) &&
2875 StringEqualsLiteral(typeLinearStr, "funcref")) {
2876 globalType = RefType::func();
2877 } else if (ReftypesAvailable(cx) &&
2878 StringEqualsLiteral(typeLinearStr, "externref")) {
2879 globalType = RefType::any();
2880 #endif
2881 } else {
2882 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2883 JSMSG_WASM_BAD_GLOBAL_TYPE);
2884 return false;
2885 }
2886
2887 bool isMutable = ToBoolean(mutableVal);
2888
2889 // Extract the initial value, or provide a suitable default.
2890 RootedVal globalVal(cx);
2891
2892 // Initialize with default value.
2893 switch (globalType.kind()) {
2894 case ValType::I32:
2895 globalVal = Val(uint32_t(0));
2896 break;
2897 case ValType::I64:
2898 globalVal = Val(uint64_t(0));
2899 break;
2900 case ValType::F32:
2901 globalVal = Val(float(0.0));
2902 break;
2903 case ValType::F64:
2904 globalVal = Val(double(0.0));
2905 break;
2906 case ValType::V128:
2907 globalVal = Val(V128());
2908 break;
2909 case ValType::Ref:
2910 switch (globalType.refTypeKind()) {
2911 case RefType::Func:
2912 globalVal = Val(RefType::func(), AnyRef::null());
2913 break;
2914 case RefType::Any:
2915 globalVal = Val(RefType::any(), AnyRef::null());
2916 break;
2917 case RefType::TypeIndex:
2918 MOZ_CRASH("Ref NYI");
2919 }
2920 break;
2921 }
2922
2923 // Override with non-undefined value, if provided.
2924 RootedValue valueVal(cx, args.get(1));
2925 if (!valueVal.isUndefined() ||
2926 (args.length() >= 2 && globalType.isReference())) {
2927 if (globalType == ValType::V128) {
2928 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2929 JSMSG_WASM_BAD_VAL_TYPE);
2930 return false;
2931 }
2932 if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal)) {
2933 return false;
2934 }
2935 }
2936
2937 RootedObject proto(cx);
2938 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmGlobal,
2939 &proto)) {
2940 return false;
2941 }
2942 if (!proto) {
2943 proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmGlobal);
2944 }
2945
2946 WasmGlobalObject* global =
2947 WasmGlobalObject::create(cx, globalVal, isMutable, proto);
2948 if (!global) {
2949 return false;
2950 }
2951
2952 args.rval().setObject(*global);
2953 return true;
2954 }
2955
IsGlobal(HandleValue v)2956 static bool IsGlobal(HandleValue v) {
2957 return v.isObject() && v.toObject().is<WasmGlobalObject>();
2958 }
2959
2960 /* static */
valueGetterImpl(JSContext * cx,const CallArgs & args)2961 bool WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) {
2962 switch (args.thisv().toObject().as<WasmGlobalObject>().type().kind()) {
2963 case ValType::I32:
2964 case ValType::I64:
2965 case ValType::F32:
2966 case ValType::F64:
2967 args.thisv().toObject().as<WasmGlobalObject>().value(cx, args.rval());
2968 return true;
2969 case ValType::V128:
2970 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2971 JSMSG_WASM_BAD_VAL_TYPE);
2972 return false;
2973 case ValType::Ref:
2974 switch (
2975 args.thisv().toObject().as<WasmGlobalObject>().type().refTypeKind()) {
2976 case RefType::Func:
2977 case RefType::Any:
2978 args.thisv().toObject().as<WasmGlobalObject>().value(cx, args.rval());
2979 return true;
2980 case RefType::TypeIndex:
2981 MOZ_CRASH("Ref NYI");
2982 }
2983 break;
2984 }
2985 MOZ_CRASH();
2986 }
2987
2988 /* static */
valueGetter(JSContext * cx,unsigned argc,Value * vp)2989 bool WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp) {
2990 CallArgs args = CallArgsFromVp(argc, vp);
2991 return CallNonGenericMethod<IsGlobal, valueGetterImpl>(cx, args);
2992 }
2993
2994 /* static */
valueSetterImpl(JSContext * cx,const CallArgs & args)2995 bool WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args) {
2996 if (!args.requireAtLeast(cx, "WebAssembly.Global setter", 1)) {
2997 return false;
2998 }
2999
3000 RootedWasmGlobalObject global(
3001 cx, &args.thisv().toObject().as<WasmGlobalObject>());
3002 if (!global->isMutable()) {
3003 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3004 JSMSG_WASM_GLOBAL_IMMUTABLE);
3005 return false;
3006 }
3007
3008 if (global->type() == ValType::V128) {
3009 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3010 JSMSG_WASM_BAD_VAL_TYPE);
3011 return false;
3012 }
3013
3014 RootedVal val(cx);
3015 if (!ToWebAssemblyValue(cx, global->type(), args.get(0), &val)) {
3016 return false;
3017 }
3018 global->setVal(cx, val);
3019
3020 args.rval().setUndefined();
3021 return true;
3022 }
3023
3024 /* static */
valueSetter(JSContext * cx,unsigned argc,Value * vp)3025 bool WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp) {
3026 CallArgs args = CallArgsFromVp(argc, vp);
3027 return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args);
3028 }
3029
3030 const JSPropertySpec WasmGlobalObject::properties[] = {
3031 JS_PSGS("value", WasmGlobalObject::valueGetter,
3032 WasmGlobalObject::valueSetter, JSPROP_ENUMERATE),
3033 JS_STRING_SYM_PS(toStringTag, "WebAssembly.Global", JSPROP_READONLY),
3034 JS_PS_END};
3035
3036 const JSFunctionSpec WasmGlobalObject::methods[] = {
3037 JS_FN(js_valueOf_str, WasmGlobalObject::valueGetter, 0, JSPROP_ENUMERATE),
3038 JS_FS_END};
3039
3040 const JSFunctionSpec WasmGlobalObject::static_methods[] = {JS_FS_END};
3041
type() const3042 ValType WasmGlobalObject::type() const {
3043 return ValType::fromBitsUnsafe(getReservedSlot(TYPE_SLOT).toInt32());
3044 }
3045
isMutable() const3046 bool WasmGlobalObject::isMutable() const {
3047 return getReservedSlot(MUTABLE_SLOT).toBoolean();
3048 }
3049
setVal(JSContext * cx,wasm::HandleVal hval)3050 void WasmGlobalObject::setVal(JSContext* cx, wasm::HandleVal hval) {
3051 const Val& val = hval.get();
3052 Cell* cell = this->cell();
3053 MOZ_ASSERT(type() == val.type());
3054 switch (type().kind()) {
3055 case ValType::I32:
3056 cell->i32 = val.i32();
3057 break;
3058 case ValType::F32:
3059 cell->f32 = val.f32();
3060 break;
3061 case ValType::F64:
3062 cell->f64 = val.f64();
3063 break;
3064 case ValType::I64:
3065 cell->i64 = val.i64();
3066 break;
3067 case ValType::V128:
3068 MOZ_CRASH("unexpected v128 when setting global's value");
3069 case ValType::Ref:
3070 switch (this->type().refTypeKind()) {
3071 case RefType::Func:
3072 case RefType::Any: {
3073 AnyRef prevPtr = cell->ref;
3074 // TODO/AnyRef-boxing: With boxed immediates and strings, the write
3075 // barrier is going to have to be more complicated.
3076 ASSERT_ANYREF_IS_JSOBJECT;
3077 JSObject::writeBarrierPre(prevPtr.asJSObject());
3078 cell->ref = val.ref();
3079 if (!cell->ref.isNull()) {
3080 JSObject::writeBarrierPost(cell->ref.asJSObjectAddress(),
3081 prevPtr.asJSObject(),
3082 cell->ref.asJSObject());
3083 }
3084 break;
3085 }
3086 case RefType::TypeIndex: {
3087 MOZ_CRASH("Ref NYI");
3088 }
3089 }
3090 break;
3091 }
3092 }
3093
val(MutableHandleVal outval) const3094 void WasmGlobalObject::val(MutableHandleVal outval) const {
3095 Cell* cell = this->cell();
3096 switch (type().kind()) {
3097 case ValType::I32:
3098 outval.set(Val(uint32_t(cell->i32)));
3099 return;
3100 case ValType::I64:
3101 outval.set(Val(uint64_t(cell->i64)));
3102 return;
3103 case ValType::V128:
3104 outval.set(Val(cell->v128));
3105 return;
3106 case ValType::F32:
3107 outval.set(Val(cell->f32));
3108 return;
3109 case ValType::F64:
3110 outval.set(Val(cell->f64));
3111 return;
3112 case ValType::Ref:
3113 switch (type().refTypeKind()) {
3114 case RefType::Func:
3115 outval.set(Val(RefType::func(), cell->ref));
3116 return;
3117 case RefType::Any:
3118 outval.set(Val(RefType::any(), cell->ref));
3119 return;
3120 case RefType::TypeIndex:
3121 MOZ_CRASH("Ref NYI");
3122 }
3123 break;
3124 }
3125 MOZ_CRASH("unexpected Global type");
3126 }
3127
value(JSContext * cx,MutableHandleValue out)3128 bool WasmGlobalObject::value(JSContext* cx, MutableHandleValue out) {
3129 RootedVal result(cx);
3130 val(&result);
3131 return ToJSValue(cx, result.get(), out);
3132 }
3133
cell() const3134 WasmGlobalObject::Cell* WasmGlobalObject::cell() const {
3135 return reinterpret_cast<Cell*>(getReservedSlot(CELL_SLOT).toPrivate());
3136 }
3137
3138 // ============================================================================
3139 // WebAssembly class and static methods
3140
WebAssembly_toSource(JSContext * cx,unsigned argc,Value * vp)3141 static bool WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp) {
3142 CallArgs args = CallArgsFromVp(argc, vp);
3143 args.rval().setString(cx->names().WebAssembly);
3144 return true;
3145 }
3146
RejectWithPendingException(JSContext * cx,Handle<PromiseObject * > promise)3147 static bool RejectWithPendingException(JSContext* cx,
3148 Handle<PromiseObject*> promise) {
3149 if (!cx->isExceptionPending()) {
3150 return false;
3151 }
3152
3153 RootedValue rejectionValue(cx);
3154 if (!GetAndClearException(cx, &rejectionValue)) {
3155 return false;
3156 }
3157
3158 return PromiseObject::reject(cx, promise, rejectionValue);
3159 }
3160
Reject(JSContext * cx,const CompileArgs & args,Handle<PromiseObject * > promise,const UniqueChars & error)3161 static bool Reject(JSContext* cx, const CompileArgs& args,
3162 Handle<PromiseObject*> promise, const UniqueChars& error) {
3163 if (!error) {
3164 ReportOutOfMemory(cx);
3165 return RejectWithPendingException(cx, promise);
3166 }
3167
3168 RootedObject stack(cx, promise->allocationSite());
3169 RootedString filename(
3170 cx, JS_NewStringCopyZ(cx, args.scriptedCaller.filename.get()));
3171 if (!filename) {
3172 return false;
3173 }
3174
3175 unsigned line = args.scriptedCaller.line;
3176
3177 // Ideally we'd report a JSMSG_WASM_COMPILE_ERROR here, but there's no easy
3178 // way to create an ErrorObject for an arbitrary error code with multiple
3179 // replacements.
3180 UniqueChars str(JS_smprintf("wasm validation error: %s", error.get()));
3181 if (!str) {
3182 return false;
3183 }
3184
3185 size_t len = strlen(str.get());
3186 RootedString message(cx, NewStringCopyN<CanGC>(cx, str.get(), len));
3187 if (!message) {
3188 return false;
3189 }
3190
3191 RootedObject errorObj(
3192 cx, ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, 0,
3193 line, 0, nullptr, message));
3194 if (!errorObj) {
3195 return false;
3196 }
3197
3198 RootedValue rejectionValue(cx, ObjectValue(*errorObj));
3199 return PromiseObject::reject(cx, promise, rejectionValue);
3200 }
3201
LogAsync(JSContext * cx,const char * funcName,const Module & module)3202 static void LogAsync(JSContext* cx, const char* funcName,
3203 const Module& module) {
3204 Log(cx, "async %s succeeded%s", funcName,
3205 module.loggingDeserialized() ? " (loaded from cache)" : "");
3206 }
3207
3208 enum class Ret { Pair, Instance };
3209
3210 class AsyncInstantiateTask : public OffThreadPromiseTask {
3211 SharedModule module_;
3212 PersistentRooted<ImportValues> imports_;
3213 Ret ret_;
3214
3215 public:
AsyncInstantiateTask(JSContext * cx,const Module & module,Ret ret,Handle<PromiseObject * > promise)3216 AsyncInstantiateTask(JSContext* cx, const Module& module, Ret ret,
3217 Handle<PromiseObject*> promise)
3218 : OffThreadPromiseTask(cx, promise),
3219 module_(&module),
3220 imports_(cx),
3221 ret_(ret) {}
3222
imports()3223 ImportValues& imports() { return imports_.get(); }
3224
resolve(JSContext * cx,Handle<PromiseObject * > promise)3225 bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
3226 RootedObject instanceProto(
3227 cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
3228
3229 RootedWasmInstanceObject instanceObj(cx);
3230 if (!module_->instantiate(cx, imports_.get(), instanceProto,
3231 &instanceObj)) {
3232 return RejectWithPendingException(cx, promise);
3233 }
3234
3235 RootedValue resolutionValue(cx);
3236 if (ret_ == Ret::Instance) {
3237 resolutionValue = ObjectValue(*instanceObj);
3238 } else {
3239 RootedObject resultObj(cx, JS_NewPlainObject(cx));
3240 if (!resultObj) {
3241 return RejectWithPendingException(cx, promise);
3242 }
3243
3244 RootedObject moduleProto(
3245 cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
3246 RootedObject moduleObj(
3247 cx, WasmModuleObject::create(cx, *module_, moduleProto));
3248 if (!moduleObj) {
3249 return RejectWithPendingException(cx, promise);
3250 }
3251
3252 RootedValue val(cx, ObjectValue(*moduleObj));
3253 if (!JS_DefineProperty(cx, resultObj, "module", val, JSPROP_ENUMERATE)) {
3254 return RejectWithPendingException(cx, promise);
3255 }
3256
3257 val = ObjectValue(*instanceObj);
3258 if (!JS_DefineProperty(cx, resultObj, "instance", val,
3259 JSPROP_ENUMERATE)) {
3260 return RejectWithPendingException(cx, promise);
3261 }
3262
3263 resolutionValue = ObjectValue(*resultObj);
3264 }
3265
3266 if (!PromiseObject::resolve(cx, promise, resolutionValue)) {
3267 return RejectWithPendingException(cx, promise);
3268 }
3269
3270 LogAsync(cx, "instantiate", *module_);
3271 return true;
3272 }
3273 };
3274
AsyncInstantiate(JSContext * cx,const Module & module,HandleObject importObj,Ret ret,Handle<PromiseObject * > promise)3275 static bool AsyncInstantiate(JSContext* cx, const Module& module,
3276 HandleObject importObj, Ret ret,
3277 Handle<PromiseObject*> promise) {
3278 auto task = js::MakeUnique<AsyncInstantiateTask>(cx, module, ret, promise);
3279 if (!task || !task->init(cx)) {
3280 return false;
3281 }
3282
3283 if (!GetImports(cx, module, importObj, &task->imports())) {
3284 return RejectWithPendingException(cx, promise);
3285 }
3286
3287 task.release()->dispatchResolveAndDestroy();
3288 return true;
3289 }
3290
ResolveCompile(JSContext * cx,const Module & module,Handle<PromiseObject * > promise)3291 static bool ResolveCompile(JSContext* cx, const Module& module,
3292 Handle<PromiseObject*> promise) {
3293 RootedObject proto(
3294 cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
3295 RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
3296 if (!moduleObj) {
3297 return RejectWithPendingException(cx, promise);
3298 }
3299
3300 RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
3301 if (!PromiseObject::resolve(cx, promise, resolutionValue)) {
3302 return RejectWithPendingException(cx, promise);
3303 }
3304
3305 LogAsync(cx, "compile", module);
3306 return true;
3307 }
3308
3309 struct CompileBufferTask : PromiseHelperTask {
3310 MutableBytes bytecode;
3311 SharedCompileArgs compileArgs;
3312 UniqueChars error;
3313 UniqueCharsVector warnings;
3314 SharedModule module;
3315 bool instantiate;
3316 PersistentRootedObject importObj;
3317
CompileBufferTaskCompileBufferTask3318 CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise,
3319 HandleObject importObj)
3320 : PromiseHelperTask(cx, promise),
3321 instantiate(true),
3322 importObj(cx, importObj) {}
3323
CompileBufferTaskCompileBufferTask3324 CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise)
3325 : PromiseHelperTask(cx, promise), instantiate(false) {}
3326
initCompileBufferTask3327 bool init(JSContext* cx, const char* introducer) {
3328 compileArgs = InitCompileArgs(cx, introducer);
3329 if (!compileArgs) {
3330 return false;
3331 }
3332 return PromiseHelperTask::init(cx);
3333 }
3334
executeCompileBufferTask3335 void execute() override {
3336 module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
3337 }
3338
resolveCompileBufferTask3339 bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
3340 if (!module) {
3341 return Reject(cx, *compileArgs, promise, error);
3342 }
3343 if (!ReportCompileWarnings(cx, warnings)) {
3344 return false;
3345 }
3346 if (instantiate) {
3347 return AsyncInstantiate(cx, *module, importObj, Ret::Pair, promise);
3348 }
3349 return ResolveCompile(cx, *module, promise);
3350 }
3351 };
3352
RejectWithPendingException(JSContext * cx,Handle<PromiseObject * > promise,CallArgs & callArgs)3353 static bool RejectWithPendingException(JSContext* cx,
3354 Handle<PromiseObject*> promise,
3355 CallArgs& callArgs) {
3356 if (!RejectWithPendingException(cx, promise)) {
3357 return false;
3358 }
3359
3360 callArgs.rval().setObject(*promise);
3361 return true;
3362 }
3363
EnsurePromiseSupport(JSContext * cx)3364 static bool EnsurePromiseSupport(JSContext* cx) {
3365 if (!cx->runtime()->offThreadPromiseState.ref().initialized()) {
3366 JS_ReportErrorASCII(
3367 cx, "WebAssembly Promise APIs not supported in this runtime.");
3368 return false;
3369 }
3370 return true;
3371 }
3372
GetBufferSource(JSContext * cx,CallArgs callArgs,const char * name,MutableBytes * bytecode)3373 static bool GetBufferSource(JSContext* cx, CallArgs callArgs, const char* name,
3374 MutableBytes* bytecode) {
3375 if (!callArgs.requireAtLeast(cx, name, 1)) {
3376 return false;
3377 }
3378
3379 if (!callArgs[0].isObject()) {
3380 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3381 JSMSG_WASM_BAD_BUF_ARG);
3382 return false;
3383 }
3384
3385 return GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG,
3386 bytecode);
3387 }
3388
WebAssembly_compile(JSContext * cx,unsigned argc,Value * vp)3389 static bool WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp) {
3390 if (!EnsurePromiseSupport(cx)) {
3391 return false;
3392 }
3393
3394 Log(cx, "async compile() started");
3395
3396 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
3397 if (!promise) {
3398 return false;
3399 }
3400
3401 auto task = cx->make_unique<CompileBufferTask>(cx, promise);
3402 if (!task || !task->init(cx, "WebAssembly.compile")) {
3403 return false;
3404 }
3405
3406 CallArgs callArgs = CallArgsFromVp(argc, vp);
3407
3408 if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode)) {
3409 return RejectWithPendingException(cx, promise, callArgs);
3410 }
3411
3412 if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) {
3413 return false;
3414 }
3415
3416 callArgs.rval().setObject(*promise);
3417 return true;
3418 }
3419
GetInstantiateArgs(JSContext * cx,CallArgs callArgs,MutableHandleObject firstArg,MutableHandleObject importObj)3420 static bool GetInstantiateArgs(JSContext* cx, CallArgs callArgs,
3421 MutableHandleObject firstArg,
3422 MutableHandleObject importObj) {
3423 if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1)) {
3424 return false;
3425 }
3426
3427 if (!callArgs[0].isObject()) {
3428 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3429 JSMSG_WASM_BAD_BUF_MOD_ARG);
3430 return false;
3431 }
3432
3433 firstArg.set(&callArgs[0].toObject());
3434
3435 return GetImportArg(cx, callArgs, importObj);
3436 }
3437
WebAssembly_instantiate(JSContext * cx,unsigned argc,Value * vp)3438 static bool WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp) {
3439 if (!EnsurePromiseSupport(cx)) {
3440 return false;
3441 }
3442
3443 Log(cx, "async instantiate() started");
3444
3445 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
3446 if (!promise) {
3447 return false;
3448 }
3449
3450 CallArgs callArgs = CallArgsFromVp(argc, vp);
3451
3452 RootedObject firstArg(cx);
3453 RootedObject importObj(cx);
3454 if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj)) {
3455 return RejectWithPendingException(cx, promise, callArgs);
3456 }
3457
3458 const Module* module;
3459 if (IsModuleObject(firstArg, &module)) {
3460 if (!AsyncInstantiate(cx, *module, importObj, Ret::Instance, promise)) {
3461 return false;
3462 }
3463 } else {
3464 auto task = cx->make_unique<CompileBufferTask>(cx, promise, importObj);
3465 if (!task || !task->init(cx, "WebAssembly.instantiate")) {
3466 return false;
3467 }
3468
3469 if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG,
3470 &task->bytecode)) {
3471 return RejectWithPendingException(cx, promise, callArgs);
3472 }
3473
3474 if (!StartOffThreadPromiseHelperTask(cx, std::move(task))) {
3475 return false;
3476 }
3477 }
3478
3479 callArgs.rval().setObject(*promise);
3480 return true;
3481 }
3482
WebAssembly_validate(JSContext * cx,unsigned argc,Value * vp)3483 static bool WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp) {
3484 CallArgs callArgs = CallArgsFromVp(argc, vp);
3485
3486 MutableBytes bytecode;
3487 if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode)) {
3488 return false;
3489 }
3490
3491 UniqueChars error;
3492 bool validated = Validate(cx, *bytecode, &error);
3493
3494 // If the reason for validation failure was OOM (signalled by null error
3495 // message), report out-of-memory so that validate's return is always
3496 // correct.
3497 if (!validated && !error) {
3498 ReportOutOfMemory(cx);
3499 return false;
3500 }
3501
3502 if (error) {
3503 MOZ_ASSERT(!validated);
3504 Log(cx, "validate() failed with: %s", error.get());
3505 }
3506
3507 callArgs.rval().setBoolean(validated);
3508 return true;
3509 }
3510
EnsureStreamSupport(JSContext * cx)3511 static bool EnsureStreamSupport(JSContext* cx) {
3512 // This should match wasm::StreamingCompilationAvailable().
3513
3514 if (!EnsurePromiseSupport(cx)) {
3515 return false;
3516 }
3517
3518 if (!CanUseExtraThreads()) {
3519 JS_ReportErrorASCII(
3520 cx, "WebAssembly.compileStreaming not supported with --no-threads");
3521 return false;
3522 }
3523
3524 if (!cx->runtime()->consumeStreamCallback) {
3525 JS_ReportErrorASCII(cx,
3526 "WebAssembly streaming not supported in this runtime");
3527 return false;
3528 }
3529
3530 return true;
3531 }
3532
3533 // This value is chosen and asserted to be disjoint from any host error code.
3534 static const size_t StreamOOMCode = 0;
3535
RejectWithStreamErrorNumber(JSContext * cx,size_t errorCode,Handle<PromiseObject * > promise)3536 static bool RejectWithStreamErrorNumber(JSContext* cx, size_t errorCode,
3537 Handle<PromiseObject*> promise) {
3538 if (errorCode == StreamOOMCode) {
3539 ReportOutOfMemory(cx);
3540 return false;
3541 }
3542
3543 cx->runtime()->reportStreamErrorCallback(cx, errorCode);
3544 return RejectWithPendingException(cx, promise);
3545 }
3546
3547 class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer {
3548 // The stream progresses monotonically through these states; the helper
3549 // thread wait()s for streamState_ to reach Closed.
3550 enum StreamState { Env, Code, Tail, Closed };
3551 ExclusiveWaitableData<StreamState> streamState_;
3552
3553 // Immutable:
3554 const bool instantiate_;
3555 const PersistentRootedObject importObj_;
3556
3557 // Immutable after noteResponseURLs() which is called at most once before
3558 // first call on stream thread:
3559 const MutableCompileArgs compileArgs_;
3560
3561 // Immutable after Env state:
3562 Bytes envBytes_;
3563 SectionRange codeSection_;
3564
3565 // The code section vector is resized once during the Env state and filled
3566 // in chunk by chunk during the Code state, updating the end-pointer after
3567 // each chunk:
3568 Bytes codeBytes_;
3569 uint8_t* codeBytesEnd_;
3570 ExclusiveBytesPtr exclusiveCodeBytesEnd_;
3571
3572 // Immutable after Tail state:
3573 Bytes tailBytes_;
3574 ExclusiveStreamEndData exclusiveStreamEnd_;
3575
3576 // Written once before Closed state and read in Closed state on main thread:
3577 SharedModule module_;
3578 Maybe<size_t> streamError_;
3579 UniqueChars compileError_;
3580 UniqueCharsVector warnings_;
3581
3582 // Set on stream thread and read racily on helper thread to abort compilation:
3583 Atomic<bool> streamFailed_;
3584
3585 // Called on some thread before consumeChunk(), streamEnd(), streamError()):
3586
noteResponseURLs(const char * url,const char * sourceMapUrl)3587 void noteResponseURLs(const char* url, const char* sourceMapUrl) override {
3588 if (url) {
3589 compileArgs_->scriptedCaller.filename = DuplicateString(url);
3590 compileArgs_->scriptedCaller.filenameIsURL = true;
3591 }
3592 if (sourceMapUrl) {
3593 compileArgs_->sourceMapURL = DuplicateString(sourceMapUrl);
3594 }
3595 }
3596
3597 // Called on a stream thread:
3598
3599 // Until StartOffThreadPromiseHelperTask succeeds, we are responsible for
3600 // dispatching ourselves back to the JS thread.
3601 //
3602 // Warning: After this function returns, 'this' can be deleted at any time, so
3603 // the caller must immediately return from the stream callback.
setClosedAndDestroyBeforeHelperThreadStarted()3604 void setClosedAndDestroyBeforeHelperThreadStarted() {
3605 streamState_.lock().get() = Closed;
3606 dispatchResolveAndDestroy();
3607 }
3608
3609 // See setClosedAndDestroyBeforeHelperThreadStarted() comment.
rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber)3610 bool rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber) {
3611 MOZ_ASSERT(streamState_.lock() == Env);
3612 MOZ_ASSERT(!streamError_);
3613 streamError_ = Some(errorNumber);
3614 setClosedAndDestroyBeforeHelperThreadStarted();
3615 return false;
3616 }
3617
3618 // Once StartOffThreadPromiseHelperTask succeeds, the helper thread will
3619 // dispatchResolveAndDestroy() after execute() returns, but execute()
3620 // wait()s for state to be Closed.
3621 //
3622 // Warning: After this function returns, 'this' can be deleted at any time, so
3623 // the caller must immediately return from the stream callback.
setClosedAndDestroyAfterHelperThreadStarted()3624 void setClosedAndDestroyAfterHelperThreadStarted() {
3625 auto streamState = streamState_.lock();
3626 MOZ_ASSERT(streamState != Closed);
3627 streamState.get() = Closed;
3628 streamState.notify_one(/* stream closed */);
3629 }
3630
3631 // See setClosedAndDestroyAfterHelperThreadStarted() comment.
rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber)3632 bool rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber) {
3633 MOZ_ASSERT(!streamError_);
3634 streamError_ = Some(errorNumber);
3635 streamFailed_ = true;
3636 exclusiveCodeBytesEnd_.lock().notify_one();
3637 exclusiveStreamEnd_.lock().notify_one();
3638 setClosedAndDestroyAfterHelperThreadStarted();
3639 return false;
3640 }
3641
consumeChunk(const uint8_t * begin,size_t length)3642 bool consumeChunk(const uint8_t* begin, size_t length) override {
3643 switch (streamState_.lock().get()) {
3644 case Env: {
3645 if (!envBytes_.append(begin, length)) {
3646 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
3647 }
3648
3649 if (!StartsCodeSection(envBytes_.begin(), envBytes_.end(),
3650 &codeSection_)) {
3651 return true;
3652 }
3653
3654 uint32_t extraBytes = envBytes_.length() - codeSection_.start;
3655 if (extraBytes) {
3656 envBytes_.shrinkTo(codeSection_.start);
3657 }
3658
3659 if (codeSection_.size > MaxCodeSectionBytes) {
3660 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
3661 }
3662
3663 if (!codeBytes_.resize(codeSection_.size)) {
3664 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
3665 }
3666
3667 codeBytesEnd_ = codeBytes_.begin();
3668 exclusiveCodeBytesEnd_.lock().get() = codeBytesEnd_;
3669
3670 if (!StartOffThreadPromiseHelperTask(this)) {
3671 return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
3672 }
3673
3674 // Set the state to Code iff StartOffThreadPromiseHelperTask()
3675 // succeeds so that the state tells us whether we are before or
3676 // after the helper thread started.
3677 streamState_.lock().get() = Code;
3678
3679 if (extraBytes) {
3680 return consumeChunk(begin + length - extraBytes, extraBytes);
3681 }
3682
3683 return true;
3684 }
3685 case Code: {
3686 size_t copyLength =
3687 std::min<size_t>(length, codeBytes_.end() - codeBytesEnd_);
3688 memcpy(codeBytesEnd_, begin, copyLength);
3689 codeBytesEnd_ += copyLength;
3690
3691 {
3692 auto codeStreamEnd = exclusiveCodeBytesEnd_.lock();
3693 codeStreamEnd.get() = codeBytesEnd_;
3694 codeStreamEnd.notify_one();
3695 }
3696
3697 if (codeBytesEnd_ != codeBytes_.end()) {
3698 return true;
3699 }
3700
3701 streamState_.lock().get() = Tail;
3702
3703 if (uint32_t extraBytes = length - copyLength) {
3704 return consumeChunk(begin + copyLength, extraBytes);
3705 }
3706
3707 return true;
3708 }
3709 case Tail: {
3710 if (!tailBytes_.append(begin, length)) {
3711 return rejectAndDestroyAfterHelperThreadStarted(StreamOOMCode);
3712 }
3713
3714 return true;
3715 }
3716 case Closed:
3717 MOZ_CRASH("consumeChunk() in Closed state");
3718 }
3719 MOZ_CRASH("unreachable");
3720 }
3721
streamEnd(JS::OptimizedEncodingListener * tier2Listener)3722 void streamEnd(JS::OptimizedEncodingListener* tier2Listener) override {
3723 switch (streamState_.lock().get()) {
3724 case Env: {
3725 SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
3726 if (!bytecode) {
3727 rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
3728 return;
3729 }
3730 module_ =
3731 CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_);
3732 setClosedAndDestroyBeforeHelperThreadStarted();
3733 return;
3734 }
3735 case Code:
3736 case Tail:
3737 // Unlock exclusiveStreamEnd_ before locking streamState_.
3738 {
3739 auto streamEnd = exclusiveStreamEnd_.lock();
3740 MOZ_ASSERT(!streamEnd->reached);
3741 streamEnd->reached = true;
3742 streamEnd->tailBytes = &tailBytes_;
3743 streamEnd->tier2Listener = tier2Listener;
3744 streamEnd.notify_one();
3745 }
3746 setClosedAndDestroyAfterHelperThreadStarted();
3747 return;
3748 case Closed:
3749 MOZ_CRASH("streamEnd() in Closed state");
3750 }
3751 }
3752
streamError(size_t errorCode)3753 void streamError(size_t errorCode) override {
3754 MOZ_ASSERT(errorCode != StreamOOMCode);
3755 switch (streamState_.lock().get()) {
3756 case Env:
3757 rejectAndDestroyBeforeHelperThreadStarted(errorCode);
3758 return;
3759 case Tail:
3760 case Code:
3761 rejectAndDestroyAfterHelperThreadStarted(errorCode);
3762 return;
3763 case Closed:
3764 MOZ_CRASH("streamError() in Closed state");
3765 }
3766 }
3767
consumeOptimizedEncoding(const uint8_t * begin,size_t length)3768 void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override {
3769 module_ = Module::deserialize(begin, length);
3770
3771 MOZ_ASSERT(streamState_.lock().get() == Env);
3772 setClosedAndDestroyBeforeHelperThreadStarted();
3773 }
3774
3775 // Called on a helper thread:
3776
execute()3777 void execute() override {
3778 module_ = CompileStreaming(*compileArgs_, envBytes_, codeBytes_,
3779 exclusiveCodeBytesEnd_, exclusiveStreamEnd_,
3780 streamFailed_, &compileError_, &warnings_);
3781
3782 // When execute() returns, the CompileStreamTask will be dispatched
3783 // back to its JS thread to call resolve() and then be destroyed. We
3784 // can't let this happen until the stream has been closed lest
3785 // consumeChunk() or streamEnd() be called on a dead object.
3786 auto streamState = streamState_.lock();
3787 while (streamState != Closed) {
3788 streamState.wait(/* stream closed */);
3789 }
3790 }
3791
3792 // Called on a JS thread after streaming compilation completes/errors:
3793
resolve(JSContext * cx,Handle<PromiseObject * > promise)3794 bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
3795 MOZ_ASSERT(streamState_.lock() == Closed);
3796
3797 if (module_) {
3798 MOZ_ASSERT(!streamFailed_ && !streamError_ && !compileError_);
3799 if (!ReportCompileWarnings(cx, warnings_)) {
3800 return false;
3801 }
3802 if (instantiate_) {
3803 return AsyncInstantiate(cx, *module_, importObj_, Ret::Pair, promise);
3804 }
3805 return ResolveCompile(cx, *module_, promise);
3806 }
3807
3808 if (streamError_) {
3809 return RejectWithStreamErrorNumber(cx, *streamError_, promise);
3810 }
3811
3812 return Reject(cx, *compileArgs_, promise, compileError_);
3813 }
3814
3815 public:
CompileStreamTask(JSContext * cx,Handle<PromiseObject * > promise,CompileArgs & compileArgs,bool instantiate,HandleObject importObj)3816 CompileStreamTask(JSContext* cx, Handle<PromiseObject*> promise,
3817 CompileArgs& compileArgs, bool instantiate,
3818 HandleObject importObj)
3819 : PromiseHelperTask(cx, promise),
3820 streamState_(mutexid::WasmStreamStatus, Env),
3821 instantiate_(instantiate),
3822 importObj_(cx, importObj),
3823 compileArgs_(&compileArgs),
3824 codeSection_{},
3825 codeBytesEnd_(nullptr),
3826 exclusiveCodeBytesEnd_(mutexid::WasmCodeBytesEnd, nullptr),
3827 exclusiveStreamEnd_(mutexid::WasmStreamEnd),
3828 streamFailed_(false) {
3829 MOZ_ASSERT_IF(importObj_, instantiate_);
3830 }
3831 };
3832
3833 // A short-lived object that captures the arguments of a
3834 // WebAssembly.{compileStreaming,instantiateStreaming} while waiting for
3835 // the Promise<Response> to resolve to a (hopefully) Promise.
3836 class ResolveResponseClosure : public NativeObject {
3837 static const unsigned COMPILE_ARGS_SLOT = 0;
3838 static const unsigned PROMISE_OBJ_SLOT = 1;
3839 static const unsigned INSTANTIATE_SLOT = 2;
3840 static const unsigned IMPORT_OBJ_SLOT = 3;
3841 static const JSClassOps classOps_;
3842
finalize(JSFreeOp * fop,JSObject * obj)3843 static void finalize(JSFreeOp* fop, JSObject* obj) {
3844 auto& closure = obj->as<ResolveResponseClosure>();
3845 fop->release(obj, &closure.compileArgs(),
3846 MemoryUse::WasmResolveResponseClosure);
3847 }
3848
3849 public:
3850 static const unsigned RESERVED_SLOTS = 4;
3851 static const JSClass class_;
3852
create(JSContext * cx,const CompileArgs & args,HandleObject promise,bool instantiate,HandleObject importObj)3853 static ResolveResponseClosure* create(JSContext* cx, const CompileArgs& args,
3854 HandleObject promise, bool instantiate,
3855 HandleObject importObj) {
3856 MOZ_ASSERT_IF(importObj, instantiate);
3857
3858 AutoSetNewObjectMetadata metadata(cx);
3859 auto* obj = NewObjectWithGivenProto<ResolveResponseClosure>(cx, nullptr);
3860 if (!obj) {
3861 return nullptr;
3862 }
3863
3864 args.AddRef();
3865 InitReservedSlot(obj, COMPILE_ARGS_SLOT, const_cast<CompileArgs*>(&args),
3866 MemoryUse::WasmResolveResponseClosure);
3867 obj->setReservedSlot(PROMISE_OBJ_SLOT, ObjectValue(*promise));
3868 obj->setReservedSlot(INSTANTIATE_SLOT, BooleanValue(instantiate));
3869 obj->setReservedSlot(IMPORT_OBJ_SLOT, ObjectOrNullValue(importObj));
3870 return obj;
3871 }
3872
compileArgs() const3873 CompileArgs& compileArgs() const {
3874 return *(CompileArgs*)getReservedSlot(COMPILE_ARGS_SLOT).toPrivate();
3875 }
promise() const3876 PromiseObject& promise() const {
3877 return getReservedSlot(PROMISE_OBJ_SLOT).toObject().as<PromiseObject>();
3878 }
instantiate() const3879 bool instantiate() const {
3880 return getReservedSlot(INSTANTIATE_SLOT).toBoolean();
3881 }
importObj() const3882 JSObject* importObj() const {
3883 return getReservedSlot(IMPORT_OBJ_SLOT).toObjectOrNull();
3884 }
3885 };
3886
3887 const JSClassOps ResolveResponseClosure::classOps_ = {
3888 nullptr, // addProperty
3889 nullptr, // delProperty
3890 nullptr, // enumerate
3891 nullptr, // newEnumerate
3892 nullptr, // resolve
3893 nullptr, // mayResolve
3894 ResolveResponseClosure::finalize, // finalize
3895 nullptr, // call
3896 nullptr, // hasInstance
3897 nullptr, // construct
3898 nullptr, // trace
3899 };
3900
3901 const JSClass ResolveResponseClosure::class_ = {
3902 "WebAssembly ResolveResponseClosure",
3903 JSCLASS_DELAY_METADATA_BUILDER |
3904 JSCLASS_HAS_RESERVED_SLOTS(ResolveResponseClosure::RESERVED_SLOTS) |
3905 JSCLASS_FOREGROUND_FINALIZE,
3906 &ResolveResponseClosure::classOps_,
3907 };
3908
ToResolveResponseClosure(CallArgs args)3909 static ResolveResponseClosure* ToResolveResponseClosure(CallArgs args) {
3910 return &args.callee()
3911 .as<JSFunction>()
3912 .getExtendedSlot(0)
3913 .toObject()
3914 .as<ResolveResponseClosure>();
3915 }
3916
RejectWithErrorNumber(JSContext * cx,uint32_t errorNumber,Handle<PromiseObject * > promise)3917 static bool RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber,
3918 Handle<PromiseObject*> promise) {
3919 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
3920 return RejectWithPendingException(cx, promise);
3921 }
3922
ResolveResponse_OnFulfilled(JSContext * cx,unsigned argc,Value * vp)3923 static bool ResolveResponse_OnFulfilled(JSContext* cx, unsigned argc,
3924 Value* vp) {
3925 CallArgs callArgs = CallArgsFromVp(argc, vp);
3926
3927 Rooted<ResolveResponseClosure*> closure(cx,
3928 ToResolveResponseClosure(callArgs));
3929 Rooted<PromiseObject*> promise(cx, &closure->promise());
3930 CompileArgs& compileArgs = closure->compileArgs();
3931 bool instantiate = closure->instantiate();
3932 Rooted<JSObject*> importObj(cx, closure->importObj());
3933
3934 auto task = cx->make_unique<CompileStreamTask>(cx, promise, compileArgs,
3935 instantiate, importObj);
3936 if (!task || !task->init(cx)) {
3937 return false;
3938 }
3939
3940 if (!callArgs.get(0).isObject()) {
3941 return RejectWithErrorNumber(cx, JSMSG_BAD_RESPONSE_VALUE, promise);
3942 }
3943
3944 RootedObject response(cx, &callArgs.get(0).toObject());
3945 if (!cx->runtime()->consumeStreamCallback(cx, response, JS::MimeType::Wasm,
3946 task.get())) {
3947 return RejectWithPendingException(cx, promise);
3948 }
3949
3950 Unused << task.release();
3951
3952 callArgs.rval().setUndefined();
3953 return true;
3954 }
3955
ResolveResponse_OnRejected(JSContext * cx,unsigned argc,Value * vp)3956 static bool ResolveResponse_OnRejected(JSContext* cx, unsigned argc,
3957 Value* vp) {
3958 CallArgs args = CallArgsFromVp(argc, vp);
3959
3960 Rooted<ResolveResponseClosure*> closure(cx, ToResolveResponseClosure(args));
3961 Rooted<PromiseObject*> promise(cx, &closure->promise());
3962
3963 if (!PromiseObject::reject(cx, promise, args.get(0))) {
3964 return false;
3965 }
3966
3967 args.rval().setUndefined();
3968 return true;
3969 }
3970
ResolveResponse(JSContext * cx,CallArgs callArgs,Handle<PromiseObject * > promise,bool instantiate=false,HandleObject importObj=nullptr)3971 static bool ResolveResponse(JSContext* cx, CallArgs callArgs,
3972 Handle<PromiseObject*> promise,
3973 bool instantiate = false,
3974 HandleObject importObj = nullptr) {
3975 MOZ_ASSERT_IF(importObj, instantiate);
3976
3977 const char* introducer = instantiate ? "WebAssembly.instantiateStreaming"
3978 : "WebAssembly.compileStreaming";
3979
3980 SharedCompileArgs compileArgs = InitCompileArgs(cx, introducer);
3981 if (!compileArgs) {
3982 return false;
3983 }
3984
3985 RootedObject closure(
3986 cx, ResolveResponseClosure::create(cx, *compileArgs, promise, instantiate,
3987 importObj));
3988 if (!closure) {
3989 return false;
3990 }
3991
3992 RootedFunction onResolved(
3993 cx, NewNativeFunction(cx, ResolveResponse_OnFulfilled, 1, nullptr,
3994 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
3995 if (!onResolved) {
3996 return false;
3997 }
3998
3999 RootedFunction onRejected(
4000 cx, NewNativeFunction(cx, ResolveResponse_OnRejected, 1, nullptr,
4001 gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
4002 if (!onRejected) {
4003 return false;
4004 }
4005
4006 onResolved->setExtendedSlot(0, ObjectValue(*closure));
4007 onRejected->setExtendedSlot(0, ObjectValue(*closure));
4008
4009 RootedObject resolve(cx,
4010 PromiseObject::unforgeableResolve(cx, callArgs.get(0)));
4011 if (!resolve) {
4012 return false;
4013 }
4014
4015 return JS::AddPromiseReactions(cx, resolve, onResolved, onRejected);
4016 }
4017
WebAssembly_compileStreaming(JSContext * cx,unsigned argc,Value * vp)4018 static bool WebAssembly_compileStreaming(JSContext* cx, unsigned argc,
4019 Value* vp) {
4020 if (!EnsureStreamSupport(cx)) {
4021 return false;
4022 }
4023
4024 Log(cx, "async compileStreaming() started");
4025
4026 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
4027 if (!promise) {
4028 return false;
4029 }
4030
4031 CallArgs callArgs = CallArgsFromVp(argc, vp);
4032
4033 if (!ResolveResponse(cx, callArgs, promise)) {
4034 return RejectWithPendingException(cx, promise, callArgs);
4035 }
4036
4037 callArgs.rval().setObject(*promise);
4038 return true;
4039 }
4040
WebAssembly_instantiateStreaming(JSContext * cx,unsigned argc,Value * vp)4041 static bool WebAssembly_instantiateStreaming(JSContext* cx, unsigned argc,
4042 Value* vp) {
4043 if (!EnsureStreamSupport(cx)) {
4044 return false;
4045 }
4046
4047 Log(cx, "async instantiateStreaming() started");
4048
4049 Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
4050 if (!promise) {
4051 return false;
4052 }
4053
4054 CallArgs callArgs = CallArgsFromVp(argc, vp);
4055
4056 RootedObject firstArg(cx);
4057 RootedObject importObj(cx);
4058 if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj)) {
4059 return RejectWithPendingException(cx, promise, callArgs);
4060 }
4061
4062 if (!ResolveResponse(cx, callArgs, promise, true, importObj)) {
4063 return RejectWithPendingException(cx, promise, callArgs);
4064 }
4065
4066 callArgs.rval().setObject(*promise);
4067 return true;
4068 }
4069
4070 static const JSFunctionSpec WebAssembly_static_methods[] = {
4071 JS_FN(js_toSource_str, WebAssembly_toSource, 0, 0),
4072 JS_FN("compile", WebAssembly_compile, 1, JSPROP_ENUMERATE),
4073 JS_FN("instantiate", WebAssembly_instantiate, 1, JSPROP_ENUMERATE),
4074 JS_FN("validate", WebAssembly_validate, 1, JSPROP_ENUMERATE),
4075 JS_FN("compileStreaming", WebAssembly_compileStreaming, 1,
4076 JSPROP_ENUMERATE),
4077 JS_FN("instantiateStreaming", WebAssembly_instantiateStreaming, 1,
4078 JSPROP_ENUMERATE),
4079 JS_FS_END};
4080
CreateWebAssemblyObject(JSContext * cx,JSProtoKey key)4081 static JSObject* CreateWebAssemblyObject(JSContext* cx, JSProtoKey key) {
4082 MOZ_RELEASE_ASSERT(HasSupport(cx));
4083
4084 Handle<GlobalObject*> global = cx->global();
4085 RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
4086 if (!proto) {
4087 return nullptr;
4088 }
4089 return NewSingletonObjectWithGivenProto(cx, &WebAssemblyClass, proto);
4090 }
4091
WebAssemblyClassFinish(JSContext * cx,HandleObject wasm,HandleObject proto)4092 static bool WebAssemblyClassFinish(JSContext* cx, HandleObject wasm,
4093 HandleObject proto) {
4094 struct NameAndProtoKey {
4095 const char* const name;
4096 JSProtoKey key;
4097 };
4098
4099 constexpr NameAndProtoKey entries[] = {
4100 {"Module", JSProto_WasmModule},
4101 {"Instance", JSProto_WasmInstance},
4102 {"Memory", JSProto_WasmMemory},
4103 {"Table", JSProto_WasmTable},
4104 {"Global", JSProto_WasmGlobal},
4105 {"CompileError", GetExceptionProtoKey(JSEXN_WASMCOMPILEERROR)},
4106 {"LinkError", GetExceptionProtoKey(JSEXN_WASMLINKERROR)},
4107 {"RuntimeError", GetExceptionProtoKey(JSEXN_WASMRUNTIMEERROR)},
4108 };
4109
4110 RootedValue ctorValue(cx);
4111 RootedId id(cx);
4112 for (const auto& entry : entries) {
4113 const char* name = entry.name;
4114 JSProtoKey key = entry.key;
4115
4116 JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, key);
4117 if (!ctor) {
4118 return false;
4119 }
4120 ctorValue.setObject(*ctor);
4121
4122 JSAtom* className = Atomize(cx, name, strlen(name));
4123 if (!className) {
4124 return false;
4125 }
4126 id.set(AtomToId(className));
4127
4128 if (!DefineDataProperty(cx, wasm, id, ctorValue, 0)) {
4129 return false;
4130 }
4131 }
4132
4133 return true;
4134 }
4135
4136 static const ClassSpec WebAssemblyClassSpec = {CreateWebAssemblyObject,
4137 nullptr,
4138 WebAssembly_static_methods,
4139 nullptr,
4140 nullptr,
4141 nullptr,
4142 WebAssemblyClassFinish};
4143
4144 const JSClass js::WebAssemblyClass = {
4145 js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly),
4146 JS_NULL_CLASS_OPS, &WebAssemblyClassSpec};
4147