Commit 6a505606 authored by Ben Smith's avatar Ben Smith Committed by Commit Bot

[wasm] Implement WebAssembly.Global import/export

The mutable-globals proposal spec allows importing as Numbers or
WebAssembly.Global values, but always exports as WebAssembly.Global.

Since the value is always boxed, we can also import/export i64 values.

This CL also includes support for export of mutable globals. Since the
underlying ArrayBuffer that stores the global's value is shared between
the module and the WebAssembly.Global object, all that needs to be done
is remove the validation check.

Bug: v8:7625
Change-Id: I24d763e3bc193d229a7cc33b2f2690a473c6f2bc
Reviewed-on: https://chromium-review.googlesource.com/1018406
Commit-Queue: Ben Smith <binji@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52789}
parent 68f2e520
......@@ -33,6 +33,10 @@ class Memory {
return *reinterpret_cast<uint64_t*>(addr);
}
static int64_t& int64_at(Address addr) {
return *reinterpret_cast<int64_t*>(addr);
}
static int& int_at(Address addr) {
return *reinterpret_cast<int*>(addr);
}
......
......@@ -325,7 +325,8 @@ class InstanceBuilder {
// Load data segments into the memory.
void LoadDataSegments(Handle<WasmInstanceObject> instance);
void WriteGlobalValue(WasmGlobal& global, Handle<Object> value);
void WriteGlobalValue(WasmGlobal& global, double value);
void WriteGlobalValue(WasmGlobal& global, Handle<WasmGlobalObject> value);
void SanitizeImports();
......@@ -2010,9 +2011,7 @@ void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) {
}
}
void InstanceBuilder::WriteGlobalValue(WasmGlobal& global,
Handle<Object> value) {
double num = value->Number();
void InstanceBuilder::WriteGlobalValue(WasmGlobal& global, double num) {
TRACE("init [globals_start=%p + %u] = %lf, type = %s\n",
reinterpret_cast<void*>(raw_buffer_ptr(globals_, 0)), global.offset,
num, ValueTypes::TypeName(global.type));
......@@ -2035,6 +2034,42 @@ void InstanceBuilder::WriteGlobalValue(WasmGlobal& global,
}
}
void InstanceBuilder::WriteGlobalValue(WasmGlobal& global,
Handle<WasmGlobalObject> value) {
TRACE("init [globals_start=%p + %u] = ",
reinterpret_cast<void*>(raw_buffer_ptr(globals_, 0)), global.offset);
switch (global.type) {
case kWasmI32: {
int32_t num = value->GetI32();
*GetRawGlobalPtr<int32_t>(global) = num;
TRACE("%d", num);
break;
}
case kWasmI64: {
int64_t num = value->GetI64();
*GetRawGlobalPtr<int64_t>(global) = num;
TRACE("%" PRId64, num);
break;
}
case kWasmF32: {
float num = value->GetF32();
*GetRawGlobalPtr<float>(global) = num;
TRACE("%f", num);
break;
}
case kWasmF64: {
double num = value->GetF64();
*GetRawGlobalPtr<double>(global) = num;
TRACE("%lf", num);
break;
}
default:
UNREACHABLE();
}
TRACE(", type = %s (from WebAssembly.Global)\n",
ValueTypes::TypeName(global.type));
}
void InstanceBuilder::SanitizeImports() {
Handle<SeqOneByteString> module_bytes(
module_object_->compiled_module()->shared()->module_bytes());
......@@ -2268,7 +2303,12 @@ int InstanceBuilder::ProcessImports(Handle<WasmInstanceObject> instance) {
case kExternalGlobal: {
// Global imports are converted to numbers and written into the
// {globals_} array buffer.
if (module_->globals[import.index].type == kWasmI64) {
WasmGlobal& global = module_->globals[import.index];
// The mutable-global proposal allows importing i64 values, but only if
// they are passed as a WebAssembly.Global object.
if (global.type == kWasmI64 && !(FLAG_experimental_wasm_mut_global &&
value->IsWasmGlobalObject())) {
ReportLinkError("global import cannot have type i64", index,
module_name, import_name);
return -1;
......@@ -2282,19 +2322,28 @@ int InstanceBuilder::ProcessImports(Handle<WasmInstanceObject> instance) {
// or friends are patched, we might need to check for that as well.
if (value->IsJSFunction()) value = isolate_->factory()->nan_value();
if (value->IsPrimitive() && !value->IsSymbol()) {
if (module_->globals[import.index].type == kWasmI32) {
if (global.type == kWasmI32) {
value = Object::ToInt32(isolate_, value).ToHandleChecked();
} else {
value = Object::ToNumber(value).ToHandleChecked();
}
}
}
if (!value->IsNumber()) {
if (FLAG_experimental_wasm_mut_global && value->IsWasmGlobalObject()) {
auto global_object = Handle<WasmGlobalObject>::cast(value);
if (global_object->type() != global.type) {
ReportLinkError("imported global does not match the expected type",
index, module_name, import_name);
return -1;
}
WriteGlobalValue(global, global_object);
} else if (value->IsNumber()) {
WriteGlobalValue(global, value->Number());
} else {
ReportLinkError("global import must be a number", index, module_name,
import_name);
return -1;
}
WriteGlobalValue(module_->globals[import.index], value);
break;
}
default:
......@@ -2498,27 +2547,40 @@ void InstanceBuilder::ProcessExports(
break;
}
case kExternalGlobal: {
// Export the value of the global variable as a number.
WasmGlobal& global = module_->globals[exp.index];
double num = 0;
switch (global.type) {
case kWasmI32:
num = *GetRawGlobalPtr<int32_t>(global);
break;
case kWasmF32:
num = *GetRawGlobalPtr<float>(global);
break;
case kWasmF64:
num = *GetRawGlobalPtr<double>(global);
break;
case kWasmI64:
thrower_->LinkError(
"export of globals of type I64 is not allowed.");
return;
default:
UNREACHABLE();
if (FLAG_experimental_wasm_mut_global) {
const bool is_mutable = false;
Handle<JSArrayBuffer> globals_buffer(instance->globals_buffer(),
isolate_);
// Since the global's array buffer is always provided, allocation
// should never fail.
Handle<WasmGlobalObject> global_obj =
WasmGlobalObject::New(isolate_, globals_buffer, global.type,
global.offset, is_mutable)
.ToHandleChecked();
desc.set_value(global_obj);
} else {
// Export the value of the global variable as a number.
double num = 0;
switch (global.type) {
case kWasmI32:
num = *GetRawGlobalPtr<int32_t>(global);
break;
case kWasmF32:
num = *GetRawGlobalPtr<float>(global);
break;
case kWasmF64:
num = *GetRawGlobalPtr<double>(global);
break;
case kWasmI64:
thrower_->LinkError(
"export of globals of type I64 is not allowed.");
return;
default:
UNREACHABLE();
}
desc.set_value(isolate_->factory()->NewNumber(num));
}
desc.set_value(isolate_->factory()->NewNumber(num));
break;
}
default:
......
......@@ -625,7 +625,7 @@ class ModuleDecoderImpl : public Decoder {
WasmGlobal* global = nullptr;
exp->index = consume_global_index(module_.get(), &global);
if (global) {
if (global->mutability) {
if (!FLAG_experimental_wasm_mut_global && global->mutability) {
error("mutable globals cannot be exported");
}
global->exported = true;
......
......@@ -84,6 +84,8 @@ Address WasmGlobalObject::address() const {
int32_t WasmGlobalObject::GetI32() { return Memory::int32_at(address()); }
int64_t WasmGlobalObject::GetI64() { return Memory::int64_at(address()); }
float WasmGlobalObject::GetF32() { return Memory::float_at(address()); }
double WasmGlobalObject::GetF64() { return Memory::double_at(address()); }
......@@ -92,6 +94,10 @@ void WasmGlobalObject::SetI32(int32_t value) {
Memory::int32_at(address()) = value;
}
void WasmGlobalObject::SetI64(int64_t value) {
Memory::int64_at(address()) = value;
}
void WasmGlobalObject::SetF32(float value) {
Memory::float_at(address()) = value;
}
......
......@@ -240,10 +240,12 @@ class WasmGlobalObject : public JSObject {
inline uint32_t type_size() const;
inline int32_t GetI32();
inline int64_t GetI64();
inline float GetF32();
inline double GetF64();
inline void SetI32(int32_t value);
inline void SetI64(int64_t value);
inline void SetF32(float value);
inline void SetF64(double value);
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-mut-global
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
(function exportImmutableGlobal() {
var builder = new WasmModuleBuilder();
let globals = [
[kWasmI32, 'i32_noinit'],
[kWasmI32, 'i32', 4711],
[kWasmF32, 'f32_noinit'],
[kWasmF32, 'f32', Math.fround(3.14)],
[kWasmF64, 'f64_noinit'],
[kWasmF64, 'f64', 1 / 7]
];
for (let [type, name, value] of globals) {
let global_builder = builder.addGlobal(type, false).exportAs(name);
if (value) global_builder.init = value;
}
var module = builder.instantiate();
for (let [type, name, value] of globals) {
let obj = module.exports[name];
assertEquals("object", typeof obj, name);
assertTrue(obj instanceof WebAssembly.Global, name);
assertEquals(value || 0, obj.value, name);
}
})();
(function canExportI64Global() {
var builder = new WasmModuleBuilder();
builder.addGlobal(kWasmI64, false).exportAs('g');
builder.instantiate();
})();
(function canExportAndImportI64() {
var builder = new WasmModuleBuilder();
builder.addGlobal(kWasmI64, false).exportAs('g');
let g = builder.instantiate().exports.g;
builder = new WasmModuleBuilder();
builder.addImportedGlobal("mod", "g", kWasmI64);
builder.instantiate({mod: {g: g}});
})();
(function exportMutableGlobal() {
var builder = new WasmModuleBuilder();
let globals = [
[kWasmI32, 'i32_noinit'], // -
[kWasmI32, 'i32', 4711], // -
[kWasmF32, 'f32_noinit'], // -
[kWasmF32, 'f32', Math.fround(3.14)], // -
[kWasmF64, 'f64_noinit'], // -
[kWasmF64, 'f64', 1 / 7] // -
];
for (let [index, [type, name, value]] of globals.entries()) {
let global_builder = builder.addGlobal(type, true).exportAs(name);
if (value) global_builder.init = value;
builder.addFunction("get " + name, makeSig([], [type]))
.addBody([kExprGetGlobal, index])
.exportFunc();
builder.addFunction("set " + name, makeSig([type], []))
.addBody([kExprGetLocal, 0, kExprSetGlobal, index])
.exportFunc();
}
var module = builder.instantiate();
for (let [type, name, value] of globals) {
let obj = module.exports[name];
assertEquals(value || 0, obj.value, name);
// Changing the exported global should change the module's global.
obj.value = 1001;
assertEquals(1001, module.exports['get ' + name](), name);
// Changing the module's global should change the exported global.
module.exports['set ' + name](112358);
assertEquals(112358, obj.value, name);
}
})();
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-mut-global
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
(function TestBasic() {
let global = new WebAssembly.Global({type: 'i32', value: 1});
let builder = new WasmModuleBuilder();
builder.addImportedGlobal("mod", "g", kWasmI32);
builder.addFunction("main", kSig_i_v)
.addBody([kExprGetGlobal, 0])
.exportAs("main");
let main = builder.instantiate({mod: {g: global}}).exports.main;
assertEquals(1, main());
})();
(function TestTypeMismatch() {
let global = new WebAssembly.Global({type: 'f32', value: 1});
let builder = new WasmModuleBuilder();
builder.addImportedGlobal("mod", "g", kWasmI32);
builder.addFunction("main", kSig_i_v)
.addBody([kExprGetGlobal, 0])
.exportAs("main");
assertThrows(() => builder.instantiate({mod: {g: global}}));
})();
(function TestImportI64AsNumber() {
let builder = new WasmModuleBuilder();
builder.addImportedGlobal("mod", "g", kWasmI64);
assertThrows(() => builder.instantiate({mod: {g: 1234}}));
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment