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

[wasm] Introduce separate limits for memory64

This breaks up the existing {max_mem_pages()} method and the
{kSpecMaxMemoryPages} and {kV8MaxWasmMemoryPages} constants into two
versions for memory32 and memory64, respectively.

For now, the limits are still the same.

Some checks and clamping is moved to earlier places where we still have
the information whether a memory is 32 or 64 bit.
We also store that information in the WasmMemoryObject and use this for
knowing the maximum for growing.

This CL is not supposed to change any observable behaviour.

R=jkummerow@chromium.org

Bug: v8:10949
Change-Id: Ieaca0596d1a24ef2746842954a75188494103eb2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3782677Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81960}
parent 4cdc220d
......@@ -300,7 +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_bytes()) return false;
if (size > wasm::max_mem32_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);
......
......@@ -1010,7 +1010,8 @@ DEFINE_BOOL(wasm_test_streaming, false,
"use streaming compilation instead of async compilation for tests")
DEFINE_BOOL(wasm_native_module_cache_enabled, true,
"enable the native module cache")
DEFINE_UINT(wasm_max_mem_pages, wasm::kV8MaxWasmMemoryPages,
// The actual value used at runtime is clamped to kV8MaxWasmMemory{32,64}Pages.
DEFINE_UINT(wasm_max_mem_pages, kMaxUInt32,
"maximum number of 64KiB memory pages per wasm memory")
DEFINE_UINT(wasm_max_table_size, wasm::kV8MaxWasmTableSize,
"maximum table size of a wasm instance")
......
......@@ -315,10 +315,6 @@ void BackingStore::SetAllocatorFromIsolate(Isolate* isolate) {
std::unique_ptr<BackingStore> BackingStore::TryAllocateWasmMemory(
Isolate* isolate, size_t initial_pages, size_t maximum_pages,
SharedFlag shared) {
// Compute size of reserved memory.
size_t engine_max_pages = wasm::max_mem_pages();
maximum_pages = std::min(engine_max_pages, maximum_pages);
auto result = TryAllocateAndPartiallyCommitMemory(
isolate, initial_pages * wasm::kWasmPageSize,
maximum_pages * wasm::kWasmPageSize, wasm::kWasmPageSize, initial_pages,
......@@ -454,9 +450,7 @@ std::unique_ptr<BackingStore> BackingStore::AllocateWasmMemory(
SharedFlag shared) {
// Wasm pages must be a multiple of the allocation page size.
DCHECK_EQ(0, wasm::kWasmPageSize % AllocatePageSize());
// Enforce engine limitation on the maximum number of pages.
if (initial_pages > wasm::max_mem_pages()) return nullptr;
DCHECK_LE(initial_pages, maximum_pages);
auto backing_store =
TryAllocateWasmMemory(isolate, initial_pages, maximum_pages, shared);
......
......@@ -2059,11 +2059,11 @@ auto Memory::make(Store* store_abs, const MemoryType* type) -> own<Memory> {
uint32_t minimum = limits.min;
// The max_mem_pages limit is only spec'ed for JS embeddings, so we'll
// directly use the maximum pages limit here.
if (minimum > i::wasm::kSpecMaxMemoryPages) return nullptr;
if (minimum > i::wasm::kSpecMaxMemory32Pages) return nullptr;
uint32_t maximum = limits.max;
if (maximum != Limits(0).max) {
if (maximum < minimum) return nullptr;
if (maximum > i::wasm::kSpecMaxMemoryPages) return nullptr;
if (maximum > i::wasm::kSpecMaxMemory32Pages) return nullptr;
}
// TODO(wasm+): Support shared memory.
i::SharedFlag shared = i::SharedFlag::kNotShared;
......
......@@ -85,19 +85,25 @@ struct CompilationEnv {
: module(module),
bounds_checks(bounds_checks),
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(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),
min_memory_size(MinPages(module) * kWasmPageSize),
max_memory_size(MaxPages(module) * kWasmPageSize),
enabled_features(enabled_features),
dynamic_tiering(dynamic_tiering) {}
static constexpr uintptr_t MinPages(const WasmModule* module) {
if (!module) return 0;
const uintptr_t platform_max_pages =
module->is_memory64 ? kV8MaxWasmMemory64Pages : kV8MaxWasmMemory32Pages;
return std::min(platform_max_pages, uintptr_t{module->initial_pages});
}
static constexpr uintptr_t MaxPages(const WasmModule* module) {
if (!module) return kV8MaxWasmMemory32Pages;
const uintptr_t platform_max_pages =
module->is_memory64 ? kV8MaxWasmMemory64Pages : kV8MaxWasmMemory32Pages;
if (!module->has_maximum_pages) return platform_max_pages;
return std::min(platform_max_pages, uintptr_t{module->maximum_pages});
}
};
// The wire bytes are either owned by the StreamingDecoder, or (after streaming)
......
......@@ -827,10 +827,12 @@ class ModuleDecoderTemplate : 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", kSpecMaxMemoryPages, &module_->initial_pages,
&module_->has_maximum_pages, kSpecMaxMemoryPages,
&module_->maximum_pages, flags);
uint32_t max_pages = module_->is_memory64 ? kSpecMaxMemory64Pages
: kSpecMaxMemory32Pages;
consume_resizable_limits("memory", "pages", max_pages,
&module_->initial_pages,
&module_->has_maximum_pages, max_pages,
&module_->maximum_pages, flags);
break;
}
case kExternalGlobal: {
......@@ -949,9 +951,11 @@ class ModuleDecoderTemplate : 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", kSpecMaxMemoryPages,
uint32_t max_pages =
module_->is_memory64 ? kSpecMaxMemory64Pages : kSpecMaxMemory32Pages;
consume_resizable_limits("memory", "pages", max_pages,
&module_->initial_pages,
&module_->has_maximum_pages, kSpecMaxMemoryPages,
&module_->has_maximum_pages, max_pages,
&module_->maximum_pages, flags);
}
}
......
......@@ -1686,9 +1686,12 @@ bool InstanceBuilder::AllocateMemory() {
? SharedFlag::kShared
: SharedFlag::kNotShared;
if (!WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared)
if (!WasmMemoryObject::New(isolate_, initial_pages, maximum_pages, shared,
module_->is_memory64 ? WasmMemoryObject::kMemory64
: WasmMemoryObject::kMemory32)
.ToHandle(&memory_object_)) {
thrower_->RangeError("Out of memory: wasm memory");
thrower_->RangeError(
"Out of memory: Cannot allocate Wasm memory for new instance");
return false;
}
memory_buffer_ =
......
......@@ -1645,12 +1645,21 @@ WasmCodeManager* GetWasmCodeManager() {
}
// {max_mem_pages} is declared in wasm-limits.h.
uint32_t max_mem_pages() {
uint32_t max_mem32_pages() {
static_assert(
kV8MaxWasmMemoryPages * kWasmPageSize <= JSArrayBuffer::kMaxByteLength,
kV8MaxWasmMemory32Pages * kWasmPageSize <= JSArrayBuffer::kMaxByteLength,
"Wasm memories must not be bigger than JSArrayBuffers");
static_assert(kV8MaxWasmMemoryPages <= kMaxUInt32);
return std::min(uint32_t{kV8MaxWasmMemoryPages},
static_assert(kV8MaxWasmMemory32Pages <= kMaxUInt32);
return std::min(uint32_t{kV8MaxWasmMemory32Pages},
FLAG_wasm_max_mem_pages.value());
}
uint32_t max_mem64_pages() {
static_assert(
kV8MaxWasmMemory64Pages * kWasmPageSize <= JSArrayBuffer::kMaxByteLength,
"Wasm memories must not be bigger than JSArrayBuffers");
static_assert(kV8MaxWasmMemory64Pages <= kMaxUInt32);
return std::min(uint32_t{kV8MaxWasmMemory64Pages},
FLAG_wasm_max_mem_pages.value());
}
......
......@@ -1291,16 +1291,19 @@ void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(args[0]);
// TODO(clemensb): The JS API spec is not updated for memory64 yet; fix this
// code once it is.
int64_t initial = 0;
if (!GetInitialOrMinimumProperty(isolate, &thrower, context, descriptor,
&initial, 0, i::wasm::kSpecMaxMemoryPages)) {
&initial, 0,
i::wasm::kSpecMaxMemory32Pages)) {
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::kSpecMaxMemoryPages)) {
initial, i::wasm::kSpecMaxMemory32Pages)) {
return;
}
......
......@@ -20,9 +20,11 @@ namespace v8 {
namespace internal {
namespace wasm {
// This constant limits the amount of *declared* memory. At runtime, memory can
// only grow up to kV8MaxWasmMemoryPages.
constexpr size_t kSpecMaxMemoryPages = 65536;
// These constants limit the amount of *declared* memory. At runtime, memory can
// only grow up to kV8MaxWasmMemory{32,64}Pages.
constexpr size_t kSpecMaxMemory32Pages = 65536; // 4GB
// TODO(clemensb): Increase the maximum for memory64.
constexpr size_t kSpecMaxMemory64Pages = 65536; // 4GB
// The following limits are imposed by V8 on WebAssembly modules.
// The limits are agreed upon with other engines for consistency.
......@@ -35,13 +37,17 @@ constexpr size_t kV8MaxWasmTags = 1000000;
constexpr size_t kV8MaxWasmExceptionTypes = 1000000;
constexpr size_t kV8MaxWasmDataSegments = 100000;
// This indicates the maximum memory size our implementation supports.
// 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.
// Do not use this limit directly; use {max_mem{32,64}_pages()} instead to take
// the spec'ed limit as well as command line flag into account.
// Also, do not use this limit to validate declared memory, use
// kSpecMaxMemoryPages for that.
constexpr size_t kV8MaxWasmMemoryPages = kSystemPointerSize == 4
? 32767 // = 2 GiB - 64Kib
: 65536; // = 4 GiB
// kSpecMaxMemory{32,64}Pages for that.
constexpr size_t kV8MaxWasmMemory32Pages = kSystemPointerSize == 4
? 32767 // = 2 GiB - 64Kib
: 65536; // = 4 GiB
// TODO(clemensb): Increase the maximum for memory64.
constexpr size_t kV8MaxWasmMemory64Pages = kSystemPointerSize == 4
? 32767 // = 2 GiB - 64Kib
: 65536; // = 4 GiB
constexpr size_t kV8MaxWasmStringSize = 100000;
constexpr size_t kV8MaxWasmModuleSize = 1024 * 1024 * 1024; // = 1 GiB
constexpr size_t kV8MaxWasmFunctionSize = 7654321;
......@@ -75,14 +81,21 @@ constexpr uint64_t kWasmMaxHeapOffset =
// 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();
// Maximum number of pages we can allocate, for memory32 and memory64. This
// might be lower than the number of pages that can be declared (e.g. as
// maximum): kSpecMaxMemory{32,64}Pages.
// Even for 64-bit memory, the number of pages is still a 32-bit number for now,
// which allows for up to 128 TB memories (2**31 * 64k).
static_assert(kV8MaxWasmMemory64Pages <= kMaxUInt32);
V8_EXPORT_PRIVATE uint32_t max_mem32_pages();
V8_EXPORT_PRIVATE uint32_t max_mem64_pages();
inline uint64_t max_mem32_bytes() {
return uint64_t{max_mem32_pages()} * kWasmPageSize;
}
inline uint64_t max_mem_bytes() {
return uint64_t{max_mem_pages()} * kWasmPageSize;
inline uint64_t max_mem64_bytes() {
return uint64_t{max_mem64_pages()} * kWasmPageSize;
}
V8_EXPORT_PRIVATE uint32_t max_table_init_entries();
......
......@@ -814,7 +814,8 @@ void SetInstanceMemory(Handle<WasmInstanceObject> instance,
} // namespace
MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(
Isolate* isolate, Handle<JSArrayBuffer> buffer, int maximum) {
Isolate* isolate, Handle<JSArrayBuffer> buffer, int maximum,
MemoryIndexType index_type) {
Handle<JSFunction> memory_ctor(
isolate->native_context()->wasm_memory_constructor(), isolate);
......@@ -822,6 +823,7 @@ MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(
isolate->factory()->NewJSObject(memory_ctor, AllocationType::kOld));
memory_object->set_array_buffer(*buffer);
memory_object->set_maximum_pages(maximum);
memory_object->set_is_memory64(index_type == MemoryIndexType::kMemory64);
if (buffer->is_shared()) {
auto backing_store = buffer->GetBackingStore();
......@@ -836,19 +838,22 @@ MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(
return memory_object;
}
MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate,
int initial, int maximum,
SharedFlag shared) {
MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(
Isolate* isolate, int initial, int maximum, SharedFlag shared,
MemoryIndexType index_type) {
bool has_maximum = maximum != kNoMaximum;
int heuristic_maximum = maximum;
if (!has_maximum) {
heuristic_maximum = static_cast<int>(wasm::max_mem_pages());
}
int engine_maximum = index_type == MemoryIndexType::kMemory64
? static_cast<int>(wasm::max_mem64_pages())
: static_cast<int>(wasm::max_mem32_pages());
if (initial > engine_maximum) return {};
#ifdef V8_TARGET_ARCH_32_BIT
// On 32-bit platforms we need an heuristic here to balance overall memory
// and address space consumption.
constexpr int kGBPages = 1024 * 1024 * 1024 / wasm::kWasmPageSize;
int heuristic_maximum;
if (initial > kGBPages) {
// We always allocate at least the initial size.
heuristic_maximum = initial;
......@@ -863,6 +868,9 @@ MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate,
// and then grow with realloc.
heuristic_maximum = initial;
}
#else
int heuristic_maximum =
has_maximum ? std::min(engine_maximum, maximum) : engine_maximum;
#endif
auto backing_store = BackingStore::AllocateWasmMemory(
......@@ -933,10 +941,11 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate,
size_t old_size = old_buffer->byte_length();
DCHECK_EQ(0, old_size % wasm::kWasmPageSize);
size_t old_pages = old_size / wasm::kWasmPageSize;
uint32_t max_pages = wasm::kSpecMaxMemoryPages;
size_t max_pages = memory_object->is_memory64() ? wasm::max_mem64_pages()
: wasm::max_mem32_pages();
if (memory_object->has_maximum_pages()) {
DCHECK_GE(max_pages, memory_object->maximum_pages());
max_pages = static_cast<uint32_t>(memory_object->maximum_pages());
max_pages = std::min(max_pages,
static_cast<size_t>(memory_object->maximum_pages()));
}
DCHECK_GE(max_pages, old_pages);
if (pages > max_pages - old_pages) return -1;
......@@ -997,7 +1006,7 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate,
// These numbers are kept small because we must be careful about address
// space consumption on 32-bit platforms.
size_t min_growth = old_pages + 8 + (old_pages >> 3);
size_t new_capacity = std::max(new_pages, min_growth);
size_t new_capacity = std::clamp(new_pages, min_growth, max_pages);
std::unique_ptr<BackingStore> new_backing_store =
backing_store->CopyWasmMemory(isolate, new_pages, new_capacity);
if (!new_backing_store) {
......@@ -1152,7 +1161,8 @@ bool WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
}
void WasmInstanceObject::SetRawMemory(byte* mem_start, size_t mem_size) {
CHECK_LE(mem_size, wasm::max_mem_bytes());
CHECK_LE(mem_size, module()->is_memory64 ? wasm::max_mem64_bytes()
: wasm::max_mem32_bytes());
set_memory_start(mem_start);
set_memory_size(mem_size);
}
......@@ -1188,7 +1198,6 @@ Handle<WasmInstanceObject> WasmInstanceObject::New(
isolate->factory()->NewFixedArray(num_imported_functions);
instance->set_imported_function_refs(*imported_function_refs);
instance->SetRawMemory(reinterpret_cast<byte*>(EmptyBackingStoreBuffer()), 0);
instance->set_isolate_root(isolate->isolate_root());
instance->set_stack_limit_address(
isolate->stack_guard()->address_of_jslimit());
......@@ -1220,6 +1229,7 @@ Handle<WasmInstanceObject> WasmInstanceObject::New(
instance->set_tiering_budget_array(
module_object->native_module()->tiering_budget_array());
instance->set_break_on_entry(module_object->script().break_on_entry());
instance->SetRawMemory(reinterpret_cast<byte*>(EmptyBackingStoreBuffer()), 0);
// Insert the new instance into the scripts weak list of instances. This list
// is used for breakpoints affecting all instances belonging to the script.
......
......@@ -253,6 +253,9 @@ class WasmTableObject
class WasmMemoryObject
: public TorqueGeneratedWasmMemoryObject<WasmMemoryObject, JSObject> {
public:
// Whether this memory object is a 64-bit memory.
enum MemoryIndexType { kMemory32, kMemory64 };
DECL_OPTIONAL_ACCESSORS(instances, WeakArrayList)
// Add an instance to the internal (weak) list.
......@@ -262,12 +265,13 @@ class WasmMemoryObject
inline bool has_maximum_pages();
V8_EXPORT_PRIVATE static MaybeHandle<WasmMemoryObject> New(
Isolate* isolate, Handle<JSArrayBuffer> buffer, int maximum);
Isolate* isolate, Handle<JSArrayBuffer> buffer, int maximum,
MemoryIndexType index_type = MemoryIndexType::kMemory32);
V8_EXPORT_PRIVATE static MaybeHandle<WasmMemoryObject> New(Isolate* isolate,
int initial,
int maximum,
SharedFlag shared);
V8_EXPORT_PRIVATE static MaybeHandle<WasmMemoryObject> New(
Isolate* isolate, int initial, int maximum,
SharedFlag shared = SharedFlag::kNotShared,
MemoryIndexType index_type = MemoryIndexType::kMemory32);
static constexpr int kNoMaximum = -1;
......
......@@ -151,6 +151,7 @@ extern class WasmTableObject extends JSObject {
extern class WasmMemoryObject extends JSObject {
array_buffer: JSArrayBuffer;
maximum_pages: Smi;
is_memory64: Smi; // Boolean
instances: WeakArrayList|Undefined;
}
......
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