Commit b5757ce5 authored by Ben Smith's avatar Ben Smith Committed by Commit Bot

[wasm] Implement `WebAssembly.Global.value{,Of}`

See
https://webassembly.github.io/mutable-global/js-api/index.html#globals
for the current spec.

Bug: v8:7625

Change-Id: I70f567a9a0c6fc44c04c245ff496386941a699a9
Reviewed-on: https://chromium-review.googlesource.com/999168
Commit-Queue: Ben Smith <binji@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52494}
parent cd782a03
......@@ -49,6 +49,10 @@ class Memory {
return *reinterpret_cast<uintptr_t*>(addr);
}
static float& float_at(Address addr) {
return *reinterpret_cast<float*>(addr);
}
static double& double_at(Address addr) {
return *reinterpret_cast<double*>(addr);
}
......
......@@ -693,7 +693,9 @@ void WebAssemblyGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(args[0]);
// TODO(binji): handle descriptor's 'value'.
// The descriptor's 'value'.
v8::MaybeLocal<v8::Value> maybe_value =
descriptor->Get(context, v8_str(isolate, "value"));
// The descriptor's 'mutable'.
bool is_mutable = false;
......@@ -732,12 +734,56 @@ void WebAssemblyGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
}
uint32_t offset = 0;
i::Handle<i::JSObject> global_obj = i::WasmGlobalObject::New(
i_isolate, i::MaybeHandle<i::JSArrayBuffer>(), type, offset, is_mutable);
args.GetReturnValue().Set(Utils::ToLocal(global_obj));
const uint32_t offset = 0;
i::MaybeHandle<i::WasmGlobalObject> maybe_global_obj =
i::WasmGlobalObject::New(i_isolate, i::MaybeHandle<i::JSArrayBuffer>(),
type, offset, is_mutable);
i::Handle<i::WasmGlobalObject> global_obj;
if (!maybe_global_obj.ToHandle(&global_obj)) {
thrower.RangeError("could not allocate memory");
return;
}
// Convert value to a WebAssembly value.
v8::Local<v8::Value> value;
if (maybe_value.ToLocal(&value)) {
switch (type) {
case i::wasm::kWasmI32: {
int32_t i32_value = 0;
v8::Local<v8::Int32> int32_value;
if (!value->ToInt32(context).ToLocal(&int32_value)) return;
if (!int32_value->Int32Value(context).To(&i32_value)) return;
global_obj->SetI32(i32_value);
break;
}
case i::wasm::kWasmF32: {
double f64_value = 0;
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return;
if (!number_value->NumberValue(context).To(&f64_value)) return;
float f32_value = static_cast<float>(f64_value);
global_obj->SetF32(f32_value);
break;
}
case i::wasm::kWasmF64: {
double f64_value = 0;
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return;
if (!number_value->NumberValue(context).To(&f64_value)) return;
global_obj->SetF64(f64_value);
break;
}
default:
UNREACHABLE();
}
}
i::Handle<i::JSObject> global_js_object(global_obj);
args.GetReturnValue().Set(Utils::ToLocal(global_js_object));
}
constexpr const char* kName_WasmGlobalObject = "WebAssembly.Global";
constexpr const char* kName_WasmMemoryObject = "WebAssembly.Memory";
constexpr const char* kName_WasmInstanceObject = "WebAssembly.Instance";
constexpr const char* kName_WasmTableObject = "WebAssembly.Table";
......@@ -949,6 +995,83 @@ void WebAssemblyMemoryGetBuffer(
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(buffer));
}
void WebAssemblyGlobalGetValueCommon(
const v8::FunctionCallbackInfo<v8::Value>& args, const char* name) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, name);
EXTRACT_THIS(receiver, WasmGlobalObject);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
switch (receiver->type()) {
case i::wasm::kWasmI32:
return_value.Set(receiver->GetI32());
break;
case i::wasm::kWasmI64:
thrower.TypeError("Can't get the value of i64 WebAssembly.Global");
break;
case i::wasm::kWasmF32:
return_value.Set(receiver->GetF32());
break;
case i::wasm::kWasmF64:
return_value.Set(receiver->GetF64());
break;
default:
UNREACHABLE();
}
}
// WebAssembly.Global.valueOf() -> num
void WebAssemblyGlobalValueOf(const v8::FunctionCallbackInfo<v8::Value>& args) {
return WebAssemblyGlobalGetValueCommon(args, "WebAssembly.Global.valueOf()");
}
// get WebAssembly.Global.value -> num
void WebAssemblyGlobalGetValue(
const v8::FunctionCallbackInfo<v8::Value>& args) {
return WebAssemblyGlobalGetValueCommon(args, "get WebAssembly.Global.value");
}
// set WebAssembly.Global.value(num)
void WebAssemblyGlobalSetValue(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
ScheduledErrorThrower thrower(i_isolate, "set WebAssembly.Global.value");
EXTRACT_THIS(receiver, WasmGlobalObject);
switch (receiver->type()) {
case i::wasm::kWasmI32: {
int32_t i32_value = 0;
if (!args[0]->Int32Value(context).To(&i32_value)) return;
receiver->SetI32(i32_value);
break;
}
case i::wasm::kWasmI64:
thrower.TypeError("Can't set the value of i64 WebAssembly.Global");
break;
case i::wasm::kWasmF32: {
double f64_value = 0;
if (!args[0]->NumberValue(context).To(&f64_value)) return;
receiver->SetF32(static_cast<float>(f64_value));
break;
}
case i::wasm::kWasmF64: {
double f64_value = 0;
if (!args[0]->NumberValue(context).To(&f64_value)) return;
receiver->SetF64(f64_value);
break;
}
default:
UNREACHABLE();
}
}
} // namespace
// TODO(titzer): we use the API to create the function template because the
......@@ -963,28 +1086,37 @@ static i::Handle<i::FunctionTemplateInfo> NewTemplate(i::Isolate* i_isolate,
namespace internal {
Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback func,
int length = 0) {
Handle<String> name = v8_str(isolate, str);
Handle<JSFunction> CreateFunc(Isolate* isolate, Handle<String> name,
FunctionCallback func) {
Handle<FunctionTemplateInfo> temp = NewTemplate(isolate, func);
Handle<JSFunction> function =
ApiNatives::InstantiateFunction(temp, name).ToHandleChecked();
DCHECK(function->shared()->HasSharedName());
return function;
}
Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback func,
int length = 0) {
Handle<String> name = v8_str(isolate, str);
Handle<JSFunction> function = CreateFunc(isolate, name, func);
function->shared()->set_length(length);
PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM);
JSObject::AddProperty(object, name, function, attributes);
return function;
}
Handle<String> GetterName(Isolate* isolate, Handle<String> name) {
return Name::ToFunctionName(name, isolate->factory()->get_string())
.ToHandleChecked();
}
void InstallGetter(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback func) {
Handle<String> name = v8_str(isolate, str);
Handle<FunctionTemplateInfo> temp = NewTemplate(isolate, func);
// TODO(ishell): shouldn't we set "get "+name as getter's name?
Handle<JSFunction> function =
ApiNatives::InstantiateFunction(temp).ToHandleChecked();
DCHECK(function->shared()->HasSharedName());
CreateFunc(isolate, GetterName(isolate, name), func);
v8::PropertyAttribute attributes =
static_cast<v8::PropertyAttribute>(v8::DontEnum);
Utils::ToLocal(object)->SetAccessorProperty(Utils::ToLocal(name),
......@@ -992,6 +1124,28 @@ void InstallGetter(Isolate* isolate, Handle<JSObject> object,
Local<Function>(), attributes);
}
Handle<String> SetterName(Isolate* isolate, Handle<String> name) {
return Name::ToFunctionName(name, isolate->factory()->set_string())
.ToHandleChecked();
}
void InstallGetterSetter(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback getter,
FunctionCallback setter) {
Handle<String> name = v8_str(isolate, str);
Handle<JSFunction> getter_func =
CreateFunc(isolate, GetterName(isolate, name), getter);
Handle<JSFunction> setter_func =
CreateFunc(isolate, SetterName(isolate, name), setter);
v8::PropertyAttribute attributes =
static_cast<v8::PropertyAttribute>(v8::DontEnum);
Utils::ToLocal(object)->SetAccessorProperty(
Utils::ToLocal(name), Utils::ToLocal(getter_func),
Utils::ToLocal(setter_func), attributes);
}
void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
Handle<JSGlobalObject> global = isolate->global_object();
Handle<Context> context(global->native_context(), isolate);
......@@ -1110,7 +1264,9 @@ void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
i::Handle<i::Map> global_map = isolate->factory()->NewMap(
i::WASM_GLOBAL_TYPE, WasmGlobalObject::kSize);
JSFunction::SetInitialMap(global_constructor, global_map, global_proto);
// TODO(binji): add other properties
InstallFunc(isolate, global_proto, "valueOf", WebAssemblyGlobalValueOf, 0);
InstallGetterSetter(isolate, global_proto, "value",
WebAssemblyGlobalGetValue, WebAssemblyGlobalSetValue);
JSObject::AddProperty(global_proto, factory->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Global"), ro_attributes);
}
......
......@@ -6,6 +6,7 @@
#define V8_WASM_WASM_OBJECTS_INL_H_
#include "src/heap/heap-inl.h"
#include "src/v8memory.h"
#include "src/wasm/wasm-objects.h"
namespace v8 {
......@@ -60,9 +61,44 @@ OPTIONAL_ACCESSORS(WasmMemoryObject, instances, FixedArrayOfWeakCells,
// WasmGlobalObject
ACCESSORS(WasmGlobalObject, array_buffer, JSArrayBuffer, kArrayBufferOffset)
SMI_ACCESSORS(WasmGlobalObject, type, kTypeOffset)
SMI_ACCESSORS(WasmGlobalObject, offset, kOffsetOffset)
SMI_ACCESSORS(WasmGlobalObject, is_mutable, kIsMutableOffset)
SMI_ACCESSORS(WasmGlobalObject, flags, kFlagsOffset)
BIT_FIELD_ACCESSORS(WasmGlobalObject, flags, type, WasmGlobalObject::TypeBits)
BIT_FIELD_ACCESSORS(WasmGlobalObject, flags, is_mutable,
WasmGlobalObject::IsMutableBit)
// static
uint32_t WasmGlobalObject::TypeSize(wasm::ValueType type) {
return 1U << ElementSizeLog2Of(type);
}
uint32_t WasmGlobalObject::type_size() const { return TypeSize(type()); }
Address WasmGlobalObject::address() const {
uint32_t buffer_size = 0;
DCHECK(array_buffer()->byte_length()->ToUint32(&buffer_size));
DCHECK(offset() + type_size() <= buffer_size);
USE(buffer_size);
return Address(array_buffer()->backing_store()) + offset();
}
int32_t WasmGlobalObject::GetI32() { return Memory::int32_at(address()); }
float WasmGlobalObject::GetF32() { return Memory::float_at(address()); }
double WasmGlobalObject::GetF64() { return Memory::double_at(address()); }
void WasmGlobalObject::SetI32(int32_t value) {
Memory::int32_at(address()) = value;
}
void WasmGlobalObject::SetF32(float value) {
Memory::float_at(address()) = value;
}
void WasmGlobalObject::SetF64(double value) {
Memory::double_at(address()) = value;
}
// WasmInstanceObject
PRIMITIVE_ACCESSORS(WasmInstanceObject, memory_start, byte*, kMemoryStartOffset)
......
......@@ -596,7 +596,7 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate,
}
// static
Handle<WasmGlobalObject> WasmGlobalObject::New(
MaybeHandle<WasmGlobalObject> WasmGlobalObject::New(
Isolate* isolate, MaybeHandle<JSArrayBuffer> maybe_buffer,
wasm::ValueType type, int32_t offset, bool is_mutable) {
Handle<JSFunction> global_ctor(
......@@ -604,12 +604,19 @@ Handle<WasmGlobalObject> WasmGlobalObject::New(
auto global_obj = Handle<WasmGlobalObject>::cast(
isolate->factory()->NewJSObject(global_ctor));
uint32_t type_size = 1 << ElementSizeLog2Of(type);
uint32_t type_size = TypeSize(type);
Handle<JSArrayBuffer> buffer;
if (!maybe_buffer.ToHandle(&buffer)) {
// If no buffer was provided, create one long enough for the given type.
buffer = wasm::SetupArrayBuffer(isolate, nullptr, type_size, false);
buffer =
isolate->factory()->NewJSArrayBuffer(SharedFlag::kNotShared, TENURED);
const bool initialize = true;
if (!JSArrayBuffer::SetupAllocatingData(buffer, isolate, type_size,
initialize)) {
return {};
}
}
// Check that the offset is in bounds.
......@@ -618,7 +625,8 @@ Handle<WasmGlobalObject> WasmGlobalObject::New(
CHECK(offset + type_size <= buffer_size);
global_obj->set_array_buffer(*buffer);
global_obj->set_type(static_cast<int>(type));
global_obj->set_flags(0);
global_obj->set_type(type);
global_obj->set_offset(offset);
global_obj->set_is_mutable(is_mutable);
......
......@@ -206,25 +206,50 @@ class WasmGlobalObject : public JSObject {
DECL_CAST(WasmGlobalObject)
DECL_ACCESSORS(array_buffer, JSArrayBuffer)
DECL_INT_ACCESSORS(type)
DECL_INT_ACCESSORS(offset)
DECL_INT_ACCESSORS(is_mutable)
DECL_INT32_ACCESSORS(offset)
DECL_INT_ACCESSORS(flags)
DECL_PRIMITIVE_ACCESSORS(type, wasm::ValueType)
DECL_BOOLEAN_ACCESSORS(is_mutable)
#define WASM_GLOBAL_OBJECT_FLAGS_BIT_FIELDS(V, _) \
V(TypeBits, wasm::ValueType, 8, _) \
V(IsMutableBit, bool, 1, _)
DEFINE_BIT_FIELDS(WASM_GLOBAL_OBJECT_FLAGS_BIT_FIELDS)
#undef WASM_GLOBAL_OBJECT_FLAGS_BIT_FIELDS
// Layout description.
#define WASM_GLOBAL_OBJECT_FIELDS(V) \
V(kArrayBufferOffset, kPointerSize) \
V(kTypeOffset, kPointerSize) \
V(kOffsetOffset, kPointerSize) \
V(kIsMutableOffset, kPointerSize) \
V(kFlagsOffset, kPointerSize) \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize,
WASM_GLOBAL_OBJECT_FIELDS)
#undef WASM_GLOBAL_OBJECT_FIELDS
V8_EXPORT_PRIVATE static Handle<WasmGlobalObject> New(
V8_EXPORT_PRIVATE static MaybeHandle<WasmGlobalObject> New(
Isolate* isolate, MaybeHandle<JSArrayBuffer> buffer, wasm::ValueType type,
int32_t offset, bool is_mutable);
static inline uint32_t TypeSize(wasm::ValueType);
inline uint32_t type_size() const;
inline int32_t GetI32();
inline float GetF32();
inline double GetF64();
inline void SetI32(int32_t value);
inline void SetF32(float value);
inline void SetF64(double value);
private:
// This function returns the address of the global's data in the
// JSArrayBuffer. This buffer may be allocated on-heap, in which case it may
// not have a fixed address.
inline Address address() const;
};
// A WebAssembly.Instance JavaScript-level object.
......
......@@ -28,3 +28,273 @@ function assertGlobalIsValid(global) {
assertGlobalIsValid(new WebAssembly.Global({type}));
}
})();
// Copied from //src/v8/test/cctest/compiler/value-helper.h
const u32_values = [
0x00000000, 0x00000001, 0xFFFFFFFF, 0x1B09788B, 0x04C5FCE8, 0xCC0DE5BF,
// This row is useful for testing lea optimizations on intel.
0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000008, 0x00000009,
0x273A798E, 0x187937A3, 0xECE3AF83, 0x5495A16B, 0x0B668ECC, 0x11223344,
0x0000009E, 0x00000043, 0x0000AF73, 0x0000116B, 0x00658ECC, 0x002B3B4C,
0x88776655, 0x70000000, 0x07200000, 0x7FFFFFFF, 0x56123761, 0x7FFFFF00,
0x761C4761, 0x80000000, 0x88888888, 0xA0000000, 0xDDDDDDDD, 0xE0000000,
0xEEEEEEEE, 0xFFFFFFFD, 0xF0000000, 0x007FFFFF, 0x003FFFFF, 0x001FFFFF,
0x000FFFFF, 0x0007FFFF, 0x0003FFFF, 0x0001FFFF, 0x0000FFFF, 0x00007FFF,
0x00003FFF, 0x00001FFF, 0x00000FFF, 0x000007FF, 0x000003FF, 0x000001FF,
// Bit pattern of a quiet NaN and signaling NaN, with or without
// additional payload.
0x7FC00000, 0x7F800000, 0x7FFFFFFF, 0x7F876543
];
const f32_values = [
-Infinity,
-2.70497e+38,
-1.4698e+37,
-1.22813e+35,
-1.20555e+35,
-1.34584e+34,
-1.0079e+32,
-6.49364e+26,
-3.06077e+25,
-1.46821e+25,
-1.17658e+23,
-1.9617e+22,
-2.7357e+20,
-9223372036854775808.0, // INT64_MIN
-1.48708e+13,
-1.89633e+12,
-4.66622e+11,
-2.22581e+11,
-1.45381e+10,
-2147483904.0, // First float32 after INT32_MIN
-2147483648.0, // INT32_MIN
-2147483520.0, // Last float32 before INT32_MIN
-1.3956e+09,
-1.32951e+09,
-1.30721e+09,
-1.19756e+09,
-9.26822e+08,
-6.35647e+08,
-4.00037e+08,
-1.81227e+08,
-5.09256e+07,
-964300.0,
-192446.0,
-28455.0,
-27194.0,
-26401.0,
-20575.0,
-17069.0,
-9167.0,
-960.178,
-113.0,
-62.0,
-15.0,
-7.0,
-1.0,
-0.0256635,
-4.60374e-07,
-3.63759e-10,
-4.30175e-14,
-5.27385e-15,
-1.5707963267948966,
-1.48084e-15,
-2.220446049250313e-16,
-1.05755e-19,
-3.2995e-21,
-1.67354e-23,
-1.11885e-23,
-1.78506e-30,
-5.07594e-31,
-3.65799e-31,
-1.43718e-34,
-1.27126e-38,
-0.0,
0.0,
1.17549e-38,
1.56657e-37,
4.08512e-29,
3.31357e-28,
6.25073e-22,
4.1723e-13,
1.44343e-09,
1.5707963267948966,
5.27004e-08,
9.48298e-08,
5.57888e-07,
4.89988e-05,
0.244326,
1.0,
12.4895,
19.0,
47.0,
106.0,
538.324,
564.536,
819.124,
7048.0,
12611.0,
19878.0,
20309.0,
797056.0,
1.77219e+09,
2147483648.0, // INT32_MAX + 1
4294967296.0, // UINT32_MAX + 1
1.51116e+11,
4.18193e+13,
3.59167e+16,
9223372036854775808.0, // INT64_MAX + 1
18446744073709551616.0, // UINT64_MAX + 1
3.38211e+19,
2.67488e+20,
1.78831e+21,
9.20914e+21,
8.35654e+23,
1.4495e+24,
5.94015e+25,
4.43608e+30,
2.44502e+33,
2.61152e+33,
1.38178e+37,
1.71306e+37,
3.31899e+38,
3.40282e+38,
Infinity,
NaN
];
const f64_values = [
-2e66,
-2.220446049250313e-16,
-9223373136366403584.0,
-9223372036854775808.0, // INT64_MIN
-2147483649.5,
-2147483648.25,
-2147483648.0,
-2147483647.875,
-2147483647.125,
-2147483647.0,
-999.75,
-2e66,
-1.75,
-1.5707963267948966,
-1.0,
-0.5,
-0.0,
0.0,
3e-88,
0.125,
0.25,
0.375,
0.5,
1.0,
1.17549e-38,
1.56657e-37,
1.0000001,
1.25,
1.5707963267948966,
2,
3.1e7,
5.125,
6.25,
888,
982983.25,
2147483647.0,
2147483647.375,
2147483647.75,
2147483648.0,
2147483648.25,
2147483649.25,
9223372036854775808.0, // INT64_MAX + 1
9223373136366403584.0,
18446744073709551616.0, // UINT64_MAX + 1
2e66,
Infinity,
-Infinity,
NaN
];
function GlobalI32(value) {
return new WebAssembly.Global({type: 'i32', value});
}
function GlobalF32(value) {
return new WebAssembly.Global({type: 'f32', value});
}
function GlobalF64(value) {
return new WebAssembly.Global({type: 'f64', value});
}
(function TestDefaultValue() {
assertSame(0, GlobalI32().value);
// TODO(binji): The spec currently requires the default value to be NaN, but
// I think we should change this to 0. See
// https://github.com/WebAssembly/mutable-global/issues/6
assertSame(NaN, GlobalF32().value);
assertSame(NaN, GlobalF64().value);
})();
(function TestValueOf() {
for (let u32_value of u32_values) {
let i32_value = u32_value | 0;
assertSame(i32_value, GlobalI32(u32_value).valueOf());
assertSame(i32_value, GlobalI32(i32_value).valueOf());
}
for (let f32_value of f32_values) {
assertSame(Math.fround(f32_value), GlobalF32(f32_value).valueOf());
}
for (let f64_value of f64_values) {
assertSame(f64_value, GlobalF64(f64_value).valueOf());
}
})();
(function TestGetValue() {
for (let u32_value of u32_values) {
let i32_value = u32_value | 0;
assertSame(i32_value, GlobalI32(u32_value).value);
assertSame(i32_value, GlobalI32(i32_value).value);
}
for (let f32_value of f32_values) {
assertSame(Math.fround(f32_value), GlobalF32(f32_value).value);
}
for (let f64_value of f64_values) {
assertSame(f64_value, GlobalF64(f64_value).value);
}
})();
(function TestSetValue() {
for (let u32_value of u32_values) {
let i32_value = u32_value | 0;
let global = GlobalI32();
global.value = u32_value;
assertSame(i32_value, global.value);
global.value = i32_value;
assertSame(i32_value, global.value);
}
for (let f32_value of f32_values) {
let global = GlobalF32();
global.value = f32_value;
assertSame(Math.fround(f32_value), global.value);
}
for (let f64_value of f64_values) {
let global = GlobalF64();
global.value = f64_value;
assertSame(f64_value, global.value);
}
})();
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