Commit ef2f33d6 authored by gdeepti's avatar gdeepti Committed by Commit bot

Implement Wasm GrowMemory opcode as a wasm runtime call

  - GrowMemory runtime function, tests added to checks if memory can be grown
  and relocation information is updated correctly

R=titzer@chromium.org, bradnelson@chromium.org

Review-Url: https://codereview.chromium.org/2051043002
Cr-Commit-Position: refs/heads/master@{#37338}
parent 3325de6d
......@@ -1408,6 +1408,7 @@ v8_source_set("v8_base") {
"src/runtime/runtime-test.cc",
"src/runtime/runtime-typedarray.cc",
"src/runtime/runtime-utils.h",
"src/runtime/runtime-wasm.cc",
"src/runtime/runtime.cc",
"src/runtime/runtime.h",
"src/safepoint-table.cc",
......
......@@ -881,6 +881,8 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
return BuildI64UConvertF32(input, position);
case wasm::kExprI64UConvertF64:
return BuildI64UConvertF64(input, position);
case wasm::kExprGrowMemory:
return BuildGrowMemory(input);
case wasm::kExprI32AsmjsLoadMem8S:
return BuildAsmjsLoadMem(MachineType::Int8(), input);
case wasm::kExprI32AsmjsLoadMem8U:
......@@ -1571,6 +1573,32 @@ Node* WasmGraphBuilder::BuildFloatToIntConversionInstruction(
return load;
}
Node* WasmGraphBuilder::BuildGrowMemory(Node* input) {
Runtime::FunctionId function_id = Runtime::kWasmGrowMemory;
const Runtime::Function* function = Runtime::FunctionForId(function_id);
CallDescriptor* desc = Linkage::GetRuntimeCallDescriptor(
jsgraph()->zone(), function_id, function->nargs, Operator::kNoProperties,
CallDescriptor::kNoFlags);
Node** control_ptr = control_;
Node** effect_ptr = effect_;
wasm::ModuleEnv* module = module_;
input = BuildChangeUint32ToSmi(input);
Node* inputs[] = {
jsgraph()->CEntryStubConstant(function->result_size), input, // C entry
jsgraph()->ExternalConstant(
ExternalReference(function_id, jsgraph()->isolate())), // ref
jsgraph()->Int32Constant(function->nargs), // arity
jsgraph()->HeapConstant(module->instance->context), // context
*effect_ptr,
*control_ptr};
Node* node = graph()->NewNode(jsgraph()->common()->Call(desc),
static_cast<int>(arraysize(inputs)), inputs);
*control_ptr = node;
*effect_ptr = node;
node = BuildChangeSmiToInt32(node);
return node;
}
Node* WasmGraphBuilder::BuildI32DivS(Node* left, Node* right,
wasm::WasmCodePosition position) {
MachineOperatorBuilder* m = jsgraph()->machine();
......@@ -2283,6 +2311,15 @@ Node* WasmGraphBuilder::BuildChangeSmiToInt32(Node* value) {
return value;
}
Node* WasmGraphBuilder::BuildChangeUint32ToSmi(Node* value) {
if (jsgraph()->machine()->Is64()) {
value =
graph()->NewNode(jsgraph()->machine()->ChangeUint32ToUint64(), value);
}
return graph()->NewNode(jsgraph()->machine()->WordShl(), value,
BuildSmiShiftBitsConstant());
}
Node* WasmGraphBuilder::BuildChangeSmiToFloat64(Node* value) {
return graph()->NewNode(jsgraph()->machine()->ChangeInt32ToFloat64(),
BuildChangeSmiToInt32(value));
......@@ -2588,7 +2625,6 @@ void WasmGraphBuilder::BoundsCheckMem(MachineType memtype, Node* index,
jsgraph()->RelocatableInt32Constant(
static_cast<uint32_t>(effective_size),
RelocInfo::WASM_MEMORY_SIZE_REFERENCE));
trap_->AddTrapIfFalse(wasm::kTrapMemOutOfBounds, cond, position);
}
......
......@@ -317,6 +317,7 @@ class WasmGraphBuilder {
Node* BuildChangeInt32ToSmi(Node* value);
Node* BuildChangeSmiToInt32(Node* value);
Node* BuildChangeUint32ToSmi(Node* value);
Node* BuildChangeSmiToFloat64(Node* value);
Node* BuildTestNotSmi(Node* value);
Node* BuildSmiShiftBitsConstant();
......@@ -324,6 +325,7 @@ class WasmGraphBuilder {
Node* BuildAllocateHeapNumberWithValue(Node* value, Node* control);
Node* BuildLoadHeapNumberValue(Node* value, Node* control);
Node* BuildHeapNumberValueIndexConstant();
Node* BuildGrowMemory(Node* input);
// Asm.js specific functionality.
Node* BuildI32AsmjsSConvertF32(Node* input);
......
......@@ -489,7 +489,8 @@ class CallSite {
T(WasmTrapRemByZero, "remainder by zero") \
T(WasmTrapFloatUnrepresentable, "integer result unrepresentable") \
T(WasmTrapFuncInvalid, "invalid function") \
T(WasmTrapFuncSigMismatch, "function signature mismatch")
T(WasmTrapFuncSigMismatch, "function signature mismatch") \
T(WasmTrapMemAllocationFail, "failed to allocate memory")
class MessageTemplate {
public:
......
// Copyright 2016 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.
#include "src/runtime/runtime-utils.h"
#include "src/arguments.h"
#include "src/assembler.h"
#include "src/conversions.h"
#include "src/debug/debug.h"
#include "src/factory.h"
#include "src/frames-inl.h"
#include "src/objects-inl.h"
#include "src/v8memory.h"
#include "src/wasm/wasm-module.h"
namespace v8 {
namespace internal {
RUNTIME_FUNCTION(Runtime_WasmGrowMemory) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
uint32_t delta_pages = 0;
RUNTIME_ASSERT(args[0]->ToUint32(&delta_pages));
// Get the module JSObject
const Address entry = Isolate::c_entry_fp(isolate->thread_local_top());
Address pc =
Memory::Address_at(entry + StandardFrameConstants::kCallerPCOffset);
Code* code = isolate->inner_pointer_to_code_cache()->GetCacheEntry(pc)->code;
FixedArray* deopt_data = code->deoptimization_data();
DCHECK(deopt_data->length() == 2);
JSObject* module_object = JSObject::cast(deopt_data->get(0));
RUNTIME_ASSERT(!module_object->IsNull(isolate));
Address old_mem_start, new_mem_start;
uint32_t old_size, new_size;
const int kWasmMemArrayBuffer = 2;
// Get mem buffer associated with module object
Object* obj = module_object->GetInternalField(kWasmMemArrayBuffer);
Handle<JSArrayBuffer> old_buffer =
Handle<JSArrayBuffer>(JSArrayBuffer::cast(obj));
if (old_buffer->byte_length()->Number() == 0) {
// If module object does not have linear memory associated with it,
// Allocate new array buffer of given size.
old_mem_start = static_cast<Address>(old_buffer->backing_store());
old_size = 0;
// TODO(gdeepti): Fix bounds check to take into account size of memtype.
new_size = delta_pages * wasm::WasmModule::kPageSize;
if (delta_pages > wasm::WasmModule::kMaxMemPages) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kWasmTrapMemOutOfBounds));
}
new_mem_start =
static_cast<Address>(isolate->array_buffer_allocator()->Allocate(
static_cast<uint32_t>(new_size)));
if (new_mem_start == NULL) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kWasmTrapMemAllocationFail));
}
#if DEBUG
// Double check the API allocator actually zero-initialized the memory.
for (size_t i = old_size; i < new_size; i++) {
DCHECK_EQ(0, new_mem_start[i]);
}
#endif
} else {
old_mem_start = static_cast<Address>(old_buffer->backing_store());
old_size = old_buffer->byte_length()->Number();
new_size = old_size + delta_pages * wasm::WasmModule::kPageSize;
if (new_size >
wasm::WasmModule::kMaxMemPages * wasm::WasmModule::kPageSize) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kWasmTrapMemOutOfBounds));
}
new_mem_start = static_cast<Address>(realloc(old_mem_start, new_size));
if (new_mem_start == NULL) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kWasmTrapMemAllocationFail));
}
old_buffer->set_is_external(true);
isolate->heap()->UnregisterArrayBuffer(*old_buffer);
// Zero initializing uninitialized memory from realloc
memset(new_mem_start + old_size, 0, new_size - old_size);
}
Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer();
JSArrayBuffer::Setup(buffer, isolate, false, new_mem_start, new_size);
buffer->set_is_neuterable(false);
// Set new buffer to be wasm memory
module_object->SetInternalField(kWasmMemArrayBuffer, *buffer);
RUNTIME_ASSERT(wasm::UpdateWasmModuleMemory(
module_object, old_mem_start, new_mem_start, old_size, new_size));
return *isolate->factory()->NewNumberFromUint(old_size /
wasm::WasmModule::kPageSize);
}
} // namespace internal
} // namespace v8
......@@ -926,6 +926,7 @@ namespace internal {
F(DataViewSetFloat32, 4, 1) \
F(DataViewSetFloat64, 4, 1)
#define FOR_EACH_INTRINSIC_WASM(F) F(WasmGrowMemory, 1, 1)
#define FOR_EACH_INTRINSIC_RETURN_PAIR(F) \
F(LoadLookupSlotForCall, 1, 2)
......@@ -989,7 +990,8 @@ namespace internal {
FOR_EACH_INTRINSIC_STRINGS(F) \
FOR_EACH_INTRINSIC_SYMBOL(F) \
FOR_EACH_INTRINSIC_TEST(F) \
FOR_EACH_INTRINSIC_TYPEDARRAY(F)
FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
FOR_EACH_INTRINSIC_WASM(F)
// FOR_EACH_INTRINSIC defines the list of all intrinsics, coming in 2 flavors,
// either returning an object or a pair.
......
......@@ -1079,6 +1079,7 @@
'runtime/runtime-test.cc',
'runtime/runtime-typedarray.cc',
'runtime/runtime-utils.h',
'runtime/runtime-wasm.cc',
'runtime/runtime.cc',
'runtime/runtime.h',
'safepoint-table.cc',
......
......@@ -331,6 +331,7 @@ class WasmDecoder : public Decoder {
FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE)
FOREACH_MISC_MEM_OPCODE(DECLARE_OPCODE_CASE)
FOREACH_SIMPLE_OPCODE(DECLARE_OPCODE_CASE)
FOREACH_SIMPLE_MEM_OPCODE(DECLARE_OPCODE_CASE)
FOREACH_ASMJS_COMPAT_OPCODE(DECLARE_OPCODE_CASE)
FOREACH_SIMD_OPCODE(DECLARE_OPCODE_CASE)
#undef DECLARE_OPCODE_CASE
......@@ -995,12 +996,6 @@ class SR_WasmDecoder : public WasmDecoder {
case kExprMemorySize:
Push(kAstI32, BUILD(MemSize, 0));
break;
case kExprGrowMemory: {
Value val = Pop(0, kAstI32);
USE(val); // TODO(titzer): build node for grow memory
Push(kAstI32, BUILD(Int32Constant, 0));
break;
}
case kExprCallFunction: {
CallFunctionOperand operand(this, pc_);
if (Validate(pc_, operand)) {
......
......@@ -1026,6 +1026,37 @@ WasmDebugInfo* GetDebugInfo(JSObject* wasm) {
return *new_info;
}
bool UpdateWasmModuleMemory(JSObject* object, Address old_start,
Address new_start, uint32_t old_size,
uint32_t new_size) {
if (!IsWasmObject(object)) {
return false;
}
// Get code table associated with the module js_object
Object* obj = object->GetInternalField(kWasmModuleCodeTable);
Handle<FixedArray> code_table(FixedArray::cast(obj));
// Iterate through the code objects in the code table and update relocation
// information
for (int i = 0; i < code_table->length(); i++) {
obj = code_table->get(i);
Handle<Code> code(Code::cast(obj));
int mode_mask = RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_REFERENCE) |
RelocInfo::ModeMask(RelocInfo::WASM_MEMORY_SIZE_REFERENCE);
for (RelocIterator it(*code, mode_mask); !it.done(); it.next()) {
RelocInfo::Mode mode = it.rinfo()->rmode();
if (RelocInfo::IsWasmMemoryReference(mode) ||
RelocInfo::IsWasmMemorySizeReference(mode)) {
it.rinfo()->update_wasm_memory_reference(old_start, new_start, old_size,
new_size);
}
}
}
return true;
}
namespace testing {
int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
......
......@@ -361,6 +361,11 @@ WasmDebugInfo* GetDebugInfo(JSObject* wasm);
// else.
bool IsWasmObject(Object* object);
// Update memory references of code objects associated with the module
bool UpdateWasmModuleMemory(JSObject* object, Address old_start,
Address new_start, uint32_t old_size,
uint32_t new_size);
namespace testing {
// Decode, verify, and run the function labeled "main" in the
......@@ -369,7 +374,6 @@ int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
const byte* module_end, bool asm_js = false);
} // namespace testing
} // namespace wasm
} // namespace internal
} // namespace v8
......
......@@ -75,6 +75,7 @@ static void InitSigTable() {
#define SET_SIG_TABLE(name, opcode, sig) \
kSimpleExprSigTable[opcode] = static_cast<int>(kSigEnum_##sig) + 1;
FOREACH_SIMPLE_OPCODE(SET_SIG_TABLE);
FOREACH_SIMPLE_MEM_OPCODE(SET_SIG_TABLE);
FOREACH_ASMJS_COMPAT_OPCODE(SET_SIG_TABLE);
#undef SET_SIG_TABLE
}
......
......@@ -116,9 +116,10 @@ const WasmCodePosition kNoCodePosition = -1;
V(F32StoreMem, 0x35, f_if) \
V(F64StoreMem, 0x36, d_id)
#define FOREACH_SIMPLE_MEM_OPCODE(V) V(GrowMemory, 0x39, i_i)
// Load memory expressions.
#define FOREACH_MISC_MEM_OPCODE(V) \
V(GrowMemory, 0x39, i_i) \
V(MemorySize, 0x3b, i_v)
// Expressions with signatures.
......@@ -410,6 +411,7 @@ const WasmCodePosition kNoCodePosition = -1;
FOREACH_CONTROL_OPCODE(V) \
FOREACH_MISC_OPCODE(V) \
FOREACH_SIMPLE_OPCODE(V) \
FOREACH_SIMPLE_MEM_OPCODE(V) \
FOREACH_STORE_MEM_OPCODE(V) \
FOREACH_LOAD_MEM_OPCODE(V) \
FOREACH_MISC_MEM_OPCODE(V) \
......@@ -468,14 +470,15 @@ enum WasmOpcode {
// The reason for a trap.
#define FOREACH_WASM_TRAPREASON(V) \
V(TrapUnreachable) \
V(TrapMemOutOfBounds) \
V(TrapDivByZero) \
V(TrapDivUnrepresentable) \
V(TrapRemByZero) \
V(TrapFloatUnrepresentable) \
V(TrapFuncInvalid) \
V(TrapFuncSigMismatch)
V(TrapUnreachable) \
V(TrapMemOutOfBounds) \
V(TrapDivByZero) \
V(TrapDivUnrepresentable) \
V(TrapRemByZero) \
V(TrapFloatUnrepresentable) \
V(TrapFuncInvalid) \
V(TrapFuncSigMismatch) \
V(TrapMemAllocationFail)
enum TrapReason {
#define DECLARE_ENUM(name) k##name,
......
......@@ -886,6 +886,11 @@
'es6/tail-call-megatest*': [SKIP],
}], # (ignition or ignition_turbofan) and msan
['system == windows and arch == ia32', {
# TODO(gdeepti): Flaky on multiple runs, windows nosnap crashes
'wasm/grow-memory': [SKIP],
}], # 'system == windows'
##############################################################################
['gcov_coverage', {
# Tests taking too long.
......
// Copyright 2016 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: --expose-wasm --expose-gc --stress-compaction
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
var kPageSize = 0x10000;
function genGrowMemoryBuilder() {
var builder = new WasmModuleBuilder();
builder.addFunction("grow_memory", kSig_i_i)
.addBody([kExprGetLocal, 0, kExprGrowMemory])
.exportFunc();
builder.addFunction("load", kSig_i_i)
.addBody([kExprGetLocal, 0, kExprI32LoadMem, 0, 0])
.exportFunc();
builder.addFunction("store", kSig_i_ii)
.addBody([kExprGetLocal, 0, kExprGetLocal, 1, kExprI32StoreMem, 0, 0])
.exportFunc();
return builder;
}
function testGrowMemoryReadWrite() {
var builder = genGrowMemoryBuilder();
builder.addMemory(1, 1, false);
var module = builder.instantiate();
var offset;
function peek() { return module.exports.load(offset); }
function poke(value) { return module.exports.store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
for(offset = 0; offset <= (kPageSize - 4); offset++) {
poke(20);
assertEquals(peek(), 20);
}
for (offset = kPageSize - 3; offset < kPageSize + 4; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertTraps(kTrapMemOutOfBounds, peek);
}
try {
assertEquals(growMem(3), 1);
} catch (e) {
assertEquals("object", typeof e);
assertEquals(e.message, kTrapMsgs[kTrapMemAllocationFail]);
return;
}
for (offset = kPageSize; offset <= 4*kPageSize -4; offset++) {
poke(20);
assertEquals(peek(), 20);
}
for (offset = 4*kPageSize - 3; offset < 4*kPageSize + 4; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertTraps(kTrapMemOutOfBounds, peek);
}
try {
assertEquals(growMem(15), 4);
} catch (e) {
assertEquals("object", typeof e);
assertEquals(e.message, kTrapMsgs[kTrapMemAllocationFail]);
return;
}
for (offset = 4*kPageSize - 3; offset <= 4*kPageSize + 4; offset++) {
poke(20);
assertEquals(peek(), 20);
}
for (offset = 19*kPageSize - 10; offset <= 19*kPageSize - 4; offset++) {
poke(20);
assertEquals(peek(), 20);
}
for (offset = 19*kPageSize - 3; offset < 19*kPageSize + 5; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertTraps(kTrapMemOutOfBounds, peek);
}
}
testGrowMemoryReadWrite();
function testGrowMemoryZeroInitialSize() {
var builder = genGrowMemoryBuilder();
var module = builder.instantiate();
var offset;
function peek() { return module.exports.load(offset); }
function poke(value) { return module.exports.store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
assertTraps(kTrapMemOutOfBounds, peek);
assertTraps(kTrapMemOutOfBounds, poke);
try {
assertEquals(growMem(1), 0);
} catch (e) {
assertEquals("object", typeof e);
assertEquals(e.message, kTrapMsgs[kTrapMemAllocationFail]);
return;
}
for(offset = 0; offset <= kPageSize - 4; offset++) {
poke(20);
assertEquals(peek(), 20);
}
//TODO(gdeepti): Fix tests with correct write boundaries
//when runtime function is fixed.
for(offset = kPageSize; offset <= kPageSize + 5; offset++) {
assertTraps(kTrapMemOutOfBounds, peek);
}
}
testGrowMemoryZeroInitialSize();
function testGrowMemoryTrapMaxPagesZeroInitialMemory() {
var builder = genGrowMemoryBuilder();
var module = builder.instantiate();
var maxPages = 16385;
function growMem() { return module.exports.grow_memory(maxPages); }
assertTraps(kTrapMemOutOfBounds, growMem);
}
testGrowMemoryTrapMaxPagesZeroInitialMemory();
function testGrowMemoryTrapMaxPages() {
var builder = genGrowMemoryBuilder();
builder.addMemory(1, 1, false);
var module = builder.instantiate();
var maxPages = 16384;
function growMem() { return module.exports.grow_memory(maxPages); }
assertTraps(kTrapMemOutOfBounds, growMem);
}
testGrowMemoryTrapMaxPages();
......@@ -312,6 +312,7 @@ var kTrapRemByZero = 4;
var kTrapFloatUnrepresentable = 5;
var kTrapFuncInvalid = 6;
var kTrapFuncSigMismatch = 7;
var kTrapMemAllocationFail = 8;
var kTrapMsgs = [
"unreachable",
......@@ -321,7 +322,8 @@ var kTrapMsgs = [
"remainder by zero",
"integer result unrepresentable",
"invalid function",
"function signature mismatch"
"function signature mismatch",
"failed to allocate memory"
];
function assertTraps(trap, code) {
......
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