Commit 2d04a627 authored by Clemens Backes's avatar Clemens Backes Committed by V8 LUCI CQ

[wasm] Clean up spec'ed max memory vs dynamic max

There are two different limits for the maximum memory size in
WebAssembly:
1) A 4GB limit which is the same on all platforms, and is observable for
JS programs. It is used to limit the allowed declared maximum size of a
wasm memory.
2) A potentially lower limit (2GB on 32-bit systems, 4GB otherwise)
which can be further limited using a command-line flag. This limit is
used whenever actually allocating or growing a wasm memory. This limit
is not directly observable, but we make sure that no wasm memory will
ever be bigger than this limit.

The second limit is the one we should check against when allocating or
growing memory, while the first limit should be used when validating
a module (or the parameters for WebAssembly.Memory). The compiler can
rely on no memory being bigger than the second limit, which again is
never bigger than the first limit.

This CL adds some more documentation to the two limits, and cleans up
all usages.
This also makes {kPlatformMaxPages} and {kMaxMemoryPagesAtRuntime}
obsolete.

R=jkummerow@chromium.org

Bug: chromium:1207263
Change-Id: I43541aafd3f497d1c368bd9400e9bc667bdfd3d9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2910787
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74742}
parent e46ce494
......@@ -300,9 +300,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}) {
return false;
}
if (size > wasm::max_mem_bytes()) return false;
// Enforce power-of-2 sizes for 2^12 - 2^24.
if (size < (1u << 24u)) {
uint32_t size32 = static_cast<uint32_t>(size);
......
......@@ -891,7 +891,7 @@ DEFINE_BOOL(wasm_async_compilation, true,
DEFINE_NEG_IMPLICATION(single_threaded, wasm_async_compilation)
DEFINE_BOOL(wasm_test_streaming, false,
"use streaming compilation instead of async compilation for tests")
DEFINE_UINT(wasm_max_mem_pages, v8::internal::wasm::kSpecMaxMemoryPages,
DEFINE_UINT(wasm_max_mem_pages, v8::internal::wasm::kV8MaxWasmMemoryPages,
"maximum number of 64KiB memory pages per wasm memory")
DEFINE_UINT(wasm_max_table_size, v8::internal::wasm::kV8MaxWasmTableSize,
"maximum table size of a wasm instance")
......
......@@ -30,13 +30,6 @@ namespace internal {
namespace {
#if V8_ENABLE_WEBASSEMBLY
// Trying to allocate 4 GiB on a 32-bit platform is guaranteed to fail.
// We don't lower the official max_mem_pages() limit because that would be
// observable upon instantiation; this way the effective limit on 32-bit
// platforms is defined by the allocator.
constexpr size_t kPlatformMaxPages =
std::numeric_limits<size_t>::max() / wasm::kWasmPageSize;
constexpr uint64_t kNegativeGuardSize = uint64_t{2} * GB;
#if V8_TARGET_ARCH_64_BIT
......@@ -476,8 +469,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::kV8MaxWasmMemoryPages) return nullptr;
if (initial_pages > kPlatformMaxPages) return nullptr;
if (initial_pages > wasm::max_mem_pages()) return nullptr;
auto backing_store =
TryAllocateWasmMemory(isolate, initial_pages, maximum_pages, shared);
......
......@@ -64,12 +64,6 @@ struct CompilationEnv {
// Features enabled for this compilation.
const WasmFeatures enabled_features;
// We assume that memories of size >= half of the virtual address space
// cannot be allocated (see https://crbug.com/1201340).
static constexpr uint32_t kMaxMemoryPagesAtRuntime = std::min(
kV8MaxWasmMemoryPages,
(uintptr_t{1} << (kSystemPointerSize == 4 ? 31 : 63)) / kWasmPageSize);
constexpr CompilationEnv(const WasmModule* module,
UseTrapHandler use_trap_handler,
RuntimeExceptionSupport runtime_exception_support,
......@@ -79,14 +73,15 @@ struct CompilationEnv {
runtime_exception_support(runtime_exception_support),
// During execution, the memory can never be bigger than what fits in a
// uintptr_t.
min_memory_size(std::min(kMaxMemoryPagesAtRuntime,
module ? module->initial_pages : 0) *
uint64_t{kWasmPageSize}),
max_memory_size(static_cast<uintptr_t>(
std::min(kMaxMemoryPagesAtRuntime,
module && module->has_maximum_pages ? module->maximum_pages
: max_mem_pages()) *
uint64_t{kWasmPageSize})),
min_memory_size(
std::min(kV8MaxWasmMemoryPages,
uintptr_t{module ? module->initial_pages : 0}) *
kWasmPageSize),
max_memory_size((module && module->has_maximum_pages
? std::min(kV8MaxWasmMemoryPages,
uintptr_t{module->maximum_pages})
: kV8MaxWasmMemoryPages) *
kWasmPageSize),
enabled_features(enabled_features) {}
};
......
......@@ -779,9 +779,6 @@ void UpdateFeatureUseCounts(Isolate* isolate, const WasmFeatures& detected) {
} // namespace
// static
constexpr uint32_t CompilationEnv::kMaxMemoryPagesAtRuntime;
//////////////////////////////////////////////////////
// PIMPL implementation of {CompilationState}.
......
......@@ -657,10 +657,10 @@ class ModuleDecoderImpl : public Decoder {
if (!AddMemory(module_.get())) break;
uint8_t flags = validate_memory_flags(&module_->has_shared_memory,
&module_->is_memory64);
consume_resizable_limits("memory", "pages", max_mem_pages(),
&module_->initial_pages,
&module_->has_maximum_pages, max_mem_pages(),
&module_->maximum_pages, flags);
consume_resizable_limits(
"memory", "pages", kSpecMaxMemoryPages, &module_->initial_pages,
&module_->has_maximum_pages, kSpecMaxMemoryPages,
&module_->maximum_pages, flags);
break;
}
case kExternalGlobal: {
......@@ -761,9 +761,9 @@ class ModuleDecoderImpl : public Decoder {
if (!AddMemory(module_.get())) break;
uint8_t flags = validate_memory_flags(&module_->has_shared_memory,
&module_->is_memory64);
consume_resizable_limits("memory", "pages", max_mem_pages(),
consume_resizable_limits("memory", "pages", kSpecMaxMemoryPages,
&module_->initial_pages,
&module_->has_maximum_pages, max_mem_pages(),
&module_->has_maximum_pages, kSpecMaxMemoryPages,
&module_->maximum_pages, flags);
}
}
......
......@@ -1654,18 +1654,12 @@ bool InstanceBuilder::AllocateMemory() {
int maximum_pages = module_->has_maximum_pages
? static_cast<int>(module_->maximum_pages)
: WasmMemoryObject::kNoMaximum;
if (initial_pages > static_cast<int>(max_mem_pages())) {
thrower_->RangeError("Out of memory: wasm memory too large");
return false;
}
auto shared = (module_->has_shared_memory && enabled_.has_threads())
? SharedFlag::kShared
: SharedFlag::kNotShared;
MaybeHandle<WasmMemoryObject> result =
WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared);
if (!result.ToHandle(&memory_object_)) {
if (!WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared)
.ToHandle(&memory_object_)) {
thrower_->RangeError("Out of memory: wasm memory");
return false;
}
......
......@@ -1140,14 +1140,14 @@ 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::kSpecMaxMemoryPages)) {
return;
}
// The descriptor's 'maximum'.
int64_t maximum = i::WasmMemoryObject::kNoMaximum;
if (!GetOptionalIntegerProperty(isolate, &thrower, context, descriptor,
v8_str(isolate, "maximum"), nullptr, &maximum,
initial, i::wasm::max_mem_pages())) {
initial, i::wasm::kSpecMaxMemoryPages)) {
return;
}
......@@ -1725,30 +1725,24 @@ void WebAssemblyMemoryGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmMemoryObject);
uint32_t delta_size;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &delta_size)) {
uint32_t delta_pages;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &delta_pages)) {
return;
}
uint64_t max_size64 = receiver->maximum_pages();
if (max_size64 > uint64_t{i::wasm::max_mem_pages()}) {
max_size64 = i::wasm::max_mem_pages();
}
i::Handle<i::JSArrayBuffer> old_buffer(receiver->array_buffer(), i_isolate);
DCHECK_LE(max_size64, std::numeric_limits<uint32_t>::max());
uint64_t old_size64 = old_buffer->byte_length() / i::wasm::kWasmPageSize;
uint64_t new_size64 = old_size64 + static_cast<uint64_t>(delta_size);
uint64_t old_pages64 = old_buffer->byte_length() / i::wasm::kWasmPageSize;
uint64_t new_pages64 = old_pages64 + static_cast<uint64_t>(delta_pages);
if (new_size64 > max_size64) {
if (new_pages64 > static_cast<uint64_t>(receiver->maximum_pages())) {
thrower.RangeError("Maximum memory size exceeded");
return;
}
int32_t ret = i::WasmMemoryObject::Grow(i_isolate, receiver, delta_size);
int32_t ret = i::WasmMemoryObject::Grow(i_isolate, receiver, delta_pages);
if (ret == -1) {
thrower.RangeError("Unable to grow instance memory.");
thrower.RangeError("Unable to grow instance memory");
return;
}
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
......
......@@ -20,8 +20,8 @@ namespace v8 {
namespace internal {
namespace wasm {
// This constant is defined in the Wasm JS API spec and as such only
// concern JS embeddings.
// This constant limits the amount of *declared* memory. At runtime, memory can
// only grow up to kV8MaxWasmMemoryPages.
constexpr size_t kSpecMaxMemoryPages = 65536;
// The following limits are imposed by V8 on WebAssembly modules.
......@@ -35,9 +35,13 @@ constexpr size_t kV8MaxWasmExceptions = 1000000;
constexpr size_t kV8MaxWasmExceptionTypes = 1000000;
constexpr size_t kV8MaxWasmDataSegments = 100000;
// This indicates the maximum memory size our implementation supports.
// Don't use this limit directly; use {max_mem_pages()} instead to take the
// Do not use this limit directly; use {max_mem_pages()} instead to take the
// spec'ed limit as well as command line flag into account.
constexpr size_t kV8MaxWasmMemoryPages = 65536; // = 4 GiB
// Also, do not use this limit to validate declared memory, use
// kSpecMaxMemoryPages for that.
constexpr size_t kV8MaxWasmMemoryPages = kSystemPointerSize == 4
? 32768 // = 2 GiB
: 65536; // = 4 GiB
constexpr size_t kV8MaxWasmStringSize = 100000;
constexpr size_t kV8MaxWasmModuleSize = 1024 * 1024 * 1024; // = 1 GiB
constexpr size_t kV8MaxWasmFunctionSize = 7654321;
......@@ -68,17 +72,21 @@ constexpr uint64_t kWasmMaxHeapOffset =
std::numeric_limits<uint32_t>::max()) // maximum base value
+ std::numeric_limits<uint32_t>::max(); // maximum index value
// Defined in wasm-engine.cc.
// The following functions are defined in wasm-engine.cc.
// Maximum number of pages we can allocate. This might be lower than the number
// of pages that can be declared (e.g. as maximum): kSpecMaxMemoryPages.
// 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();
uint32_t max_table_init_entries();
size_t max_module_size();
inline uint64_t max_mem_bytes() {
return uint64_t{max_mem_pages()} * kWasmPageSize;
}
uint32_t max_table_init_entries();
size_t max_module_size();
} // namespace wasm
} // namespace internal
} // namespace v8
......
// Copyright 2021 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-module-builder.js');
const builder = new WasmModuleBuilder();
const two_gb = 2 * 1024 * 1024 * 1024;
builder.addMemory(two_gb / kPageSize + 1);
builder.addFunction('load', kSig_v_v)
.addBody([
kExprI32Const, 0, kExprI32LoadMem, 0, ...wasmUnsignedLeb(two_gb),
kExprDrop
])
.exportFunc();
let instance;
try {
instance = builder.instantiate();
} catch (RangeError) { }
if (instance) {
instance.exports.load();
}
......@@ -9,5 +9,4 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
let builder = new WasmModuleBuilder();
const num_pages = 49152;
builder.addMemory(num_pages, num_pages);
// num_pages * 64k (page size) > kMaxInt.
assertThrows(() => builder.instantiate(), WebAssembly.CompileError);
assertThrows(() => builder.instantiate(), RangeError);
......@@ -1962,6 +1962,21 @@ function wasmSignedLeb(val, max_len = 5) {
'Leb value <' + val + '> exceeds maximum length of ' + max_len);
}
function wasmUnsignedLeb(val, max_len = 5) {
let res = [];
for (let i = 0; i < max_len; ++i) {
let v = val & 0x7f;
if (v == val) {
res.push(v);
return res;
}
res.push(v | 0x80);
val = val >>> 7;
}
throw new Error(
'Leb value <' + val + '> exceeds maximum length of ' + max_len);
}
function wasmI32Const(val) {
return [kExprI32Const, ...wasmSignedLeb(val, 5)];
}
......
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