From 352e408b0edd6e574d251281e395fcde92ec6898 Mon Sep 17 00:00:00 2001 From: "Ben L. Titzer" Date: Fri, 27 Jul 2018 15:31:12 +0200 Subject: [PATCH] [wasm] Support 4GiB memories in Liftoff Add codegen support for up to 4GiB memories in Liftoff code. This CL also adds three new mjsunit tests that stress large WASM memories (1, 2, and 4 GiB) and checks that accesses near these boundaries properly generate traps. Note there is still some trickiness around the setting of: 1.) the flag --wasm-max-mem-pages 2.) wasm-limits.h kSpecMaxWasmMemoryPages = 65536 3.) wasm-limits.h kV8MaxWasmMemoryPages = 32767 In particular, the allocation of memories is still limited to 3.) and the runtime flag can only lower this limit. The above means that the tests for 2GiB and 4GiB memories will silently OOM by design until 3.) is changed (though they currently pass with manual testing). I argue it is better to include these tests up front, since they will immediately trigger if their memory allocation succeeds. Therefore the plan is to lift the restriction on 3.) after removing all other other internal V8 limitations including array buffers and views. R=clemensh@chromium.org CC=mstarzinger@chromium.org BUG=v8:7881 Change-Id: I3205ac2daf5c9a84364c670a2c3ef2258e5649f6 Reviewed-on: https://chromium-review.googlesource.com/1151309 Commit-Queue: Ben Titzer Reviewed-by: Clemens Hammacher Cr-Commit-Position: refs/heads/master@{#54754} --- src/compiler/wasm-compiler.cc | 17 ++-- src/wasm/baseline/arm/liftoff-assembler-arm.h | 2 +- .../baseline/ia32/liftoff-assembler-ia32.h | 16 ++- src/wasm/baseline/liftoff-assembler.h | 8 ++ src/wasm/baseline/liftoff-compiler.cc | 43 ++++---- .../baseline/mips/liftoff-assembler-mips.h | 2 +- src/wasm/baseline/ppc/liftoff-assembler-ppc.h | 6 +- src/wasm/baseline/x64/liftoff-assembler-x64.h | 17 ++-- src/wasm/function-compiler.h | 16 +++ src/wasm/wasm-limits.h | 6 +- test/mjsunit/wasm/bounds-check-64bit.js | 5 +- test/mjsunit/wasm/memory_1gb_oob.js | 99 +++++++++++++++++++ test/mjsunit/wasm/memory_2gb_oob.js | 99 +++++++++++++++++++ test/mjsunit/wasm/memory_4gb_oob.js | 97 ++++++++++++++++++ 14 files changed, 381 insertions(+), 52 deletions(-) create mode 100644 test/mjsunit/wasm/memory_1gb_oob.js create mode 100644 test/mjsunit/wasm/memory_2gb_oob.js create mode 100644 test/mjsunit/wasm/memory_4gb_oob.js diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index 411ce70fbd..e9e4e7734f 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -3038,19 +3038,14 @@ Node* WasmGraphBuilder::BoundsCheckMem(uint8_t access_size, Node* index, return index; } - uint64_t min_size = - env_->module->initial_pages * uint64_t{wasm::kWasmPageSize}; - uint64_t max_size = - (env_->module->has_maximum_pages ? env_->module->maximum_pages - : wasm::kV8MaxWasmMemoryPages) * - uint64_t{wasm::kWasmPageSize}; - - if (access_size > max_size || offset > max_size - access_size) { + const bool statically_oob = access_size > env_->max_memory_size || + offset > env_->max_memory_size - access_size; + if (statically_oob) { // The access will be out of bounds, even for the largest memory. TrapIfEq32(wasm::kTrapMemOutOfBounds, Int32Constant(0), 0, position); return mcgraph()->IntPtrConstant(0); } - uint64_t end_offset = offset + access_size - 1; + uint64_t end_offset = uint64_t{offset} + access_size - 1u; Node* end_offset_node = IntPtrConstant(end_offset); // The accessed memory is [index + offset, index + end_offset]. @@ -3064,7 +3059,7 @@ Node* WasmGraphBuilder::BoundsCheckMem(uint8_t access_size, Node* index, auto m = mcgraph()->machine(); Node* mem_size = instance_cache_->mem_size; - if (end_offset >= min_size) { + if (end_offset >= env_->min_memory_size) { // The end offset is larger than the smallest memory. // Dynamically check the end offset against the dynamic memory size. Node* cond = graph()->NewNode(m->UintLessThan(), end_offset_node, mem_size); @@ -3075,7 +3070,7 @@ Node* WasmGraphBuilder::BoundsCheckMem(uint8_t access_size, Node* index, UintPtrMatcher match(index); if (match.HasValue()) { uintptr_t index_val = match.Value(); - if (index_val < min_size - end_offset) { + if (index_val < env_->min_memory_size - end_offset) { // The input index is a constant and everything is statically within // bounds of the smallest possible memory. return index; diff --git a/src/wasm/baseline/arm/liftoff-assembler-arm.h b/src/wasm/baseline/arm/liftoff-assembler-arm.h index ca55fe5d52..cab49f9170 100644 --- a/src/wasm/baseline/arm/liftoff-assembler-arm.h +++ b/src/wasm/baseline/arm/liftoff-assembler-arm.h @@ -249,7 +249,7 @@ bool LiftoffAssembler::emit_i64_remu(LiftoffRegister dst, LiftoffRegister lhs, } void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) { - UNREACHABLE(); + // This is a nop on arm. } bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode, diff --git a/src/wasm/baseline/ia32/liftoff-assembler-ia32.h b/src/wasm/baseline/ia32/liftoff-assembler-ia32.h index ae8c9e012f..2db3a6a034 100644 --- a/src/wasm/baseline/ia32/liftoff-assembler-ia32.h +++ b/src/wasm/baseline/ia32/liftoff-assembler-ia32.h @@ -227,9 +227,10 @@ void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr, // Wasm memory is limited to a size <2GB, so all offsets can be encoded as // immediate value (in 31 bits, interpreted as signed value). // If the offset is bigger, we always trap and this code is not reached. - DCHECK(is_uint31(offset_imm)); + // Note: We shouldn't have memories larger than 2GiB on 32-bit, but if we + // did, we encode {offset_im} as signed, and it will simply wrap around. Operand src_op = offset_reg == no_reg - ? Operand(src_addr, offset_imm) + ? Operand(src_addr, bit_cast(offset_imm)) : Operand(src_addr, offset_reg, times_1, offset_imm); if (protected_load_pc) *protected_load_pc = pc_offset(); @@ -278,10 +279,9 @@ void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr, break; case LoadType::kI64Load: { // Compute the operand for the load of the upper half. - DCHECK(is_uint31(offset_imm + 4)); Operand upper_src_op = offset_reg == no_reg - ? Operand(src_addr, offset_imm + 4) + ? Operand(src_addr, bit_cast(offset_imm + 4)) : Operand(src_addr, offset_reg, times_1, offset_imm + 4); // The high word has to be mov'ed first, such that this is the protected // instruction. The mov of the low word cannot segfault. @@ -308,9 +308,8 @@ void LiftoffAssembler::Store(Register dst_addr, Register offset_reg, // Wasm memory is limited to a size <2GB, so all offsets can be encoded as // immediate value (in 31 bits, interpreted as signed value). // If the offset is bigger, we always trap and this code is not reached. - DCHECK(is_uint31(offset_imm)); Operand dst_op = offset_reg == no_reg - ? Operand(dst_addr, offset_imm) + ? Operand(dst_addr, bit_cast(offset_imm)) : Operand(dst_addr, offset_reg, times_1, offset_imm); if (protected_store_pc) *protected_store_pc = pc_offset(); @@ -342,10 +341,9 @@ void LiftoffAssembler::Store(Register dst_addr, Register offset_reg, break; case StoreType::kI64Store: { // Compute the operand for the store of the upper half. - DCHECK(is_uint31(offset_imm + 4)); Operand upper_dst_op = offset_reg == no_reg - ? Operand(dst_addr, offset_imm + 4) + ? Operand(dst_addr, bit_cast(offset_imm + 4)) : Operand(dst_addr, offset_reg, times_1, offset_imm + 4); // The high word has to be mov'ed first, such that this is the protected // instruction. The mov of the low word cannot segfault. @@ -893,7 +891,7 @@ void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src, } void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) { - UNREACHABLE(); + // This is a nop on ia32. } void LiftoffAssembler::emit_f32_add(DoubleRegister dst, DoubleRegister lhs, diff --git a/src/wasm/baseline/liftoff-assembler.h b/src/wasm/baseline/liftoff-assembler.h index 822c620b82..cd8b12fd3a 100644 --- a/src/wasm/baseline/liftoff-assembler.h +++ b/src/wasm/baseline/liftoff-assembler.h @@ -448,6 +448,14 @@ class LiftoffAssembler : public TurboAssembler { emit_i32_add(dst, lhs, rhs); } } + inline void emit_ptrsize_sub(Register dst, Register lhs, Register rhs) { + if (kPointerSize == 8) { + emit_i64_sub(LiftoffRegister(dst), LiftoffRegister(lhs), + LiftoffRegister(rhs)); + } else { + emit_i32_sub(dst, lhs, rhs); + } + } // f32 binops. inline void emit_f32_add(DoubleRegister dst, DoubleRegister lhs, diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index 56b5df6660..59209fd847 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -138,11 +138,6 @@ class LiftoffCompiler { : descriptor_( GetLoweredCallDescriptor(compilation_zone, call_descriptor)), env_(env), - min_size_(uint64_t{env_->module->initial_pages} * wasm::kWasmPageSize), - max_size_(uint64_t{env_->module->has_maximum_pages - ? env_->module->maximum_pages - : wasm::kV8MaxWasmMemoryPages} * - wasm::kWasmPageSize), compilation_zone_(compilation_zone), safepoint_table_builder_(compilation_zone_) {} @@ -1321,15 +1316,15 @@ class LiftoffCompiler { // (a jump to the trap was generated then); return false otherwise. bool BoundsCheckMem(Decoder* decoder, uint32_t access_size, uint32_t offset, Register index, LiftoffRegList pinned) { - const bool statically_oob = - access_size > max_size_ || offset > max_size_ - access_size; + const bool statically_oob = access_size > env_->max_memory_size || + offset > env_->max_memory_size - access_size; if (!statically_oob && (FLAG_wasm_no_bounds_checks || env_->use_trap_handler)) { return false; } - // TODO(eholk): This adds protected instruction information for the jump + // TODO(wasm): This adds protected instruction information for the jump // instruction we are about to generate. It would be better to just not add // protected instruction info when the pc is 0. Label* trap_label = AddOutOfLineTrap( @@ -1348,7 +1343,7 @@ class LiftoffCompiler { DCHECK(!env_->use_trap_handler); DCHECK(!FLAG_wasm_no_bounds_checks); - uint32_t end_offset = offset + access_size - 1; + uint64_t end_offset = uint64_t{offset} + access_size - 1u; // If the end offset is larger than the smallest memory, dynamically check // the end offset against the actual memory size, which is not known at @@ -1356,19 +1351,30 @@ class LiftoffCompiler { LiftoffRegister end_offset_reg = pinned.set(__ GetUnusedRegister(kGpReg, pinned)); LiftoffRegister mem_size = __ GetUnusedRegister(kGpReg, pinned); - LOAD_INSTANCE_FIELD(mem_size, MemorySize, LoadType::kI32Load); - __ LoadConstant(end_offset_reg, WasmValue(end_offset)); - if (end_offset >= min_size_) { - __ emit_cond_jump(kUnsignedGreaterEqual, trap_label, kWasmI32, - end_offset_reg.gp(), mem_size.gp()); + LOAD_INSTANCE_FIELD(mem_size, MemorySize, kPointerLoadType); + + if (kPointerSize == 8) { + __ LoadConstant(end_offset_reg, WasmValue(end_offset)); + } else { + __ LoadConstant(end_offset_reg, + WasmValue(static_cast(end_offset))); + } + + if (end_offset >= env_->min_memory_size) { + __ emit_cond_jump(kUnsignedGreaterEqual, trap_label, + LiftoffAssembler::kWasmIntPtr, end_offset_reg.gp(), + mem_size.gp()); } // Just reuse the end_offset register for computing the effective size. LiftoffRegister effective_size_reg = end_offset_reg; - __ emit_i32_sub(effective_size_reg.gp(), mem_size.gp(), - end_offset_reg.gp()); + __ emit_ptrsize_sub(effective_size_reg.gp(), mem_size.gp(), + end_offset_reg.gp()); - __ emit_cond_jump(kUnsignedGreaterEqual, trap_label, kWasmI32, index, + __ emit_i32_to_intptr(index, index); + + __ emit_cond_jump(kUnsignedGreaterEqual, trap_label, + LiftoffAssembler::kWasmIntPtr, index, effective_size_reg.gp()); return false; } @@ -1797,9 +1803,6 @@ class LiftoffCompiler { LiftoffAssembler asm_; compiler::CallDescriptor* const descriptor_; ModuleEnv* const env_; - // {min_size_} and {max_size_} are cached values computed from the ModuleEnv. - const uint64_t min_size_; - const uint64_t max_size_; bool ok_ = true; std::vector out_of_line_code_; SourcePositionTableBuilder source_position_table_builder_; diff --git a/src/wasm/baseline/mips/liftoff-assembler-mips.h b/src/wasm/baseline/mips/liftoff-assembler-mips.h index d2ea65211b..319fc491d6 100644 --- a/src/wasm/baseline/mips/liftoff-assembler-mips.h +++ b/src/wasm/baseline/mips/liftoff-assembler-mips.h @@ -745,7 +745,7 @@ void LiftoffAssembler::emit_i64_shr(LiftoffRegister dst, LiftoffRegister src, } void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) { - UNREACHABLE(); + // This is a nop on mips32. } void LiftoffAssembler::emit_f32_neg(DoubleRegister dst, DoubleRegister src) { diff --git a/src/wasm/baseline/ppc/liftoff-assembler-ppc.h b/src/wasm/baseline/ppc/liftoff-assembler-ppc.h index a4bd20622e..a5bfed118f 100644 --- a/src/wasm/baseline/ppc/liftoff-assembler-ppc.h +++ b/src/wasm/baseline/ppc/liftoff-assembler-ppc.h @@ -258,7 +258,11 @@ bool LiftoffAssembler::emit_i64_remu(LiftoffRegister dst, LiftoffRegister lhs, } void LiftoffAssembler::emit_i32_to_intptr(Register dst, Register src) { - UNREACHABLE(); +#ifdef V8_TARGET_ARCH_PPC64 + BAILOUT("emit_i32_to_intptr"); +#else +// This is a nop on ppc32. +#endif } bool LiftoffAssembler::emit_type_conversion(WasmOpcode opcode, diff --git a/src/wasm/baseline/x64/liftoff-assembler-x64.h b/src/wasm/baseline/x64/liftoff-assembler-x64.h index 4628b0f7ed..49326687d5 100644 --- a/src/wasm/baseline/x64/liftoff-assembler-x64.h +++ b/src/wasm/baseline/x64/liftoff-assembler-x64.h @@ -50,12 +50,17 @@ inline Operand GetInstanceOperand() { return Operand(rbp, -16); } inline Operand GetMemOp(LiftoffAssembler* assm, Register addr, Register offset, uint32_t offset_imm, LiftoffRegList pinned) { - // Wasm memory is limited to a size <2GB, so all offsets can be encoded as - // immediate value (in 31 bits, interpreted as signed value). - // If the offset is bigger, we always trap and this code is not reached. - DCHECK(is_uint31(offset_imm)); - if (offset == no_reg) return Operand(addr, offset_imm); - return Operand(addr, offset, times_1, offset_imm); + if (is_uint31(offset_imm)) { + if (offset == no_reg) return Operand(addr, offset_imm); + return Operand(addr, offset, times_1, offset_imm); + } + // Offset immediate does not fit in 31 bits. + Register scratch = kScratchRegister; + assm->movl(scratch, Immediate(offset_imm)); + if (offset != no_reg) { + assm->addq(scratch, offset); + } + return Operand(addr, scratch, times_1, 0); } inline void Load(LiftoffAssembler* assm, LiftoffRegister dst, Operand src, diff --git a/src/wasm/function-compiler.h b/src/wasm/function-compiler.h index 4b55e12235..6d9740fd71 100644 --- a/src/wasm/function-compiler.h +++ b/src/wasm/function-compiler.h @@ -6,6 +6,8 @@ #define V8_WASM_FUNCTION_COMPILER_H_ #include "src/wasm/function-body-decoder.h" +#include "src/wasm/wasm-limits.h" +#include "src/wasm/wasm-module.h" namespace v8 { namespace internal { @@ -47,6 +49,14 @@ struct ModuleEnv { // be generated differently. const RuntimeExceptionSupport runtime_exception_support; + // The smallest size of any memory that could be used with this module, in + // bytes. + const uint64_t min_memory_size; + + // The largest size of any memory that could be used with this module, in + // bytes. + const uint64_t max_memory_size; + const LowerSimd lower_simd; constexpr ModuleEnv(const WasmModule* module, UseTrapHandler use_trap_handler, @@ -55,6 +65,12 @@ struct ModuleEnv { : module(module), use_trap_handler(use_trap_handler), runtime_exception_support(runtime_exception_support), + min_memory_size( + module ? module->initial_pages * uint64_t{wasm::kWasmPageSize} : 0), + max_memory_size( + module && module->has_maximum_pages + ? (module->maximum_pages * uint64_t{wasm::kWasmPageSize}) + : wasm::kSpecMaxWasmMemoryBytes), lower_simd(lower_simd) {} }; diff --git a/src/wasm/wasm-limits.h b/src/wasm/wasm-limits.h index b5928dd04e..db99313e07 100644 --- a/src/wasm/wasm-limits.h +++ b/src/wasm/wasm-limits.h @@ -15,6 +15,8 @@ namespace v8 { namespace internal { namespace wasm { +constexpr size_t kSpecMaxWasmMemoryPages = 65536; + // The following limits are imposed by V8 on WebAssembly modules. // The limits are agreed upon with other engines for consistency. constexpr size_t kV8MaxWasmTypes = 1000000; @@ -40,7 +42,6 @@ constexpr size_t kV8MaxWasmTableEntries = 10000000; constexpr size_t kV8MaxWasmTables = 1; constexpr size_t kV8MaxWasmMemories = 1; -constexpr size_t kSpecMaxWasmMemoryPages = 65536; static_assert(kV8MaxWasmMemoryPages <= kSpecMaxWasmMemoryPages, "v8 should not be more permissive than the spec"); constexpr size_t kSpecMaxWasmTableSize = 0xFFFFFFFFu; @@ -48,6 +49,9 @@ constexpr size_t kSpecMaxWasmTableSize = 0xFFFFFFFFu; constexpr uint64_t kV8MaxWasmMemoryBytes = kV8MaxWasmMemoryPages * uint64_t{kWasmPageSize}; +constexpr uint64_t kSpecMaxWasmMemoryBytes = + kSpecMaxWasmMemoryPages * uint64_t{kWasmPageSize}; + constexpr uint64_t kWasmMaxHeapOffset = static_cast( std::numeric_limits::max()) // maximum base value diff --git a/test/mjsunit/wasm/bounds-check-64bit.js b/test/mjsunit/wasm/bounds-check-64bit.js index 8643093db0..9308393da4 100644 --- a/test/mjsunit/wasm/bounds-check-64bit.js +++ b/test/mjsunit/wasm/bounds-check-64bit.js @@ -21,13 +21,14 @@ builder.addFunction('load', kSig_i_ii) const module = builder.instantiate(); let start = 12; let address = start; -for (i = 1; i < 64; i++) { +for (i = 0; i < 64; i++) { // This is the address which will be accessed in the code. We cannot use // shifts to calculate the address because JS shifts work on 32-bit integers. - address = (address * 2) % 4294967296; + print(`address=${address}`); if (address < kPageSize) { assertEquals(0, module.exports.load(start, i)); } else { assertTraps(kTrapMemOutOfBounds, _ => { module.exports.load(start, i);}); } + address = (address * 2) % 4294967296; } diff --git a/test/mjsunit/wasm/memory_1gb_oob.js b/test/mjsunit/wasm/memory_1gb_oob.js new file mode 100644 index 0000000000..f9593e84f7 --- /dev/null +++ b/test/mjsunit/wasm/memory_1gb_oob.js @@ -0,0 +1,99 @@ +// 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: --wasm-max-mem-pages=16384 + +load('test/mjsunit/wasm/wasm-constants.js'); +load('test/mjsunit/wasm/wasm-module-builder.js'); + +const k1MiB = 1 * 1024 * 1024; +const k1GiB = 1 * 1024 * 1024 * 1024; +const k2GiB = 2 * k1GiB; +const k3GiB = 3 * k1GiB; +const k4GiB = 4 * k1GiB; +const kMaxMemory = k1GiB; + +// Indexes (and offsets) used to systematically probe the memory. +const indexes = (() => { + const a = k1GiB, b = k2GiB, c = k3GiB, d = k4GiB; + return [ + 0, 1, 2, 3, 4, 5, 7, 8, 9, // near 0 + a-8, a-4, a+0, a+1, a+2, a+3, a+4, a+5, a+7, a+8, a+9, // near 1GiB + b-8, b-4, b+0, b+1, b+2, b+3, b+4, b+5, b+7, b+8, b+9, // near 2GiB + c-8, c-4, c+0, c+1, c+2, c+3, c+4, c+5, c+7, c+8, c+9, // near 3GiB + d-9, d-8, d-7, d-5, d-4, d-3, d-2, d-1 // near 4GiB +]; +})(); + +(function Test() { + var memory; + + function BuildAccessors(type, load_opcode, store_opcode, offset) { + builder = new WasmModuleBuilder(); + builder.addImportedMemory("i", "mem"); + const h = 0x80; + const m = 0x7f; + let offset_bytes = [h|((offset >>> 0) & m), // LEB encoding of offset + h|((offset >>> 7) & m), + h|((offset >>> 14) & m), + h|((offset >>> 21) & m), + 0|((offset >>> 28) & m)]; + builder.addFunction("load", makeSig([kWasmI32], [type])) + .addBody([ // -- + kExprGetLocal, 0, // -- + load_opcode, 0, ...offset_bytes, // -- + ]) // -- + .exportFunc(); + builder.addFunction("store", makeSig([kWasmI32, type], [])) + .addBody([ // -- + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + store_opcode, 0, ...offset_bytes, // -- + ]) // -- + .exportFunc(); + let i = builder.instantiate({i: {mem: memory}}); + return {offset: offset, load: i.exports.load, store: i.exports.store}; + } + + function probe(a, size, offset, f) { + print(`size=${size} offset=${offset}`); + for (let i of indexes) { + let oob = (i + size + offset) > kMaxMemory; + if (oob) { +// print(` ${i} + ${offset} OOB`); + assertThrows(() => a.store(i, f(i))); + assertThrows(() => a.load(i)); + } else { +// print(` ${i} = ${f(i)}`); + a.store(i, f(i)); + assertEquals(f(i), a.load(i)); + } + } + } + + try { + const kPages = kMaxMemory / kPageSize; + memory = new WebAssembly.Memory({initial: kPages, maximum: kPages}); + } catch (e) { + print("OOM: sorry, best effort max memory size test."); + return; + } + + assertEquals(kMaxMemory, memory.buffer.byteLength); + + for (let offset of indexes) { + let a = BuildAccessors(kWasmI32, kExprI32LoadMem, kExprI32StoreMem, offset); + probe(a, 4, offset, i => (0xaabbccee ^ ((i >> 11) * 0x110005)) | 0); + } + + for (let offset of indexes) { + let a = BuildAccessors(kWasmI32, kExprI32LoadMem8U, kExprI32StoreMem8, offset); + probe(a, 1, offset, i => (0xee ^ ((i >> 11) * 0x05)) & 0xFF); + } + + for (let offset of indexes) { + let a = BuildAccessors(kWasmF64, kExprF64LoadMem, kExprF64StoreMem, offset); + probe(a, 8, offset, i => 0xaabbccee ^ ((i >> 11) * 0x110005)); + } +})(); diff --git a/test/mjsunit/wasm/memory_2gb_oob.js b/test/mjsunit/wasm/memory_2gb_oob.js new file mode 100644 index 0000000000..db344f30f3 --- /dev/null +++ b/test/mjsunit/wasm/memory_2gb_oob.js @@ -0,0 +1,99 @@ +// 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: --wasm-max-mem-pages=32768 + +load('test/mjsunit/wasm/wasm-constants.js'); +load('test/mjsunit/wasm/wasm-module-builder.js'); + +const k1MiB = 1 * 1024 * 1024; +const k1GiB = 1 * 1024 * 1024 * 1024; +const k2GiB = 2 * k1GiB; +const k3GiB = 3 * k1GiB; +const k4GiB = 4 * k1GiB; +const kMaxMemory = k2GiB; + +// Indexes (and offsets) used to systematically probe the memory. +const indexes = (() => { + const a = k1GiB, b = k2GiB, c = k3GiB, d = k4GiB; + return [ + 0, 1, 2, 3, 4, 5, 7, 8, 9, // near 0 + a-8, a-4, a+0, a+1, a+2, a+3, a+4, a+5, a+7, a+8, a+9, // near 1GiB + b-8, b-4, b+0, b+1, b+2, b+3, b+4, b+5, b+7, b+8, b+9, // near 2GiB + c-8, c-4, c+0, c+1, c+2, c+3, c+4, c+5, c+7, c+8, c+9, // near 3GiB + d-9, d-8, d-7, d-5, d-4, d-3, d-2, d-1 // near 4GiB +]; +})(); + +(function Test() { + var memory; + + function BuildAccessors(type, load_opcode, store_opcode, offset) { + builder = new WasmModuleBuilder(); + builder.addImportedMemory("i", "mem"); + const h = 0x80; + const m = 0x7f; + let offset_bytes = [h|((offset >>> 0) & m), // LEB encoding of offset + h|((offset >>> 7) & m), + h|((offset >>> 14) & m), + h|((offset >>> 21) & m), + 0|((offset >>> 28) & m)]; + builder.addFunction("load", makeSig([kWasmI32], [type])) + .addBody([ // -- + kExprGetLocal, 0, // -- + load_opcode, 0, ...offset_bytes, // -- + ]) // -- + .exportFunc(); + builder.addFunction("store", makeSig([kWasmI32, type], [])) + .addBody([ // -- + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + store_opcode, 0, ...offset_bytes, // -- + ]) // -- + .exportFunc(); + let i = builder.instantiate({i: {mem: memory}}); + return {offset: offset, load: i.exports.load, store: i.exports.store}; + } + + function probe(a, size, offset, f) { + print(`size=${size} offset=${offset}`); + for (let i of indexes) { + let oob = (i + size + offset) > kMaxMemory; + if (oob) { +// print(` ${i} + ${offset} OOB`); + assertThrows(() => a.store(i, f(i))); + assertThrows(() => a.load(i)); + } else { +// print(` ${i} = ${f(i)}`); + a.store(i, f(i)); + assertEquals(f(i), a.load(i)); + } + } + } + + try { + let kPages = kMaxMemory / kPageSize; + memory = new WebAssembly.Memory({initial: kPages, maximum: kPages}); + } catch (e) { + print("OOM: sorry, best effort max memory size test."); + return; + } + + assertEquals(kMaxMemory, memory.buffer.byteLength); + + for (let offset of indexes) { + let a = BuildAccessors(kWasmI32, kExprI32LoadMem, kExprI32StoreMem, offset); + probe(a, 4, offset, i => (0xaabbccee ^ ((i >> 11) * 0x110005)) | 0); + } + + for (let offset of indexes) { + let a = BuildAccessors(kWasmI32, kExprI32LoadMem8U, kExprI32StoreMem8, offset); + probe(a, 1, offset, i => (0xee ^ ((i >> 11) * 0x05)) & 0xFF); + } + + for (let offset of indexes) { + let a = BuildAccessors(kWasmF64, kExprF64LoadMem, kExprF64StoreMem, offset); + probe(a, 8, offset, i => 0xaabbccee ^ ((i >> 11) * 0x110005)); + } +})(); diff --git a/test/mjsunit/wasm/memory_4gb_oob.js b/test/mjsunit/wasm/memory_4gb_oob.js new file mode 100644 index 0000000000..d5cb006a79 --- /dev/null +++ b/test/mjsunit/wasm/memory_4gb_oob.js @@ -0,0 +1,97 @@ +// 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. + +load('test/mjsunit/wasm/wasm-constants.js'); +load('test/mjsunit/wasm/wasm-module-builder.js'); + +const k1MiB = 1 * 1024 * 1024; +const k1GiB = 1 * 1024 * 1024 * 1024; +const k2GiB = 2 * k1GiB; +const k3GiB = 3 * k1GiB; +const k4GiB = 4 * k1GiB; +const kMaxMemory = k4GiB; + +// Indexes (and offsets) used to systematically probe the memory. +const indexes = (() => { + const a = k1GiB, b = k2GiB, c = k3GiB, d = k4GiB; + return [ + 0, 1, 2, 3, 4, 5, 7, 8, 9, // near 0 + a-8, a-4, a+0, a+1, a+2, a+3, a+4, a+5, a+7, a+8, a+9, // near 1GiB + b-8, b-4, b+0, b+1, b+2, b+3, b+4, b+5, b+7, b+8, b+9, // near 2GiB + c-8, c-4, c+0, c+1, c+2, c+3, c+4, c+5, c+7, c+8, c+9, // near 3GiB + d-9, d-8, d-7, d-5, d-4, d-3, d-2, d-1 // near 4GiB +]; +})(); + +(function Test() { + var memory; + + function BuildAccessors(type, load_opcode, store_opcode, offset) { + builder = new WasmModuleBuilder(); + builder.addImportedMemory("i", "mem"); + const h = 0x80; + const m = 0x7f; + let offset_bytes = [h|((offset >>> 0) & m), // LEB encoding of offset + h|((offset >>> 7) & m), + h|((offset >>> 14) & m), + h|((offset >>> 21) & m), + 0|((offset >>> 28) & m)]; + builder.addFunction("load", makeSig([kWasmI32], [type])) + .addBody([ // -- + kExprGetLocal, 0, // -- + load_opcode, 0, ...offset_bytes, // -- + ]) // -- + .exportFunc(); + builder.addFunction("store", makeSig([kWasmI32, type], [])) + .addBody([ // -- + kExprGetLocal, 0, // -- + kExprGetLocal, 1, // -- + store_opcode, 0, ...offset_bytes, // -- + ]) // -- + .exportFunc(); + let i = builder.instantiate({i: {mem: memory}}); + return {offset: offset, load: i.exports.load, store: i.exports.store}; + } + + function probe(a, size, offset, f) { + print(`size=${size} offset=${offset}`); + for (let i of indexes) { + let oob = (i + size + offset) > kMaxMemory; + if (oob) { +// print(` ${i} + ${offset} OOB`); + assertThrows(() => a.store(i, f(i))); + assertThrows(() => a.load(i)); + } else { +// print(` ${i} = ${f(i)}`); + a.store(i, f(i)); + assertEquals(f(i), a.load(i)); + } + } + } + + try { + let kPages = kMaxMemory / kPageSize; + memory = new WebAssembly.Memory({initial: kPages, maximum: kPages}); + } catch (e) { + print("OOM: sorry, best effort max memory size test."); + return; + } + + assertEquals(kMaxMemory, memory.buffer.byteLength); + + for (let offset of indexes) { + let a = BuildAccessors(kWasmI32, kExprI32LoadMem, kExprI32StoreMem, offset); + probe(a, 4, offset, i => (0xaabbccee ^ ((i >> 11) * 0x110005)) | 0); + } + + for (let offset of indexes) { + let a = BuildAccessors(kWasmI32, kExprI32LoadMem8U, kExprI32StoreMem8, offset); + probe(a, 1, offset, i => (0xee ^ ((i >> 11) * 0x05)) & 0xFF); + } + + for (let offset of indexes) { + let a = BuildAccessors(kWasmF64, kExprF64LoadMem, kExprF64StoreMem, offset); + probe(a, 8, offset, i => 0xaabbccee ^ ((i >> 11) * 0x110005)); + } +})();