Bug 1412238 - Implement WebAssembly.Global for immutables. r=luke

--HG--
extra : rebase_source : 95f927424504041f5e08cce8f233f2cbbc1a641f
This commit is contained in:
Lars T Hansen 2018-01-10 14:44:35 +01:00
parent 6b0c25d28f
commit 3d9e46b95b
9 changed files with 444 additions and 59 deletions

View File

@ -119,7 +119,12 @@ module = wasmEvalText(`(module
)`, { globals: {x: 42} }).exports;
assertEq(module.getter(), 42);
assertEq(module.value, 42);
// Adapt to ongoing experiment with WebAssembly.Global.
// assertEq() will not trigger @@toPrimitive, so we must have a cast here.
if (typeof WebAssembly.Global === "function")
assertEq(Number(module.value), 42);
else
assertEq(module.value, 42);
// Can only import numbers (no implicit coercions).
module = new WebAssembly.Module(wasmTextToBinary(`(module
@ -174,8 +179,14 @@ module = wasmEvalText(`(module
(export "defined" global 1)
)`, { globals: {x: 42} }).exports;
assertEq(module.imported, 42);
assertEq(module.defined, 1337);
// See comment earlier about WebAssembly.Global
if (typeof WebAssembly.Global === "function") {
assertEq(Number(module.imported), 42);
assertEq(Number(module.defined), 1337);
} else {
assertEq(module.imported, 42);
assertEq(module.defined, 1337);
}
// Initializer expressions can reference an imported immutable global.
wasmFailValidateText(`(module (global f32 (f32.const 13.37)) (global i32 (get_global 0)))`, /must reference a global immutable import/);
@ -212,12 +223,20 @@ function testInitExpr(type, initialValue, nextValue, coercion, assertFunc = asse
assertFunc(module.get0(), coercion(initialValue));
assertFunc(module.get1(), coercion(initialValue));
assertFunc(module.global_imm, coercion(initialValue));
// See comment earlier about WebAssembly.Global
if (typeof WebAssembly.Global === "function")
assertFunc(Number(module.global_imm), coercion(initialValue));
else
assertFunc(module.global_imm, coercion(initialValue));
assertEq(module.set1(coercion(nextValue)), undefined);
assertFunc(module.get1(), coercion(nextValue));
assertFunc(module.get0(), coercion(initialValue));
assertFunc(module.global_imm, coercion(initialValue));
// See comment earlier about WebAssembly.Global
if (typeof WebAssembly.Global === "function")
assertFunc(Number(module.global_imm), coercion(initialValue));
else
assertFunc(module.global_imm, coercion(initialValue));
assertFunc(module.get_cst(), coercion(initialValue));
}
@ -270,3 +289,64 @@ wasmAssert(`(module
dv.setFloat32(0, module.nan32, true);
assertEq(dv.getUint32(0, true), 0x7fc00000);
}
// WebAssembly.Global experiment
if (typeof WebAssembly.Global === "function") {
// These types should work:
assertEq(new WebAssembly.Global({type: "i32"}) instanceof WebAssembly.Global, true);
assertEq(new WebAssembly.Global({type: "f32"}) instanceof WebAssembly.Global, true);
assertEq(new WebAssembly.Global({type: "f64"}) instanceof WebAssembly.Global, true);
// These types should not work:
assertErrorMessage(() => new WebAssembly.Global({type: "i64"}),
TypeError,
/bad type for a WebAssembly.Global/);
assertErrorMessage(() => new WebAssembly.Global({}),
TypeError,
/bad type for a WebAssembly.Global/);
assertErrorMessage(() => new WebAssembly.Global({type: "fnord"}),
TypeError,
/bad type for a WebAssembly.Global/);
assertErrorMessage(() => new WebAssembly.Global(),
TypeError,
/WebAssembly.Global requires more than 0 arguments/);
// Coercion of init value; ".value" accessor
assertEq((new WebAssembly.Global({type: "i32", value: 3.14})).value, 3);
assertEq((new WebAssembly.Global({type: "f32", value: { valueOf: () => 33.5 }})).value, 33.5);
// Misc internal conversions
let g = new WebAssembly.Global({type: "i32", value: 42});
// @@toPrimitive
assertEq(g - 5, 37);
assertEq(String(g), "42");
// @@toStringTag
assertEq(g.toString(), "[object WebAssembly.Global]");
// An exported global should appear as a WebAssembly.Global instance:
let i =
new WebAssembly.Instance(
new WebAssembly.Module(
wasmTextToBinary(`(module (global (export "g") i32 (i32.const 42)))`)));
assertEq(typeof i.exports.g, "object");
assertEq(i.exports.g instanceof WebAssembly.Global, true);
// An exported global can be imported into another instance even if
// it is an object:
let j =
new WebAssembly.Instance(
new WebAssembly.Module(
wasmTextToBinary(`(module
(global (import "" "g") i32)
(func (export "f") (result i32)
(get_global 0)))`)),
{ "": { "g": i.exports.g }});
// And when it is then accessed it has the right value:
assertEq(j.exports.f(), 42);
}

View File

@ -222,6 +222,15 @@ function get(instance, name) {
if (instance.isError())
return instance;
// Experimental API change. We try to export WebAssembly.Global instances,
// not primitive values. In that case the Number() cast is necessary here
// to convert the Global to a value: the harness examines types carefully
// and will not trigger the @@toPrimitive hook on Global, unlike most user
// code.
if (typeof WebAssembly.Global === "function")
return ValueResult(Number(instance.value.exports[name]));
return ValueResult(instance.value.exports[name]);
}

View File

@ -387,10 +387,12 @@ MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG, 0, JSEXN_TYPEERR, "second argument mu
MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD, 1, JSEXN_TYPEERR, "import object field '{0}' is not an Object")
MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE, 0, JSEXN_TYPEERR, "can only assign WebAssembly exported functions to Table")
MSG_DEF(JSMSG_WASM_BAD_I64_TYPE, 0, JSEXN_TYPEERR, "cannot pass i64 to or from JS")
MSG_DEF(JSMSG_WASM_BAD_GLOBAL_TYPE, 0, JSEXN_TYPEERR, "bad type for a WebAssembly.Global")
MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer")
MSG_DEF(JSMSG_WASM_STREAM_ERROR, 0, JSEXN_TYPEERR, "stream error during WebAssembly compilation")
MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}")
MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified")
MSG_DEF(JSMSG_WASM_GLOBAL_IMMUTABLE, 0, JSEXN_TYPEERR, "can't set value of immutable global")
// Proxy
MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")

View File

@ -127,6 +127,7 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \
imaginary(WasmInstance, dummy, dummy) \
imaginary(WasmMemory, dummy, dummy) \
imaginary(WasmTable, dummy, dummy) \
imaginary(WasmGlobal, dummy, dummy) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)

View File

@ -652,6 +652,11 @@ if CONFIG['NIGHTLY_BUILD']:
DEFINES['ENABLE_SIMD'] = True
DEFINES['ENABLE_WASM_SIGNEXTEND_OPS'] = True
DEFINES['ENABLE_WASM_THREAD_OPS'] = True
# An experiment we want to run on Nightly: Can we change the JS
# representation of an exported global from the global's value
# to an instance of WebAssembly.Global without breaking existing
# wasm content?
DEFINES['ENABLE_WASM_GLOBAL'] = True
if CONFIG['JS_BUILD_BINAST']:
# Using SOURCES as UNIFIED_SOURCES causes mysterious bugs on 32-bit platforms.

View File

@ -98,6 +98,61 @@ wasm::HasSupport(JSContext* cx)
return cx->options().wasm() && HasCompilerSupport(cx);
}
bool
wasm::ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, Val* val)
{
switch (targetType) {
case ValType::I32: {
int32_t i32;
if (!ToInt32(cx, v, &i32))
return false;
*val = Val(uint32_t(i32));
return true;
}
case ValType::F32: {
double d;
if (!ToNumber(cx, v, &d))
return false;
*val = Val(float(d));
return true;
}
case ValType::F64: {
double d;
if (!ToNumber(cx, v, &d))
return false;
*val = Val(d);
return true;
}
default: {
MOZ_CRASH("unexpected import value type, caller must guard");
}
}
}
void
wasm::ToJSValue(const Val& val, MutableHandleValue value)
{
switch (val.type()) {
case ValType::I32: {
value.set(Int32Value(val.i32()));
return;
}
case ValType::F32: {
float f = val.f32();
value.set(DoubleValue(JS::CanonicalizeNaN(double(f))));
return;
}
case ValType::F64: {
double d = val.f64();
value.set(DoubleValue(JS::CanonicalizeNaN(d)));
return;
}
default: {
MOZ_CRASH("unexpected type when translating to a JS value");
}
}
}
// ============================================================================
// Imports
@ -187,42 +242,22 @@ GetImports(JSContext* cx,
const GlobalDesc& global = globals[globalIndex++];
MOZ_ASSERT(global.importIndex() == globalIndex - 1);
MOZ_ASSERT(!global.isMutable());
switch (global.type()) {
case ValType::I32: {
if (!v.isNumber())
return ThrowBadImportType(cx, import.field.get(), "Number");
int32_t i32;
if (!ToInt32(cx, v, &i32))
return false;
val = Val(uint32_t(i32));
break;
}
case ValType::I64: {
#ifdef ENABLE_WASM_GLOBAL
if (v.isObject() && v.toObject().is<WasmGlobalObject>())
v.set(v.toObject().as<WasmGlobalObject>().value());
#endif
if (global.type() == ValType::I64) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
return false;
}
case ValType::F32: {
if (!v.isNumber())
return ThrowBadImportType(cx, import.field.get(), "Number");
double d;
if (!ToNumber(cx, v, &d))
return false;
val = Val(float(d));
break;
}
case ValType::F64: {
if (!v.isNumber())
return ThrowBadImportType(cx, import.field.get(), "Number");
double d;
if (!ToNumber(cx, v, &d))
return false;
val = Val(d);
break;
}
default: {
MOZ_CRASH("unexpected import value type");
}
}
if (!v.isNumber())
return ThrowBadImportType(cx, import.field.get(), "Number");
if (!ToWebAssemblyValue(cx, global.type(), v, &val))
return false;
if (!globalImports->append(val))
return false;
}
@ -1884,6 +1919,204 @@ WasmTableObject::table() const
return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
}
// ============================================================================
// WebAssembly.global class and methods
#ifdef ENABLE_WASM_GLOBAL
const ClassOps WasmGlobalObject::classOps_ =
{
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
nullptr /* trace */
};
const Class WasmGlobalObject::class_ =
{
"WebAssembly.Global",
JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS),
&WasmGlobalObject::classOps_
};
/* static */ WasmGlobalObject*
WasmGlobalObject::create(JSContext* cx, wasm::ValType type, bool isMutable, HandleValue val)
{
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmGlobal).toObject());
AutoSetNewObjectMetadata metadata(cx);
Rooted<WasmGlobalObject*> obj(cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
if (!obj)
return nullptr;
obj->initReservedSlot(TYPE_SLOT, Int32Value(int32_t(type)));
obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
obj->initReservedSlot(VALUE_SLOT, val);
return obj;
}
/* static */ bool
WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "Global"))
return false;
if (!args.requireAtLeast(cx, "WebAssembly.Global", 1))
return false;
if (!args.get(0).isObject()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "global");
return false;
}
RootedObject obj(cx, &args[0].toObject());
RootedValue typeVal(cx);
if (!JS_GetProperty(cx, obj, "type", &typeVal))
return false;
RootedString typeStr(cx, ToString(cx, typeVal));
if (!typeStr)
return false;
RootedLinearString typeLinearStr(cx, typeStr->ensureLinear(cx));
if (!typeLinearStr)
return false;
ValType globalType;
if (StringEqualsAscii(typeLinearStr, "i32")) {
globalType = ValType::I32;
} else if (StringEqualsAscii(typeLinearStr, "f32")) {
globalType = ValType::F32;
} else if (StringEqualsAscii(typeLinearStr, "f64")) {
globalType = ValType::F64;
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOBAL_TYPE);
return false;
}
RootedValue mutableVal(cx);
if (!JS_GetProperty(cx, obj, "mutable", &mutableVal))
return false;
bool isMutable = ToBoolean(mutableVal);
RootedValue valueVal(cx);
if (!JS_GetProperty(cx, obj, "value", &valueVal))
return false;
Val globalVal;
if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal))
return false;
RootedValue globalValue(cx);
ToJSValue(globalVal, &globalValue);
Rooted<WasmGlobalObject*> global(cx, WasmGlobalObject::create(cx, globalType, isMutable,
globalValue));
if (!global)
return false;
args.rval().setObject(*global);
return true;
}
static bool
IsGlobal(HandleValue v)
{
return v.isObject() && v.toObject().is<WasmGlobalObject>();
}
/* static */ bool
WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args)
{
switch (args.thisv().toObject().as<WasmGlobalObject>().type()) {
case ValType::I32:
case ValType::F32:
case ValType::F64:
args.rval().set(args.thisv().toObject().as<WasmGlobalObject>().value());
return true;
case ValType::I64:
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE);
return false;
default:
MOZ_CRASH();
}
}
/* static */ bool
WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsGlobal, valueGetterImpl>(cx, args);
}
/* static */ bool
WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args)
{
if (!args.thisv().toObject().as<WasmGlobalObject>().isMutable()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_GLOBAL_IMMUTABLE);
return false;
}
// TODO - implement this, we probably need a different representation for
// mutable globals.
JS_ReportErrorASCII(cx, "Value setter not yet implemented");
return false;
}
/* static */ bool
WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsGlobal, valueSetterImpl>(cx, args);
}
const JSPropertySpec WasmGlobalObject::properties[] =
{
JS_PSGS("value", WasmGlobalObject::valueGetter, WasmGlobalObject::valueSetter, 0),
JS_PS_END
};
const JSFunctionSpec WasmGlobalObject::methods[] =
{
JS_SYM_FN(toPrimitive, WasmGlobalObject::valueGetter, 1, JSPROP_READONLY),
JS_FS_END
};
const JSFunctionSpec WasmGlobalObject::static_methods[] =
{ JS_FS_END };
ValType
WasmGlobalObject::type() const
{
return static_cast<ValType>(getReservedSlot(TYPE_SLOT).toInt32());
}
bool
WasmGlobalObject::isMutable() const
{
return getReservedSlot(MUTABLE_SLOT).toBoolean();
}
Value
WasmGlobalObject::value() const
{
return getReservedSlot(VALUE_SLOT);
}
#endif // ENABLE_WASM_GLOBAL
// ============================================================================
// WebAssembly class and static methods
@ -2717,6 +2950,9 @@ js::InitWebAssemblyClass(JSContext* cx, HandleObject obj)
return nullptr;
RootedObject moduleProto(cx), instanceProto(cx), memoryProto(cx), tableProto(cx);
#ifdef ENABLE_WASM_GLOBAL
RootedObject globalProto(cx);
#endif
if (!InitConstructor<WasmModuleObject>(cx, wasm, "Module", &moduleProto))
return nullptr;
if (!InitConstructor<WasmInstanceObject>(cx, wasm, "Instance", &instanceProto))
@ -2725,6 +2961,10 @@ js::InitWebAssemblyClass(JSContext* cx, HandleObject obj)
return nullptr;
if (!InitConstructor<WasmTableObject>(cx, wasm, "Table", &tableProto))
return nullptr;
#ifdef ENABLE_WASM_GLOBAL
if (!InitConstructor<WasmGlobalObject>(cx, wasm, "Global", &globalProto))
return nullptr;
#endif
if (!InitErrorClass(cx, wasm, "CompileError", JSEXN_WASMCOMPILEERROR))
return nullptr;
if (!InitErrorClass(cx, wasm, "LinkError", JSEXN_WASMLINKERROR))
@ -2744,6 +2984,9 @@ js::InitWebAssemblyClass(JSContext* cx, HandleObject obj)
global->setPrototype(JSProto_WasmInstance, ObjectValue(*instanceProto));
global->setPrototype(JSProto_WasmMemory, ObjectValue(*memoryProto));
global->setPrototype(JSProto_WasmTable, ObjectValue(*tableProto));
#ifdef ENABLE_WASM_GLOBAL
global->setPrototype(JSProto_WasmGlobal, ObjectValue(*globalProto));
#endif
global->setConstructor(JSProto_WebAssembly, ObjectValue(*wasm));
MOZ_ASSERT(global->isStandardClassResolved(JSProto_WebAssembly));

View File

@ -43,6 +43,15 @@ HasCompilerSupport(JSContext* cx);
bool
HasSupport(JSContext* cx);
// ToWebAssemblyValue and ToJSValue are conversion functions defined in
// the Wasm JS API spec.
bool
ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, Val* val);
void
ToJSValue(const Val& val, MutableHandleValue v);
// Compiles the given binary wasm module given the ArrayBufferObject
// and links the module's imports with the given import object.
@ -275,6 +284,42 @@ class WasmTableObject : public NativeObject
wasm::Table& table() const;
};
#ifdef ENABLE_WASM_GLOBAL
// The class of WebAssembly.Global. A WasmGlobalObject holds either the value
// of an immutable wasm global or the cell of a mutable wasm global.
class WasmGlobalObject : public NativeObject
{
static const unsigned TYPE_SLOT = 0;
static const unsigned MUTABLE_SLOT = 1;
static const unsigned VALUE_SLOT = 2;
static const ClassOps classOps_;
static bool valueGetterImpl(JSContext* cx, const CallArgs& args);
static bool valueGetter(JSContext* cx, unsigned argc, Value* vp);
static bool valueSetterImpl(JSContext* cx, const CallArgs& args);
static bool valueSetter(JSContext* cx, unsigned argc, Value* vp);
public:
static const unsigned RESERVED_SLOTS = 3;
static const Class class_;
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
static const JSFunctionSpec static_methods[];
static bool construct(JSContext*, unsigned, Value*);
static WasmGlobalObject* create(JSContext* cx, wasm::ValType type, bool isMutable,
HandleValue value);
wasm::ValType type() const;
bool isMutable() const;
Value value() const;
};
#endif // ENABLE_WASM_GLOBAL
} // namespace js
#endif // wasm_js_h

View File

@ -1052,30 +1052,21 @@ GetGlobalExport(JSContext* cx, const GlobalDescVector& globals, uint32_t globalI
}
}
switch (global.type()) {
case ValType::I32: {
jsval.set(Int32Value(val.i32()));
return true;
}
case ValType::I64: {
if (val.type() == ValType::I64) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
return false;
}
case ValType::F32: {
float f = val.f32();
jsval.set(DoubleValue(JS::CanonicalizeNaN(double(f))));
return true;
}
case ValType::F64: {
double d = val.f64();
jsval.set(DoubleValue(JS::CanonicalizeNaN(d)));
return true;
}
default: {
break;
}
}
MOZ_CRASH("unexpected type when creating global exports");
ToJSValue(val, jsval);
#ifdef ENABLE_WASM_GLOBAL
Rooted<WasmGlobalObject*> go(cx, WasmGlobalObject::create(cx, ValType::I32, false, jsval));
if (!go)
return false;
jsval.setObject(*go);
#endif
return true;
}
static bool

View File

@ -222,6 +222,15 @@ function get(instance, name) {
if (instance.isError())
return instance;
// Experimental API change. We try to export WebAssembly.Global instances,
// not primitive values. In that case the Number() cast is necessary here
// to convert the Global to a value: the harness examines types carefully
// and will not trigger the @@toPrimitive hook on Global, unlike most user
// code.
if (typeof WebAssembly.Global === "function")
return ValueResult(Number(instance.value.exports[name]));
return ValueResult(instance.value.exports[name]);
}