Commit a8b7d477 authored by Jakob Kummerow's avatar Jakob Kummerow Committed by Commit Bot

[wasm] Bring memory limits up to spec

Make sure the "initial pages" memory limit is enforced correctly and
throws a CompileError when exceeded.
Bump the "maximum pages" memory limit to 65536.
The --wasm-max-mem-pages flag now controls the "initial pages" limit;
the "maximum pages" limit is always 65536 as spec'ed.

This CL depends on https://github.com/WebAssembly/spec/pull/1121.

Bug: v8:7881, v8:8633
Change-Id: I68d07cef56633b8b8ce3b3d047c14e1096daf547
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2035876Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66400}
parent 38b4f19b
......@@ -330,7 +330,7 @@ inline bool IsValidAsmjsMemorySize(size_t size) {
// Enforce asm.js spec minimum size.
if (size < (1u << 12u)) return false;
// Enforce engine-limited and flag-limited maximum allocation size.
if (size > wasm::max_mem_pages() * uint64_t{wasm::kWasmPageSize}) {
if (size > wasm::max_initial_mem_pages() * uint64_t{wasm::kWasmPageSize}) {
return false;
}
// Enforce power-of-2 sizes for 2^12 - 2^24.
......
......@@ -116,7 +116,7 @@ class V8_EXPORT_PRIVATE TypeCache final {
Type const kJSArrayBufferViewByteOffsetType = kJSArrayBufferByteLengthType;
// The JSTypedArray::length property always contains an untagged number in
// the range [0, kMaxSmiValue].
// the range [0, JSTypedArray::kMaxLength].
Type const kJSTypedArrayLengthType =
CreateRange(0.0, JSTypedArray::kMaxLength);
......
......@@ -671,9 +671,12 @@ DEFINE_BOOL(wasm_async_compilation, true,
"enable actual asynchronous compilation for WebAssembly.compile")
DEFINE_BOOL(wasm_test_streaming, false,
"use streaming compilation instead of async compilation for tests")
// TODO(4153): Set this back to v8::internal::wasm::kV8MaxWasmMemoryPages
DEFINE_UINT(wasm_max_mem_pages, 32767,
"maximum number of 64KiB memory pages of a wasm instance")
DEFINE_UINT(wasm_max_mem_pages,
v8::internal::wasm::kSpecMaxWasmInitialMemoryPages,
"maximum initial number of 64KiB memory pages of a wasm instance")
DEFINE_UINT(wasm_max_mem_pages_growth,
v8::internal::wasm::kSpecMaxWasmMaximumMemoryPages,
"maximum number of 64KiB pages a Wasm memory can grow to")
DEFINE_UINT(wasm_max_table_size, v8::internal::wasm::kV8MaxWasmTableSize,
"maximum table size of a wasm instance")
DEFINE_UINT(wasm_max_code_space, v8::internal::kMaxWasmCodeMB,
......
......@@ -313,7 +313,7 @@ std::unique_ptr<BackingStore> BackingStore::TryAllocateWasmMemory(
// Compute size of reserved memory.
size_t engine_max_pages = wasm::max_mem_pages();
size_t engine_max_pages = wasm::max_initial_mem_pages();
size_t byte_capacity =
std::min(engine_max_pages, maximum_pages) * wasm::kWasmPageSize;
size_t reservation_size = GetReservationSize(guards, byte_capacity);
......@@ -408,7 +408,7 @@ std::unique_ptr<BackingStore> BackingStore::AllocateWasmMemory(
DCHECK_EQ(0, wasm::kWasmPageSize % AllocatePageSize());
// Enforce engine limitation on the maximum number of pages.
if (initial_pages > wasm::max_mem_pages()) return nullptr;
if (initial_pages > wasm::max_initial_mem_pages()) return nullptr;
auto backing_store =
TryAllocateWasmMemory(isolate, initial_pages, maximum_pages, shared);
......
......@@ -1879,11 +1879,13 @@ auto Memory::make(Store* store_abs, const MemoryType* type) -> own<Memory> {
const Limits& limits = type->limits();
uint32_t minimum = limits.min;
if (minimum > i::wasm::max_mem_pages()) return nullptr;
// The max_initial_mem_pages limit is only spec'ed for JS embeddings,
// so we'll directly use the maximum pages limit here.
if (minimum > i::wasm::kSpecMaxWasmMaximumMemoryPages) return nullptr;
uint32_t maximum = limits.max;
if (maximum != Limits(0).max) {
if (maximum < minimum) return nullptr;
if (maximum > i::wasm::kSpecMaxWasmMemoryPages) return nullptr;
if (maximum > i::wasm::kSpecMaxWasmMaximumMemoryPages) return nullptr;
}
// TODO(wasm+): Support shared memory.
i::SharedFlag shared = i::SharedFlag::kNotShared;
......
......@@ -76,7 +76,7 @@ struct CompilationEnv {
: 0),
max_memory_size((module && module->has_maximum_pages
? module->maximum_pages
: max_mem_pages()) *
: max_initial_mem_pages()) *
uint64_t{kWasmPageSize}),
enabled_features(enabled_features),
lower_simd(lower_simd),
......
......@@ -584,9 +584,9 @@ class ModuleDecoderImpl : public Decoder {
if (!AddMemory(module_.get())) break;
uint8_t flags = validate_memory_flags(&module_->has_shared_memory);
consume_resizable_limits(
"memory", "pages", kSpecMaxWasmMemoryPages,
"memory", "pages", max_initial_mem_pages(),
&module_->initial_pages, &module_->has_maximum_pages,
kSpecMaxWasmMemoryPages, &module_->maximum_pages, flags);
max_maximum_mem_pages(), &module_->maximum_pages, flags);
break;
}
case kExternalGlobal: {
......@@ -676,8 +676,8 @@ class ModuleDecoderImpl : public Decoder {
if (!AddMemory(module_.get())) break;
uint8_t flags = validate_memory_flags(&module_->has_shared_memory);
consume_resizable_limits(
"memory", "pages", kSpecMaxWasmMemoryPages, &module_->initial_pages,
&module_->has_maximum_pages, kSpecMaxWasmMemoryPages,
"memory", "pages", max_initial_mem_pages(), &module_->initial_pages,
&module_->has_maximum_pages, max_maximum_mem_pages(),
&module_->maximum_pages, flags);
}
}
......
......@@ -1402,10 +1402,11 @@ void InstanceBuilder::InitGlobals(Handle<WasmInstanceObject> instance) {
// Allocate memory for a module instance as a new JSArrayBuffer.
bool InstanceBuilder::AllocateMemory() {
auto initial_pages = module_->initial_pages;
auto maximum_pages = module_->has_maximum_pages ? module_->maximum_pages
: wasm::max_mem_pages();
if (initial_pages > max_mem_pages()) {
uint32_t initial_pages = module_->initial_pages;
uint32_t maximum_pages = module_->has_maximum_pages
? module_->maximum_pages
: wasm::max_initial_mem_pages();
if (initial_pages > max_initial_mem_pages()) {
thrower_->RangeError("Out of memory: wasm memory too large");
return false;
}
......
......@@ -6,6 +6,7 @@
#include "src/base/functional.h"
#include "src/base/platform/time.h"
#include "src/common/globals.h"
#include "src/diagnostics/code-tracer.h"
#include "src/diagnostics/compilation-statistics.h"
#include "src/execution/frames.h"
......@@ -21,6 +22,7 @@
#include "src/wasm/module-decoder.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/streaming-decoder.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-objects-inl.h"
#ifdef V8_ENABLE_WASM_GDB_REMOTE_DEBUGGING
......@@ -1168,12 +1170,18 @@ std::shared_ptr<WasmEngine> WasmEngine::GetWasmEngine() {
return *GetSharedWasmEngine();
}
// {max_mem_pages} is declared in wasm-limits.h.
uint32_t max_mem_pages() {
// {max_initial_mem_pages} is declared in wasm-limits.h.
uint32_t max_initial_mem_pages() {
STATIC_ASSERT(kV8MaxWasmMemoryPages <= kMaxUInt32);
return std::min(uint32_t{kV8MaxWasmMemoryPages}, FLAG_wasm_max_mem_pages);
}
uint32_t max_maximum_mem_pages() {
STATIC_ASSERT(kV8MaxWasmMemoryPages <= kMaxUInt32);
return std::min(uint32_t{kV8MaxWasmMemoryPages},
FLAG_wasm_max_mem_pages_growth);
}
// {max_table_init_entries} is declared in wasm-limits.h.
uint32_t max_table_init_entries() {
return std::min(uint32_t{kV8MaxWasmTableInitEntries},
......
......@@ -1148,14 +1148,15 @@ void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
int64_t initial = 0;
if (!GetInitialOrMinimumProperty(isolate, &thrower, context, descriptor,
&initial, 0, i::wasm::max_mem_pages())) {
&initial, 0,
i::wasm::max_initial_mem_pages())) {
return;
}
// The descriptor's 'maximum'.
int64_t maximum = -1;
if (!GetOptionalIntegerProperty(isolate, &thrower, context, descriptor,
v8_str(isolate, "maximum"), nullptr, &maximum,
initial, i::wasm::kSpecMaxWasmMemoryPages)) {
initial, i::wasm::max_maximum_mem_pages())) {
return;
}
......@@ -1697,8 +1698,8 @@ void WebAssemblyMemoryGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
uint64_t max_size64 = receiver->maximum_pages();
if (max_size64 > uint64_t{i::wasm::max_mem_pages()}) {
max_size64 = i::wasm::max_mem_pages();
if (max_size64 > uint64_t{i::wasm::max_initial_mem_pages()}) {
max_size64 = i::wasm::max_initial_mem_pages();
}
i::Handle<i::JSArrayBuffer> old_buffer(receiver->array_buffer(), i_isolate);
......
......@@ -9,13 +9,17 @@
#include <cstdint>
#include <limits>
#include "src/base/macros.h"
#include "src/wasm/wasm-constants.h"
namespace v8 {
namespace internal {
namespace wasm {
constexpr size_t kSpecMaxWasmMemoryPages = 65536;
// These two constants are defined in the Wasm JS API spec and as such only
// concern JS embeddings.
constexpr size_t kSpecMaxWasmInitialMemoryPages = 32767;
constexpr size_t kSpecMaxWasmMaximumMemoryPages = 65536;
// The following limits are imposed by V8 on WebAssembly modules.
// The limits are agreed upon with other engines for consistency.
......@@ -27,7 +31,9 @@ constexpr size_t kV8MaxWasmGlobals = 1000000;
constexpr size_t kV8MaxWasmExceptions = 1000000;
constexpr size_t kV8MaxWasmExceptionTypes = 1000000;
constexpr size_t kV8MaxWasmDataSegments = 100000;
// Don't use this limit directly, but use the value of {max_mem_pages()}.
// This indicates the maximum memory size our implementation supports.
// Don't use this limit directly; use {max_initial_mem_pages()} instead
// to take the spec'ed limit as well as command line flag into account.
constexpr size_t kV8MaxWasmMemoryPages = 65536; // = 4 GiB
constexpr size_t kV8MaxWasmStringSize = 100000;
constexpr size_t kV8MaxWasmModuleSize = 1024 * 1024 * 1024; // = 1 GiB
......@@ -43,8 +49,6 @@ constexpr size_t kV8MaxWasmTableInitEntries = 10000000;
constexpr size_t kV8MaxWasmTables = 1;
constexpr size_t kV8MaxWasmMemories = 1;
static_assert(kV8MaxWasmMemoryPages <= kSpecMaxWasmMemoryPages,
"v8 should not be more permissive than the spec");
static_assert(kV8MaxWasmTableSize <= 4294967295, // 2^32 - 1
"v8 should not exceed WebAssembly's non-web embedding limits");
static_assert(kV8MaxWasmTableInitEntries <= kV8MaxWasmTableSize,
......@@ -58,11 +62,12 @@ constexpr uint64_t kWasmMaxHeapOffset =
// Defined in wasm-engine.cc.
// TODO(wasm): Make this size_t for wasm64. Currently the --wasm-max-mem-pages
// flag is only uint32_t.
V8_EXPORT_PRIVATE uint32_t max_mem_pages();
V8_EXPORT_PRIVATE uint32_t max_initial_mem_pages();
V8_EXPORT_PRIVATE uint32_t max_maximum_mem_pages();
uint32_t max_table_init_entries();
inline uint64_t max_mem_bytes() {
return uint64_t{max_mem_pages()} * kWasmPageSize;
return uint64_t{max_initial_mem_pages()} * kWasmPageSize;
}
} // namespace wasm
......
......@@ -879,18 +879,18 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate,
if (old_buffer->is_asmjs_memory()) return -1;
// Checks for maximum memory size.
uint32_t maximum_pages = wasm::max_mem_pages();
uint32_t maximum_pages = wasm::max_initial_mem_pages();
if (memory_object->has_maximum_pages()) {
maximum_pages = std::min(
maximum_pages, static_cast<uint32_t>(memory_object->maximum_pages()));
}
CHECK_GE(wasm::max_mem_pages(), maximum_pages);
CHECK_GE(wasm::max_initial_mem_pages(), maximum_pages);
size_t old_size = old_buffer->byte_length();
CHECK_EQ(0, old_size % wasm::kWasmPageSize);
size_t old_pages = old_size / wasm::kWasmPageSize;
CHECK_GE(wasm::max_mem_pages(), old_pages);
if ((pages > maximum_pages - old_pages) || // exceeds remaining
(pages > wasm::max_mem_pages() - old_pages)) { // exceeds limit
CHECK_GE(wasm::max_initial_mem_pages(), old_pages);
if ((pages > maximum_pages - old_pages) || // exceeds remaining
(pages > wasm::max_initial_mem_pages() - old_pages)) { // exceeds limit
return -1;
}
std::shared_ptr<BackingStore> backing_store = old_buffer->GetBackingStore();
......
......@@ -32,6 +32,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// We reduce the maximum memory size and table size of WebAssembly instances
// to avoid OOMs in the fuzzer.
i::FlagScope<uint32_t> max_mem_flag_scope(&i::FLAG_wasm_max_mem_pages, 32);
i::FlagScope<uint32_t> max_mem_growth_flag_scope(
&i::FLAG_wasm_max_mem_pages_growth, 32);
i::FlagScope<uint32_t> max_table_size_scope(&i::FLAG_wasm_max_table_size,
100);
v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
......
......@@ -10,4 +10,4 @@ let builder = new WasmModuleBuilder();
const num_pages = 49152;
builder.addMemory(num_pages, num_pages);
// num_pages * 64k (page size) > kMaxInt.
assertThrows(() => builder.instantiate(), RangeError);
assertThrows(() => builder.instantiate(), WebAssembly.CompileError);
......@@ -4,8 +4,7 @@
[
[ALWAYS, {
# https://bugs.chromium.org/p/v8/issues/detail?id=8633
'limits': [SKIP],
# These are slow, and not useful to run for the proposals:
'proposals/reference-types/limits': [SKIP],
'proposals/multi-value/limits': [SKIP],
'proposals/bulk-memory-operations/limits': [SKIP],
......@@ -22,6 +21,11 @@
'constructor/instantiate': [SKIP],
}], # 'arch == s390 or arch == s390x or system == aix'
['mode == debug or simulator_run or variant != default', {
# Slow, and we always have the same limits anyway.
'limits': [SKIP],
}], # mode == debug or simulator_run or variant != default
##############################################################################
['lite_mode or variant == jitless', {
# TODO(v8:7777): Re-enable once wasm is supported in jitless mode.
......
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