From 1ac5859f437ff0b1e9794a9ad0aac6688a6047b0 Mon Sep 17 00:00:00 2001 From: Cai Congcong Date: Thu, 12 Feb 2026 14:52:04 +0800 Subject: [PATCH 1/5] wip --- scripts/test/shared.py | 2 + src/binaryen-c.cpp | 3 + src/binaryen-c.h | 1 + src/ir/module-utils.cpp | 1 + src/js/binaryen.js-post.js | 1 + src/parser/context-decls.cpp | 1 + src/parser/contexts.h | 28 ++- src/parser/parsers.h | 45 +++- src/passes/LLVMMemoryCopyFillLowering.cpp | 45 ++-- src/passes/Memory64Lowering.cpp | 4 +- src/passes/MemoryPacking.cpp | 2 +- src/passes/MultiMemoryLowering.cpp | 64 ++++-- src/passes/Print.cpp | 5 + src/passes/RemoveUnusedModuleElements.cpp | 2 +- src/shell-interface.h | 2 +- src/tools/fuzzing/fuzzing.cpp | 9 +- src/tools/tool-options.h | 1 + src/tools/wasm-split/instrumenter.cpp | 20 +- src/wasm-binary.h | 18 +- src/wasm-builder.h | 13 +- src/wasm-features.h | 6 +- src/wasm-interpreter.h | 192 ++++++++++++------ src/wasm.h | 28 +-- src/wasm/wasm-binary.cpp | 63 +++++- src/wasm/wasm-validator.cpp | 16 +- src/wasm/wasm.cpp | 1 + src/wasm2js.h | 44 ++-- test/binaryen.js/kitchen-sink.js | 1 + test/binaryen.js/kitchen-sink.js.txt | 3 +- test/example/c-api-kitchen-sink.c | 2 + test/example/c-api-kitchen-sink.txt | 3 +- test/lit/help/wasm-as.test | 4 + test/lit/help/wasm-ctor-eval.test | 4 + test/lit/help/wasm-dis.test | 4 + test/lit/help/wasm-emscripten-finalize.test | 4 + test/lit/help/wasm-merge.test | 4 + test/lit/help/wasm-metadce.test | 4 + test/lit/help/wasm-opt.test | 4 + test/lit/help/wasm-reduce.test | 4 + test/lit/help/wasm-split.test | 4 + test/lit/help/wasm2js.test | 4 + .../lit/passes/memory-copy-fill-lowering.wast | 8 +- test/lit/passes/multi-memory-lowering.wast | 88 ++++---- .../multi-memory-lowering-export.wast | 20 +- .../multi-memory-lowering-import.wast | 20 +- ..._roundtrip_print-features_all-features.txt | 1 + test/spec/custom-page-sizes-invalid.wast | 115 +++++++++++ test/spec/custom-page-sizes.wast | 138 +++++++++++++ test/spec/memory_max.wast | 37 ++++ test/spec/memory_max_i64.wast | 39 ++++ test/unit/test_features.py | 1 + test/wasm2js/atomics_32.2asm.js | 2 +- test/wasm2js/atomics_32.2asm.js.opt | 2 +- test/wasm2js/bulk-memory.2asm.js | 14 +- test/wasm2js/bulk-memory.2asm.js.opt | 14 +- test/wasm2js/deterministic.2asm.js | 2 +- test/wasm2js/deterministic.2asm.js.opt | 2 +- test/wasm2js/dynamicLibrary.2asm.js | 2 +- test/wasm2js/dynamicLibrary.2asm.js.opt | 2 +- test/wasm2js/emscripten-grow-no.2asm.js | 2 +- test/wasm2js/emscripten-grow-no.2asm.js.opt | 2 +- test/wasm2js/emscripten-grow-yes.2asm.js | 4 +- test/wasm2js/emscripten-grow-yes.2asm.js.opt | 4 +- test/wasm2js/emscripten.2asm.js | 2 +- test/wasm2js/emscripten.2asm.js.opt | 2 +- test/wasm2js/endianness.2asm.js | 4 +- test/wasm2js/grow-memory-tricky.2asm.js | 4 +- test/wasm2js/grow-memory-tricky.2asm.js.opt | 4 +- test/wasm2js/grow_memory.2asm.js | 4 +- test/wasm2js/left-to-right.2asm.js | 4 +- test/wasm2js/minified-memory.2asm.js | 4 +- test/wasm2js/minified-memory.2asm.js.opt | 4 +- test/wasm2js/reinterpret_scratch.2asm.js | 2 +- test/wasm2js/reinterpret_scratch.2asm.js.opt | 2 +- test/wasm2js/start_func.2asm.js | 4 +- test/wasm2js/start_func.2asm.js.opt | 4 +- test/wasm2js/traps.2asm.js | 4 +- test/wasm2js/unaligned.2asm.js | 2 +- test/wasm2js/unaligned.2asm.js.opt | 2 +- 79 files changed, 933 insertions(+), 304 deletions(-) create mode 100644 test/spec/custom-page-sizes-invalid.wast create mode 100644 test/spec/custom-page-sizes.wast create mode 100644 test/spec/memory_max.wast create mode 100644 test/spec/memory_max_i64.wast diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 72a026fb595..369f9d19cac 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -388,6 +388,8 @@ def get_tests(test_dir, extensions=[], recursive=False): # Unlinkable module accepted 'linking.wast', + 'memory_max.wast', + 'memory_max_i64.wast', # Invalid module accepted 'unreached-invalid.wast', diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index efbf69d3de3..207c2a5ea57 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -502,6 +502,9 @@ BinaryenFeatures BinaryenFeatureCallIndirectOverlong(void) { BinaryenFeatures BinaryenFeatureRelaxedAtomics(void) { return static_cast(FeatureSet::RelaxedAtomics); } +BinaryenFeatures BinaryenFeatureCustomPageSizes(void) { + return static_cast(FeatureSet::CustomPageSizes); +} BinaryenFeatures BinaryenFeatureAll(void) { return static_cast(FeatureSet::All); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 806574f744e..0c1a27df3a0 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -244,6 +244,7 @@ BINARYEN_API BinaryenFeatures BinaryenFeatureFP16(void); BINARYEN_API BinaryenFeatures BinaryenFeatureBulkMemoryOpt(void); BINARYEN_API BinaryenFeatures BinaryenFeatureCallIndirectOverlong(void); BINARYEN_API BinaryenFeatures BinaryenFeatureRelaxedAtomics(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureCustomPageSizes(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void); // Modules diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index cc13101b717..8653e86f8b6 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -173,6 +173,7 @@ Memory* copyMemory(const Memory* memory, Module& out) { ret->hasExplicitName = memory->hasExplicitName; ret->initial = memory->initial; ret->max = memory->max; + ret->pageSizeLog2 = memory->pageSizeLog2; ret->shared = memory->shared; ret->addressType = memory->addressType; ret->module = memory->module; diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 0541b171f22..365df6ce4d5 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -191,6 +191,7 @@ function initializeConstants() { 'BulkMemoryOpt', 'CallIndirectOverlong', 'RelaxedAtomics', + 'CustomPageSizes', 'All' ].forEach(name => { Module['Features'][name] = Module['_BinaryenFeature' + name](); diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index f02ec30dd66..a98e3334a33 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -144,6 +144,7 @@ Result ParseDeclsCtx::addMemoryDecl(Index pos, m->initial = type.limits.initial; m->max = type.limits.max ? *type.limits.max : Memory::kUnlimitedSize; m->shared = type.shared; + m->pageSizeLog2 = type.pageSizeLog2; if (name) { // TODO: if the existing memory is not explicitly named, fix its name // and continue. diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 3978afff45d..8f2af907193 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -49,6 +49,7 @@ struct Limits { struct MemType { Type addressType; Limits limits; + uint8_t pageSizeLog2; bool shared; }; @@ -353,7 +354,9 @@ template struct TypeParserCtx { Result makeLimits(uint64_t, std::optional) { return Ok{}; } LimitsT getLimitsFromData(DataStringT) { return Ok{}; } - MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; } + MemTypeT makeMemType(Type, LimitsT, bool, std::optional) { + return Ok{}; + } HeapType getBlockTypeFromResult(const std::vector results) { assert(results.size() == 1); @@ -1072,13 +1075,20 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { data.insert(data.end(), str.begin(), str.end()); } - Limits getLimitsFromData(const std::vector& data) { - uint64_t size = (data.size() + Memory::kPageSize - 1) / Memory::kPageSize; + Limits getLimitsFromData(const std::vector& data, + std::optional pageSizeLog2) { + uint8_t _pageSizeLog2 = pageSizeLog2.value_or(16); + uint64_t size = + (data.size() + (1 << _pageSizeLog2) - 1) / (1 << _pageSizeLog2); return {size, size}; } - MemType makeMemType(Type addressType, Limits limits, bool shared) { - return {addressType, limits, shared}; + MemType makeMemType(Type addressType, + Limits limits, + bool shared, + std::optional pageSize) { + uint8_t pageSizeLog2 = pageSize.value_or(16); + return {addressType, limits, pageSizeLog2, shared}; } Result @@ -1447,8 +1457,12 @@ struct ParseModuleTypesCtx : TypeParserCtx, Type makeTableType(Type addressType, LimitsT, Type type) { return type; } - LimitsT getLimitsFromData(DataStringT) { return Ok{}; } - MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; } + LimitsT getLimitsFromData(DataStringT, std::optional) { + return Ok{}; + } + MemTypeT makeMemType(Type, LimitsT, bool, std::optional) { + return Ok{}; + } Result<> addFunc(Name name, const std::vector&, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index d606563f1c1..90c0f5a2e75 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -825,7 +825,34 @@ template Result limits64(Ctx& ctx) { return ctx.makeLimits(uint64_t(*n), m); } -// memtype ::= (limits32 | 'i32' limits32 | 'i64' limit64) shared? +// mempagesize? ::= ('(' 'pagesize' u64 ')') ? +template Result> mempagesize(Ctx& ctx) { + if (!ctx.in.takeSExprStart("pagesize"sv)) { + return std::nullopt; // No pagesize specified + } + auto pageSize = ctx.in.takeU64(); + if (!pageSize) { + return ctx.in.err("expected page size"); + } + + if (!Bits::isPowerOf2(*pageSize)) { + return ctx.in.err("page size must be a power of two"); + } + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of mempagesize"); + } + + uint8_t pageSizeLog2 = (uint8_t)Bits::ceilLog2(*pageSize); + + if (pageSizeLog2 != 0 && pageSizeLog2 != Memory::kDefaultPageSizeLog2) { + return ctx.in.err("memory page size can only be 1 or 64 KiB"); + } + + return std::make_optional(pageSizeLog2); +} + +// memtype ::= (limits32 | 'i32' limits32 | 'i64' limit64) shared? mempagesize? // note: the index type 'i32' or 'i64' is already parsed to simplify parsing of // memory abbreviations. template Result memtype(Ctx& ctx) { @@ -847,7 +874,9 @@ Result memtypeContinued(Ctx& ctx, Type addressType) { if (ctx.in.takeKeyword("shared"sv)) { shared = true; } - return ctx.makeMemType(addressType, *limits, shared); + auto pageSize = mempagesize(ctx); + CHECK_ERR(pageSize); + return ctx.makeMemType(addressType, *limits, shared, *pageSize); } // memorder ::= 'seqcst' | 'acqrel' @@ -3558,6 +3587,8 @@ template MaybeResult<> memory(Ctx& ctx) { std::optional mtype; std::optional data; + auto mempageSize = mempagesize(ctx); + CHECK_ERR(mempageSize); if (ctx.in.takeSExprStart("data"sv)) { if (import) { return ctx.in.err("imported memories cannot have inline data"); @@ -3567,9 +3598,15 @@ template MaybeResult<> memory(Ctx& ctx) { if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of inline data"); } - mtype = - ctx.makeMemType(addressType, ctx.getLimitsFromData(*datastr), false); + mtype = ctx.makeMemType(addressType, + ctx.getLimitsFromData(*datastr, *mempageSize), + false, + *mempageSize); data = *datastr; + } else if ((*mempageSize).has_value()) { + // If we have a memory page size not within a memtype expression, we expect + // a memory abbreviation. + return ctx.in.err("expected data segment in memory abbreviation"); } else { auto type = memtypeContinued(ctx, addressType); CHECK_ERR(type); diff --git a/src/passes/LLVMMemoryCopyFillLowering.cpp b/src/passes/LLVMMemoryCopyFillLowering.cpp index a2b29227c51..9ccf2934a5a 100644 --- a/src/passes/LLVMMemoryCopyFillLowering.cpp +++ b/src/passes/LLVMMemoryCopyFillLowering.cpp @@ -117,14 +117,17 @@ struct LLVMMemoryCopyFillLowering void createMemoryCopyFunc(Module* module) { Builder b(*module); Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; - Name memory = module->memories.front()->name; + Name memoryName = module->memories.front()->name; + Address::address32_t pageSizeLog2 = module->memories.front()->pageSizeLog2; Block* body = b.makeBlock(); // end = memory size in bytes - body->list.push_back( - b.makeLocalSet(end, - b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize)))); + body->list.push_back(b.makeLocalSet( + end, + pageSizeLog2 == 0 + ? static_cast(b.makeMemorySize(memoryName)) + : static_cast(b.makeBinary(BinaryOp::ShlInt32, + b.makeMemorySize(memoryName), + b.makeConst(pageSizeLog2))))); // if dst + size > memsize or src + size > memsize, then trap. body->list.push_back(b.makeIf( b.makeBinary(BinaryOp::OrInt32, @@ -187,9 +190,9 @@ struct LLVMMemoryCopyFillLowering b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), Type::i32, - memory), + memoryName), Type::i32, - memory), + memoryName), // i += step b.makeLocalSet(i, b.makeBinary(BinaryOp::AddInt32, @@ -203,19 +206,23 @@ struct LLVMMemoryCopyFillLowering void createMemoryFillFunc(Module* module) { Builder b(*module); Index dst = 0, val = 1, size = 2; - Name memory = module->memories.front()->name; + Name memoryName = module->memories.front()->name; + Address::address32_t pageSizeLog2 = module->memories.front()->pageSizeLog2; Block* body = b.makeBlock(); // if dst + size > memsize in bytes, then trap. - body->list.push_back( - b.makeIf(b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)), - b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize))), - b.makeUnreachable())); + body->list.push_back(b.makeIf( + b.makeBinary( + BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + pageSizeLog2 == 0 + ? static_cast(b.makeMemorySize(memoryName)) + : static_cast(b.makeBinary(BinaryOp::ShlInt32, + b.makeMemorySize(memoryName), + b.makeConst(pageSizeLog2)))), + b.makeUnreachable())); body->list.push_back(b.makeBlock( "out", @@ -241,7 +248,7 @@ struct LLVMMemoryCopyFillLowering b.makeLocalGet(size, Type::i32)), b.makeLocalGet(val, Type::i32), Type::i32, - memory), + memoryName), b.makeBreak("copy", nullptr)})))); module->getFunction(memFillFuncName)->body = body; } diff --git a/src/passes/Memory64Lowering.cpp b/src/passes/Memory64Lowering.cpp index 1506c016eea..76777df50d3 100644 --- a/src/passes/Memory64Lowering.cpp +++ b/src/passes/Memory64Lowering.cpp @@ -293,8 +293,8 @@ struct Memory64Lowering : public WalkerPass> { for (auto& memory : module->memories) { if (memory->is64()) { memory->addressType = Type::i32; - if (memory->hasMax() && memory->max > Memory::kMaxSize32) { - memory->max = Memory::kMaxSize32; + if (memory->hasMax() && memory->max > memory->maxSize32()) { + memory->max = memory->maxSize32(); } } } diff --git a/src/passes/MemoryPacking.cpp b/src/passes/MemoryPacking.cpp index b9acb32f92e..23cbeb58567 100644 --- a/src/passes/MemoryPacking.cpp +++ b/src/passes/MemoryPacking.cpp @@ -315,7 +315,7 @@ void MemoryPacking::calculateRanges(Module* module, // Check if we can rule out a trap by it being in bounds. if (auto* c = segment->offset->dynCast()) { auto* memory = module->getMemory(segment->memory); - auto memorySize = memory->initial * Memory::kPageSize; + auto memorySize = memory->initial << memory->pageSizeLog2; Index start = c->value.getUnsigned(); Index size = segment->data.size(); Index end; diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp index 9397f6cad44..5d0a2f19137 100644 --- a/src/passes/MultiMemoryLowering.cpp +++ b/src/passes/MultiMemoryLowering.cpp @@ -68,9 +68,11 @@ struct MultiMemoryLowering : public Pass { // properties will be set Name module; Name base; - // The initial page size of the combined memory + // The page size of the combined memory + uint8_t pageSizeLog2; + // The initial page count of the combined memory Address totalInitialPages; - // The max page size of the combined memory + // The max page count of the combined memory Address totalMaxPages; // There is no offset for the first memory, so offsetGlobalNames will always // have a size that is one less than the count of memories at the time this @@ -435,6 +437,7 @@ struct MultiMemoryLowering : public Pass { : Builder::MemoryInfo::Memory64; isShared = getFirstMemory().shared; isImported = getFirstMemory().imported(); + pageSizeLog2 = Memory::kDefaultPageSizeLog2; for (auto& memory : wasm->memories) { // We are assuming that each memory is configured the same as the first // and assert if any of the memories does not match this configuration @@ -446,18 +449,25 @@ struct MultiMemoryLowering : public Pass { Fatal() << "MultiMemoryLowering: only the first memory can be imported"; } + // Calculating the page size of the combined memory. + // This corresponds to the smaller granularity among combined memories + pageSizeLog2 = std::min(pageSizeLog2, memory->pageSizeLog2); + // Calculating the total initial and max page size for the combined memory // by totaling the initial and max page sizes for the memories in the // module - totalInitialPages = totalInitialPages + memory->initial; + totalInitialPages = + totalInitialPages + + (memory->initial << (memory->pageSizeLog2 - pageSizeLog2)); if (memory->hasMax()) { - totalMaxPages = totalMaxPages + memory->max; + totalMaxPages = totalMaxPages + + (memory->max << (memory->pageSizeLog2 - pageSizeLog2)); } } // Ensuring valid initial and max page sizes that do not exceed the number // of pages addressable by the pointerType - Address maxSize = - pointerType == Type::i32 ? Memory::kMaxSize32 : Memory::kMaxSize64; + Address maxSize = pointerType == Type::i32 ? 1ull << (32 - pageSizeLog2) + : 1ull << (64 - pageSizeLog2); if (totalMaxPages > maxSize || totalMaxPages == 0) { totalMaxPages = Memory::kUnlimitedSize; } @@ -504,9 +514,10 @@ struct MultiMemoryLowering : public Pass { Name name = Names::getValidGlobalName( *wasm, memory->name.toString() + "_byte_offset"); offsetGlobalNames.push_back(std::move(name)); - addGlobal(name, offsetRunningTotal * Memory::kPageSize); + addGlobal(name, offsetRunningTotal << pageSizeLog2); } - offsetRunningTotal += memory->initial; + offsetRunningTotal += memory->initial + << (memory->pageSizeLog2 - pageSizeLog2); } } @@ -553,13 +564,18 @@ struct MultiMemoryLowering : public Pass { auto function = Builder::makeFunction( functionName, Signature(pointerType, pointerType), {}); function->setLocalName(0, "page_delta"); + auto currPageSizeLog2 = wasm->memories[memIdx]->pageSizeLog2; auto pageSizeConst = [&]() { - return builder.makeConst(Literal(Memory::kPageSize)); + return builder.makeConst(Literal(currPageSizeLog2)); }; auto getOffsetDelta = [&]() { - return builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Mul), - builder.makeLocalGet(0, pointerType), - pageSizeConst()); + if (currPageSizeLog2 == 0) { + return static_cast(builder.makeLocalGet(0, pointerType)); + } + return static_cast( + builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Shl), + builder.makeLocalGet(0, pointerType), + pageSizeConst())); }; auto getMoveSource = [&](Name global) { return builder.makeGlobalGet(global, pointerType); @@ -588,7 +604,14 @@ struct MultiMemoryLowering : public Pass { builder.makeBinary( EqInt32, builder.makeMemoryGrow( - builder.makeLocalGet(0, pointerType), combinedMemory, memoryInfo), + currPageSizeLog2 - pageSizeLog2 == 0 + ? static_cast(builder.makeLocalGet(0, pointerType)) + : static_cast(builder.makeBinary( + Abstract::getBinary(pointerType, Abstract::Shl), + builder.makeLocalGet(0, pointerType), + builder.makeConst(Literal(currPageSizeLog2 - pageSizeLog2)))), + combinedMemory, + memoryInfo), builder.makeConst(-1)), builder.makeReturn(builder.makeConst(-1)))); @@ -609,9 +632,13 @@ struct MultiMemoryLowering : public Pass { // size builder.makeBinary( Abstract::getBinary(pointerType, Abstract::Sub), - builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Mul), - builder.makeLocalGet(sizeLocal, pointerType), - pageSizeConst()), + currPageSizeLog2 == 0 + ? static_cast( + builder.makeLocalGet(sizeLocal, pointerType)) + : static_cast(builder.makeBinary( + Abstract::getBinary(pointerType, Abstract::Shl), + builder.makeLocalGet(sizeLocal, pointerType), + pageSizeConst())), getMoveSource(offsetGlobalName)), combinedMemory, combinedMemory)); @@ -646,11 +673,11 @@ struct MultiMemoryLowering : public Pass { functionName, Signature(Type::none, pointerType), {}); Expression* functionBody; auto pageSizeConst = [&]() { - return builder.makeConst(Literal(Memory::kPageSize)); + return builder.makeConst(Literal(pageSizeLog2)); }; auto getOffsetInPageUnits = [&](Name global) { return builder.makeBinary( - Abstract::getBinary(pointerType, Abstract::DivU), + Abstract::getBinary(pointerType, Abstract::ShrU), builder.makeGlobalGet(global, pointerType), pageSizeConst()); }; @@ -697,6 +724,7 @@ struct MultiMemoryLowering : public Pass { memory->base = base; memory->module = module; } + memory->pageSizeLog2 = pageSizeLog2; wasm->addMemory(std::move(memory)); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index d92a412dbbd..4878b95fffb 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3468,6 +3468,11 @@ void PrintSExpression::printMemoryHeader(Memory* curr) { if (curr->shared) { printMedium(o, " shared"); } + if (curr->pageSizeLog2 != Memory::kDefaultPageSizeLog2) { + o << " ("; + printMedium(o, "pagesize") << ' ' << (1 << (curr->pageSizeLog2)); + o << ')'; + } o << ")"; } diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 64f56744624..a87414c85ac 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -842,7 +842,7 @@ struct RemoveUnusedModuleElements : public Pass { segment->data.size(), segment->offset, memory, - memory->initial * Memory::kPageSize); + memory->initial << memory->pageSizeLog2); } }); ModuleUtils::iterActiveElementSegments( diff --git a/src/shell-interface.h b/src/shell-interface.h index 615000a7fba..9a16499f4e3 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -111,7 +111,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { void init(Module& wasm, ModuleRunner& instance) override { ModuleUtils::iterDefinedMemories(wasm, [&](wasm::Memory* memory) { auto shellMemory = Memory(); - shellMemory.resize(memory->initial * wasm::Memory::kPageSize); + shellMemory.resize(memory->initial << memory->pageSizeLog2); memories[memory->name] = shellMemory; }); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index aa16e2b7833..06cb14ee8bb 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -839,9 +839,10 @@ void TranslateToFuzzReader::finalizeMemory() { // annoying in the fuzzer). Address ONE_GB = 1024 * 1024 * 1024; if (maxOffset <= ONE_GB) { - memory->initial = std::max( - memory->initial, - Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize)); + memory->initial = + std::max(memory->initial, + Address((maxOffset + (1 << memory->pageSizeLog2) - 1) / + (1 << memory->pageSizeLog2))); } } memory->initial = std::max(memory->initial, fuzzParams->USABLE_MEMORY); @@ -855,7 +856,7 @@ void TranslateToFuzzReader::finalizeMemory() { // maximum larger than the initial. // TODO: scan the wasm for grow instructions? memory->max = - std::min(Address(memory->initial + 1), Address(Memory::kMaxSize32)); + std::min(Address(memory->initial + 1), Address(memory->maxSize32())); } if (!preserveImportsAndExports) { diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 298fb5db349..164ae38564d 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -110,6 +110,7 @@ struct ToolOptions : public Options { "custom descriptors (RTTs) and exact references") .addFeature(FeatureSet::RelaxedAtomics, "acquire/release atomic memory operations") + .addFeature(FeatureSet::CustomPageSizes, "custom page sizes") .add("--enable-typed-function-references", "", "Deprecated compatibility flag", diff --git a/src/tools/wasm-split/instrumenter.cpp b/src/tools/wasm-split/instrumenter.cpp index 92861a8729a..6cc783a7a8a 100644 --- a/src/tools/wasm-split/instrumenter.cpp +++ b/src/tools/wasm-split/instrumenter.cpp @@ -81,7 +81,10 @@ void Instrumenter::addSecondaryMemory(size_t numFuncs) { secondaryMemory = Names::getValidMemoryName(*wasm, config.secondaryMemoryName); // Create a memory with enough pages to write into - size_t pages = (numFuncs + Memory::kPageSize - 1) / Memory::kPageSize; + // The memory uses the default page size to avoid issues in case custom page + // sizes are not supported + size_t pages = + (numFuncs + Memory::kDefaultPageSize - 1) / Memory::kDefaultPageSize; auto mem = Builder::makeMemory(secondaryMemory, pages, pages, true); mem->module = config.importNamespace; mem->base = config.secondaryMemoryName; @@ -184,15 +187,20 @@ void Instrumenter::addProfileExport(size_t numFuncs) { const size_t profileSize = 8 + 4 * numFuncs; // Make sure there is a memory with enough pages to write into - size_t pages = (profileSize + Memory::kPageSize - 1) / Memory::kPageSize; if (wasm->memories.empty()) { + size_t pages = + (profileSize + Memory::kDefaultPageSize - 1) / Memory::kDefaultPageSize; wasm->addMemory(Builder::makeMemory("0")); wasm->memories[0]->initial = pages; wasm->memories[0]->max = pages; - } else if (wasm->memories[0]->initial < pages) { - wasm->memories[0]->initial = pages; - if (wasm->memories[0]->max < pages) { - wasm->memories[0]->max = pages; + } else { + size_t pages = (profileSize + (1 << wasm->memories[0]->pageSizeLog2) - 1) / + (1 << wasm->memories[0]->pageSizeLog2); + if (wasm->memories[0]->initial < pages) { + wasm->memories[0]->initial = pages; + if (wasm->memories[0]->max < pages) { + wasm->memories[0]->max = pages; + } } } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 3bd3199b67a..0d1841dbca7 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -461,6 +461,7 @@ extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; extern const char* CustomDescriptorsFeature; extern const char* RelaxedAtomicsFeature; +extern const char* CustomPageSizesFeature; enum Subsection { NameModule = 0, @@ -1264,7 +1265,12 @@ enum MemoryAccess { NaturalAlignment = 0 }; -enum MemoryFlags { HasMaximum = 1 << 0, IsShared = 1 << 1, Is64 = 1 << 2 }; +enum MemoryFlags { + HasMaximum = 1 << 0, + IsShared = 1 << 1, + Is64 = 1 << 2, + HasCustomPageSize = 1 << 3 +}; enum FeaturePrefix { FeatureUsed = '+', FeatureDisallowed = '-' }; @@ -1378,8 +1384,13 @@ class WasmBinaryWriter { void write(); void writeHeader(); int32_t writeU32LEBPlaceholder(); - void writeResizableLimits( - Address initial, Address maximum, bool hasMaximum, bool shared, bool is64); + void writeResizableLimits(Address initial, + Address maximum, + bool hasMaximum, + bool shared, + bool is64, + bool hasPageSize, + Address::address32_t pageSizeLog2); template int32_t startSection(T code); void finishSection(int32_t start); int32_t startSubsection(BinaryConsts::CustomSections::Subsection code); @@ -1635,6 +1646,7 @@ class WasmBinaryReader { Address& max, bool& shared, Type& addressType, + uint8_t& pageSizeLog2, Address defaultIfNoMax); void readImports(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index cd73babb6ca..dc144d3af42 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -130,15 +130,18 @@ class Builder { return seg; } - static std::unique_ptr makeMemory(Name name, - Address initial = 0, - Address max = Memory::kMaxSize32, - bool shared = false, - Type addressType = Type::i32) { + static std::unique_ptr + makeMemory(Name name, + Address initial = 0, + Address max = Memory::kDefaultMaxSize32, + bool shared = false, + uint8_t pageSizeLog2 = Memory::kDefaultPageSizeLog2, + Type addressType = Type::i32) { auto memory = std::make_unique(); memory->name = name; memory->initial = initial; memory->max = max; + memory->pageSizeLog2 = pageSizeLog2; memory->shared = shared; memory->addressType = addressType; return memory; diff --git a/src/wasm-features.h b/src/wasm-features.h index 7f4b0a451af..adfa1e1a30b 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -56,11 +56,12 @@ struct FeatureSet { CallIndirectOverlong = 1 << 20, CustomDescriptors = 1 << 21, RelaxedAtomics = 1 << 22, + CustomPageSizes = 1 << 23, MVP = None, // Keep in sync with llvm default features: // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153 Default = SignExt | MutableGlobals, - All = (1 << 23) - 1, + All = (1 << 24) - 1, }; static std::string toString(Feature f) { @@ -111,6 +112,8 @@ struct FeatureSet { return "custom-descriptors"; case RelaxedAtomics: return "relaxed-atomics"; + case CustomPageSizes: + return "custom-page-sizes"; case MVP: case Default: case All: @@ -172,6 +175,7 @@ struct FeatureSet { return (features & CustomDescriptors) != 0; } bool hasRelaxedAtomics() const { return (features & RelaxedAtomics) != 0; } + bool hasCustomPageSizes() const { return (features & CustomPageSizes) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 20578e03089..49d72f79958 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3647,6 +3647,15 @@ class ModuleRunnerBase : public ExpressionRunner { iter->second = size; } + Address::address64_t getMemoryPageSize(Name memory) { + Memory* mem = wasm.getMemoryOrNull(memory); + if (!mem) { + externalInterface->trap( + "getMemoryPageSize called on non-existing memory"); + } + return mem->pageSize(); + } + public: class FunctionScope { public: @@ -4017,10 +4026,12 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(flow, curr->ptr) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - auto addr = - info.instance->getFinalAddress(curr, flow.getSingleValue(), memorySize); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + auto addr = info.instance->getFinalAddress( + curr, flow.getSingleValue(), memorySize, memoryPageSize); if (curr->isAtomic()) { - info.instance->checkAtomicAddress(addr, curr->bytes, memorySize); + info.instance->checkAtomicAddress( + addr, curr->bytes, memorySize, memoryPageSize); } auto ret = info.interface()->load(curr, addr, info.name); return ret; @@ -4030,10 +4041,12 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(value, curr->value) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - auto addr = - info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + auto addr = info.instance->getFinalAddress( + curr, ptr.getSingleValue(), memorySize, memoryPageSize); if (curr->isAtomic()) { - info.instance->checkAtomicAddress(addr, curr->bytes, memorySize); + info.instance->checkAtomicAddress( + addr, curr->bytes, memorySize, memoryPageSize); } info.interface()->store(curr, addr, value.getSingleValue(), info.name); return Flow(); @@ -4044,10 +4057,16 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(value, curr->value) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - auto addr = - info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize); - auto loaded = info.instance->doAtomicLoad( - addr, curr->bytes, curr->type, info.name, memorySize, curr->order); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + auto addr = info.instance->getFinalAddress( + curr, ptr.getSingleValue(), memorySize, memoryPageSize); + auto loaded = info.instance->doAtomicLoad(addr, + curr->bytes, + curr->type, + info.name, + memorySize, + memoryPageSize, + curr->order); auto computed = value.getSingleValue(); switch (curr->op) { case RMWAdd: @@ -4069,7 +4088,7 @@ class ModuleRunnerBase : public ExpressionRunner { break; } info.instance->doAtomicStore( - addr, curr->bytes, computed, info.name, memorySize); + addr, curr->bytes, computed, info.name, memorySize, memoryPageSize); return loaded; } Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { @@ -4078,14 +4097,24 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(replacement, curr->replacement) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - auto addr = - info.instance->getFinalAddress(curr, ptr.getSingleValue(), memorySize); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + auto addr = info.instance->getFinalAddress( + curr, ptr.getSingleValue(), memorySize, memoryPageSize); expected = Flow(wrapToSmallerSize(expected.getSingleValue(), curr->bytes)); - auto loaded = info.instance->doAtomicLoad( - addr, curr->bytes, curr->type, info.name, memorySize, curr->order); + auto loaded = info.instance->doAtomicLoad(addr, + curr->bytes, + curr->type, + info.name, + memorySize, + memoryPageSize, + curr->order); if (loaded == expected.getSingleValue()) { - info.instance->doAtomicStore( - addr, curr->bytes, replacement.getSingleValue(), info.name, memorySize); + info.instance->doAtomicStore(addr, + curr->bytes, + replacement.getSingleValue(), + info.name, + memorySize, + memoryPageSize); } return loaded; } @@ -4096,13 +4125,15 @@ class ModuleRunnerBase : public ExpressionRunner { auto bytes = curr->expectedType.getByteSize(); auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); auto addr = info.instance->getFinalAddress( - curr, ptr.getSingleValue(), bytes, memorySize); + curr, ptr.getSingleValue(), bytes, memorySize, memoryPageSize); auto loaded = info.instance->doAtomicLoad(addr, bytes, curr->expectedType, info.name, memorySize, + memoryPageSize, MemoryOrder::SeqCst); if (loaded != expected.getSingleValue()) { return Literal(int32_t(1)); // not equal @@ -4122,10 +4153,11 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(count, curr->notifyCount) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - auto addr = - info.instance->getFinalAddress(curr, ptr.getSingleValue(), 4, memorySize); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + auto addr = info.instance->getFinalAddress( + curr, ptr.getSingleValue(), 4, memorySize, memoryPageSize); // Just check TODO actual threads support - info.instance->checkAtomicAddress(addr, 4, memorySize); + info.instance->checkAtomicAddress(addr, 4, memorySize, memoryPageSize); return Literal(int32_t(0)); // none woken up } Flow visitSIMDLoad(SIMDLoad* curr) { @@ -4207,12 +4239,13 @@ class ModuleRunnerBase : public ExpressionRunner { WASM_UNREACHABLE("invalid op"); }; auto memorySize = info.instance->getMemorySize(info.name); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); auto addressType = curr->ptr->type; auto fillLanes = [&](auto lanes, size_t laneBytes) { for (auto& lane : lanes) { auto ptr = Literal::makeFromInt64(src, addressType); - lane = loadLane( - info.instance->getFinalAddress(curr, ptr, laneBytes, memorySize)); + lane = loadLane(info.instance->getFinalAddress( + curr, ptr, laneBytes, memorySize, memoryPageSize)); src = ptr.add(Literal::makeFromInt32(laneBytes, addressType)).getUnsigned(); } @@ -4243,8 +4276,12 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(flow, curr->ptr) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - Address src = info.instance->getFinalAddress( - curr, flow.getSingleValue(), curr->getMemBytes(), memorySize); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + Address src = info.instance->getFinalAddress(curr, + flow.getSingleValue(), + curr->getMemBytes(), + memorySize, + memoryPageSize); auto zero = Literal::makeZero(curr->op == Load32ZeroVec128 ? Type::i32 : Type::i64); if (curr->op == Load32ZeroVec128) { @@ -4260,8 +4297,12 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(vecFlow, curr->vec) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - Address addr = info.instance->getFinalAddress( - curr, ptrFlow.getSingleValue(), curr->getMemBytes(), memorySize); + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + Address addr = info.instance->getFinalAddress(curr, + ptrFlow.getSingleValue(), + curr->getMemBytes(), + memorySize, + memoryPageSize); Literal vec = vecFlow.getSingleValue(); switch (curr->op) { case Load8LaneVec128: @@ -4329,7 +4370,7 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(flow, curr->delta) auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - auto* memory = info.instance->wasm.getMemory(info.name); + Memory* memory = info.instance->wasm.getMemory(info.name); auto addressType = memory->addressType; auto fail = Literal::makeFromInt64(-1, addressType); Flow ret = Literal::makeFromInt64(memorySize, addressType); @@ -4337,7 +4378,8 @@ class ModuleRunnerBase : public ExpressionRunner { uint64_t maxAddr = addressType == Type::i32 ? std::numeric_limits::max() : std::numeric_limits::max(); - if (delta > maxAddr / Memory::kPageSize) { + Address::address64_t pageSizeLog2 = memory->pageSizeLog2; + if (delta > (maxAddr >> pageSizeLog2)) { // Impossible to grow this much. return fail; } @@ -4349,9 +4391,8 @@ class ModuleRunnerBase : public ExpressionRunner { if (newSize > memory->max) { return fail; } - if (!info.interface()->growMemory(info.name, - memorySize * Memory::kPageSize, - newSize * Memory::kPageSize)) { + if (!info.interface()->growMemory( + info.name, (memorySize << pageSizeLog2), (newSize << pageSizeLog2))) { // We failed to grow the memory in practice, even though it was valid // to try to do so. return fail; @@ -4379,15 +4420,16 @@ class ModuleRunnerBase : public ExpressionRunner { } auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); - if (destVal + sizeVal > memorySize * Memory::kPageSize) { + auto memoryPageSize = info.instance->getMemoryPageSize(info.name); + if (destVal + sizeVal > (memorySize * memoryPageSize)) { trap("out of bounds memory access in memory.init"); } for (size_t i = 0; i < sizeVal; ++i) { Literal addr(destVal + i); - info.interface()->store8( - info.instance->getFinalAddressWithoutOffset(addr, 1, memorySize), - segment->data[offsetVal + i], - info.name); + info.interface()->store8(info.instance->getFinalAddressWithoutOffset( + addr, 1, memorySize, memoryPageSize), + segment->data[offsetVal + i], + info.name); } return {}; } @@ -4406,9 +4448,13 @@ class ModuleRunnerBase : public ExpressionRunner { auto destInfo = getMemoryInstanceInfo(curr->destMemory); auto sourceInfo = getMemoryInstanceInfo(curr->sourceMemory); auto destMemorySize = destInfo.instance->getMemorySize(destInfo.name); + auto const destMemoryPageSize = + destInfo.instance->getMemoryPageSize(destInfo.name); auto sourceMemorySize = sourceInfo.instance->getMemorySize(sourceInfo.name); - if (sourceVal + sizeVal > sourceMemorySize * Memory::kPageSize || - destVal + sizeVal > destMemorySize * Memory::kPageSize || + auto const sourceMemoryPageSize = + sourceInfo.instance->getMemoryPageSize(sourceInfo.name); + if (sourceVal + sizeVal > (sourceMemorySize * sourceMemoryPageSize) || + destVal + sizeVal > (destMemorySize * destMemoryPageSize) || // FIXME: better/cheaper way to detect wrapping? sourceVal + sizeVal < sourceVal || sourceVal + sizeVal < sizeVal || destVal + sizeVal < destVal || destVal + sizeVal < sizeVal) { @@ -4427,10 +4473,10 @@ class ModuleRunnerBase : public ExpressionRunner { for (int64_t i = start; i != end; i += step) { destInfo.interface()->store8( destInfo.instance->getFinalAddressWithoutOffset( - Literal(destVal + i), 1, destMemorySize), + Literal(destVal + i), 1, destMemorySize, destMemoryPageSize), sourceInfo.interface()->load8s( sourceInfo.instance->getFinalAddressWithoutOffset( - Literal(sourceVal + i), 1, sourceMemorySize), + Literal(sourceVal + i), 1, sourceMemorySize, sourceMemoryPageSize), sourceInfo.name), destInfo.name); } @@ -4445,18 +4491,20 @@ class ModuleRunnerBase : public ExpressionRunner { auto info = getMemoryInstanceInfo(curr->memory); auto memorySize = info.instance->getMemorySize(info.name); + auto const memoryPageSize = info.instance->getMemoryPageSize(info.name); // FIXME: cheaper wrapping detection? - if (destVal > memorySize * Memory::kPageSize || - sizeVal > memorySize * Memory::kPageSize || - destVal + sizeVal > memorySize * Memory::kPageSize) { + if (destVal > memorySize * memoryPageSize || + sizeVal > memorySize * memoryPageSize || + destVal + sizeVal > memorySize * memoryPageSize) { trap("out of bounds memory access in memory.fill"); } uint8_t val(value.getSingleValue().geti32()); for (size_t i = 0; i < sizeVal; ++i) { - info.interface()->store8(info.instance->getFinalAddressWithoutOffset( - Literal(destVal + i), 1, memorySize), - val, - info.name); + info.interface()->store8( + info.instance->getFinalAddressWithoutOffset( + Literal(destVal + i), 1, memorySize, memoryPageSize), + val, + info.name); } return {}; } @@ -5144,37 +5192,51 @@ class ModuleRunnerBase : public ExpressionRunner { } template - Address - getFinalAddress(LS* curr, Literal ptr, Index bytes, Address memorySize) { - Address memorySizeBytes = memorySize * Memory::kPageSize; + Address getFinalAddress(LS* curr, + Literal ptr, + Index bytes, + Address memorySize, + Address::address64_t memoryPageSize) { + Address memorySizeBytes = memorySize * memoryPageSize; uint64_t addr = ptr.type == Type::i32 ? ptr.geti32() : ptr.geti64(); trapIfGt(curr->offset, memorySizeBytes, "offset > memory"); trapIfGt(addr, memorySizeBytes - curr->offset, "final > memory"); addr += curr->offset; trapIfGt(bytes, memorySizeBytes, "bytes > memory"); - checkLoadAddress(addr, bytes, memorySize); + checkLoadAddress(addr, bytes, memorySize, memoryPageSize); return addr; } template - Address getFinalAddress(LS* curr, Literal ptr, Address memorySize) { - return getFinalAddress(curr, ptr, curr->bytes, memorySize); + Address getFinalAddress(LS* curr, + Literal ptr, + Address memorySize, + Address::address64_t memoryPageSize) { + return getFinalAddress(curr, ptr, curr->bytes, memorySize, memoryPageSize); } - Address - getFinalAddressWithoutOffset(Literal ptr, Index bytes, Address memorySize) { + Address getFinalAddressWithoutOffset(Literal ptr, + Index bytes, + Address memorySize, + Address::address64_t memoryPageSize) { uint64_t addr = ptr.type == Type::i32 ? ptr.geti32() : ptr.geti64(); - checkLoadAddress(addr, bytes, memorySize); + checkLoadAddress(addr, bytes, memorySize, memoryPageSize); return addr; } - void checkLoadAddress(Address addr, Index bytes, Address memorySize) { - Address memorySizeBytes = memorySize * Memory::kPageSize; + void checkLoadAddress(Address addr, + Index bytes, + Address memorySize, + Address::address64_t memoryPageSize) { + Address memorySizeBytes = memorySize * memoryPageSize; trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); } - void checkAtomicAddress(Address addr, Index bytes, Address memorySize) { - checkLoadAddress(addr, bytes, memorySize); + void checkAtomicAddress(Address addr, + Index bytes, + Address memorySize, + Address::address64_t memoryPageSize) { + checkLoadAddress(addr, bytes, memorySize, memoryPageSize); // Unaligned atomics trap. if (bytes > 1) { if (addr & (bytes - 1)) { @@ -5188,11 +5250,12 @@ class ModuleRunnerBase : public ExpressionRunner { Type type, Name memoryName, Address memorySize, + Address::address64_t memoryPageSize, MemoryOrder order) { if (order == MemoryOrder::Unordered) { Fatal() << "Expected a non-unordered MemoryOrder in doAtomicLoad"; } - checkAtomicAddress(addr, bytes, memorySize); + checkAtomicAddress(addr, bytes, memorySize, memoryPageSize); Const ptr; ptr.value = Literal(int32_t(addr)); ptr.type = Type::i32; @@ -5213,8 +5276,9 @@ class ModuleRunnerBase : public ExpressionRunner { Index bytes, Literal toStore, Name memoryName, - Address memorySize) { - checkAtomicAddress(addr, bytes, memorySize); + Address memorySize, + Address::address64_t memoryPageSize) { + checkAtomicAddress(addr, bytes, memorySize, memoryPageSize); Const ptr; ptr.value = Literal(int32_t(addr)); ptr.type = Type::i32; diff --git a/src/wasm.h b/src/wasm.h index e74d6532ac2..f4ff1512217 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2467,28 +2467,30 @@ class DataSegment : public Named { class Memory : public Importable { public: - static const Address::address32_t kPageSize = 64 * 1024; static const Address::address64_t kUnlimitedSize = Address::address64_t(-1); - // In wasm32, the maximum memory size is limited by a 32-bit pointer: 4GB - static const Address::address32_t kMaxSize32 = - (uint64_t(4) * 1024 * 1024 * 1024) / kPageSize; - // in wasm64, the maximum number of pages - static const Address::address64_t kMaxSize64 = 1ull << (64 - 16); + + static const uint8_t kDefaultPageSizeLog2 = 16; + + static const Address::address32_t kDefaultPageSize = 1 + << kDefaultPageSizeLog2; + + static const Address::address32_t kDefaultMaxSize32 = + 1 << (32 - kDefaultPageSizeLog2); Address initial = 0; // sizes are in pages - Address max = kMaxSize32; + Address max = kDefaultMaxSize32; + + uint8_t pageSizeLog2 = kDefaultPageSizeLog2; bool shared = false; Type addressType = Type::i32; bool hasMax() { return max != kUnlimitedSize; } bool is64() { return addressType == Type::i64; } - void clear() { - name = ""; - initial = 0; - max = kMaxSize32; - shared = false; - addressType = Type::i32; + Address::address64_t maxSize32() const { return 1ull << (32 - pageSizeLog2); } + Address::address64_t maxSize64() const { return 1ull << (64 - pageSizeLog2); } + Address::address64_t pageSize() const { + return 1ull << static_cast(pageSizeLog2); } }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 85768b55fbc..f2fb3340a6f 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -108,11 +108,18 @@ int32_t WasmBinaryWriter::writeU32LEBPlaceholder() { return o.writeU32LEBPlaceholder(); } -void WasmBinaryWriter::writeResizableLimits( - Address initial, Address maximum, bool hasMaximum, bool shared, bool is64) { - uint32_t flags = (hasMaximum ? (uint32_t)BinaryConsts::HasMaximum : 0U) | - (shared ? (uint32_t)BinaryConsts::IsShared : 0U) | - (is64 ? (uint32_t)BinaryConsts::Is64 : 0U); +void WasmBinaryWriter::writeResizableLimits(Address initial, + Address maximum, + bool hasMaximum, + bool shared, + bool is64, + bool hasPageSize, + Address::address32_t pageSizeLog2) { + uint32_t flags = + (hasMaximum ? (uint32_t)BinaryConsts::HasMaximum : 0U) | + (shared ? (uint32_t)BinaryConsts::IsShared : 0U) | + (is64 ? (uint32_t)BinaryConsts::Is64 : 0U) | + (hasPageSize ? (uint32_t)BinaryConsts::HasCustomPageSize : 0U); o << U32LEB(flags); if (is64) { o << U64LEB(initial); @@ -125,6 +132,9 @@ void WasmBinaryWriter::writeResizableLimits( o << U32LEB(maximum); } } + if (hasPageSize) { + o << U32LEB(pageSizeLog2); + } } template int32_t WasmBinaryWriter::startSection(T code) { @@ -209,7 +219,9 @@ void WasmBinaryWriter::writeMemories() { memory->max, memory->hasMax(), memory->shared, - memory->is64()); + memory->is64(), + memory->pageSizeLog2 != Memory::kDefaultPageSizeLog2, + memory->pageSizeLog2); }); finishSection(start); } @@ -354,7 +366,9 @@ void WasmBinaryWriter::writeImports() { memory->max, memory->hasMax(), memory->shared, - memory->is64()); + memory->is64(), + memory->pageSizeLog2 != Memory::kDefaultPageSizeLog2, + memory->pageSizeLog2); }); ModuleUtils::iterImportedTables(*wasm, [&](Table* table) { writeImportHeader(table); @@ -364,7 +378,9 @@ void WasmBinaryWriter::writeImports() { table->max, table->hasMax(), /*shared=*/false, - table->is64()); + table->is64(), + /*hasPageSize=*/false, + /*PageSize*/ 1); }); finishSection(start); } @@ -778,7 +794,9 @@ void WasmBinaryWriter::writeTableDeclarations() { table->max, table->hasMax(), /*shared=*/false, - table->is64()); + table->is64(), + /*hasPageSize=*/false, + /*PageSize*/ 1); }); finishSection(start); } @@ -1457,6 +1475,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::CustomDescriptorsFeature; case FeatureSet::RelaxedAtomics: return BinaryConsts::CustomSections::RelaxedAtomicsFeature; + case FeatureSet::CustomPageSizes: + return BinaryConsts::CustomSections::CustomPageSizesFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: @@ -2606,6 +2626,7 @@ void WasmBinaryReader::readMemories() { memory->max, memory->shared, memory->addressType, + memory->pageSizeLog2, Memory::kUnlimitedSize); wasm.addMemory(std::move(memory)); } @@ -2910,11 +2931,13 @@ void WasmBinaryReader::getResizableLimits(Address& initial, Address& max, bool& shared, Type& addressType, + uint8_t& pageSizeLog2, Address defaultIfNoMax) { auto flags = getU32LEB(); bool hasMax = (flags & BinaryConsts::HasMaximum) != 0; bool isShared = (flags & BinaryConsts::IsShared) != 0; bool is64 = (flags & BinaryConsts::Is64) != 0; + bool hasCustomPageSize = (flags & BinaryConsts::HasCustomPageSize) != 0; initial = is64 ? getU64LEB() : getU32LEB(); if (isShared && !hasMax) { throwError("shared memory must have max size"); @@ -2926,6 +2949,14 @@ void WasmBinaryReader::getResizableLimits(Address& initial, } else { max = defaultIfNoMax; } + if (hasCustomPageSize) { + auto readPageSizeLog2 = getU32LEB(); + if (readPageSizeLog2 != 0 && + readPageSizeLog2 != Memory::kDefaultPageSizeLog2) { + throwError("Memory page size is only allowed to be 1 or 64 KiB"); + } + pageSizeLog2 = (uint8_t)readPageSizeLog2; + } } void WasmBinaryReader::readImports() { @@ -2975,16 +3006,20 @@ void WasmBinaryReader::readImports() { table->module = module; table->base = base; table->type = getType(); - bool is_shared; + uint8_t page_size = 0xff; getResizableLimits(table->initial, table->max, is_shared, table->addressType, + page_size, Table::kUnlimitedSize); if (is_shared) { throwError("Tables may not be shared"); } + if (page_size != 0xff) { + throwError("Tables may not have a custom page size"); + } wasm.addTable(std::move(table)); break; } @@ -3002,6 +3037,7 @@ void WasmBinaryReader::readImports() { memory->max, memory->shared, memory->addressType, + memory->pageSizeLog2, Memory::kUnlimitedSize); wasm.addMemory(std::move(memory)); break; @@ -4973,14 +5009,19 @@ void WasmBinaryReader::readTableDeclarations() { auto table = Builder::makeTable(name, elemType); table->hasExplicitName = isExplicit; bool is_shared; + uint8_t pageSize = 0xff; getResizableLimits(table->initial, table->max, is_shared, table->addressType, + pageSize, Table::kUnlimitedSize); if (is_shared) { throwError("Tables may not be shared"); } + if (pageSize != 0xff) { + throwError("Tables may not specify a custom page size"); + } wasm.addTable(std::move(table)); } } @@ -5353,6 +5394,8 @@ void WasmBinaryReader::readFeatures(size_t sectionPos, size_t payloadLen) { feature = FeatureSet::CustomDescriptors; } else if (name == BinaryConsts::CustomSections::RelaxedAtomicsFeature) { feature = FeatureSet::RelaxedAtomics; + } else if (name == BinaryConsts::CustomSections::CustomPageSizesFeature) { + feature = FeatureSet::CustomPageSizes; } else { // Silently ignore unknown features (this may be and old binaryen running // on a new wasm). diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 5b1f0c7f4ed..e48370d1c39 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4606,10 +4606,10 @@ static void validateMemories(Module& module, ValidationInfo& info) { "memory", "64-bit memories require memory64 [--enable-memory64]"); } else { - info.shouldBeTrue(memory->initial <= Memory::kMaxSize32, + info.shouldBeTrue(memory->initial <= memory->maxSize32(), "memory", "initial memory must be <= 4GB"); - info.shouldBeTrue(!memory->hasMax() || memory->max <= Memory::kMaxSize32, + info.shouldBeTrue(!memory->hasMax() || memory->max <= memory->maxSize32(), "memory", "max memory must be <= 4GB, or unlimited"); } @@ -4621,6 +4621,18 @@ static void validateMemories(Module& module, ValidationInfo& info) { "memory", "shared memory requires threads [--enable-threads]"); } + + if (memory->pageSizeLog2 != Memory::kDefaultPageSizeLog2) { + info.shouldBeTrue( + module.features.hasCustomPageSizes(), + "memory", + "custom page sizes not enabled [--enable-custom-page-sizes]"); + info.shouldBeEqual( + Address(memory->pageSizeLog2), + Address(0), + "memory", + "custom page size must be 1 Byte or 65536 Bytes (64KiB)"); + } } } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 4e901c152de..b51c32d4d71 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -62,6 +62,7 @@ const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; const char* CustomDescriptorsFeature = "custom-descriptors"; const char* RelaxedAtomicsFeature = "relaxed-atomics"; +const char* CustomPageSizesFeature = "custom-page-sizes"; } // namespace BinaryConsts::CustomSections diff --git a/src/wasm2js.h b/src/wasm2js.h index b92a7f851e5..5d35c31bc25 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -476,13 +476,13 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { } else { Ref theVar = ValueBuilder::makeVar(); asmFunc[3]->push_back(theVar); - ValueBuilder::appendToVar( - theVar, - BUFFER, - ValueBuilder::makeNew(ValueBuilder::makeCall( - ValueBuilder::makeName("ArrayBuffer"), - ValueBuilder::makeInt(Address::address32_t( - wasm->memories[0]->initial.addr * Memory::kPageSize))))); + ValueBuilder::appendToVar(theVar, + BUFFER, + ValueBuilder::makeNew(ValueBuilder::makeCall( + ValueBuilder::makeName("ArrayBuffer"), + ValueBuilder::makeInt(Address::address32_t( + wasm->memories[0]->initial.addr + << wasm->memories[0]->pageSizeLog2))))); } } @@ -2486,13 +2486,12 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, void Wasm2JSBuilder::addMemoryFuncs(Ref ast, Module* wasm) { Ref memorySizeFunc = ValueBuilder::makeFunction(WASM_MEMORY_SIZE); - memorySizeFunc[3]->push_back(ValueBuilder::makeReturn( - makeJsCoercion(ValueBuilder::makeBinary( - ValueBuilder::makeDot(ValueBuilder::makeName(BUFFER), - IString("byteLength")), - DIV, - ValueBuilder::makeInt(Memory::kPageSize)), - JsType::JS_INT))); + memorySizeFunc[3]->push_back( + ValueBuilder::makeReturn(ValueBuilder::makeBinary( + ValueBuilder::makeDot(ValueBuilder::makeName(BUFFER), + IString("byteLength")), + RSHIFT, + ValueBuilder::makeInt(wasm->memories[0]->pageSizeLog2)))); ast->push_back(memorySizeFunc); if (!wasm->memories.empty() && @@ -2537,9 +2536,10 @@ void Wasm2JSBuilder::addMemoryGrowFunc(Ref ast, Module* wasm) { LT, ValueBuilder::makeName(IString("newPages"))), IString("&&"), - ValueBuilder::makeBinary(ValueBuilder::makeName(IString("newPages")), - LT, - ValueBuilder::makeInt(Memory::kMaxSize32))), + ValueBuilder::makeBinary( + ValueBuilder::makeName(IString("newPages")), + LT, + ValueBuilder::makeNum(wasm->memories[0]->maxSize32()))), block, NULL)); @@ -2550,9 +2550,10 @@ void Wasm2JSBuilder::addMemoryGrowFunc(Ref ast, Module* wasm) { IString("newBuffer"), ValueBuilder::makeNew(ValueBuilder::makeCall( ARRAY_BUFFER, - ValueBuilder::makeCall(MATH_IMUL, - ValueBuilder::makeName(IString("newPages")), - ValueBuilder::makeInt(Memory::kPageSize))))); + ValueBuilder::makeBinary( + ValueBuilder::makeName(IString("newPages")), + LSHIFT, + ValueBuilder::makeInt(wasm->memories[0]->pageSizeLog2))))); Ref newHEAP8 = ValueBuilder::makeVar(); ValueBuilder::appendToBlock(block, newHEAP8); @@ -2744,7 +2745,8 @@ void Wasm2JSGlue::emitPostES6() { // can be used for conversions, so make sure there's at least one page. if (!wasm.memories.empty() && wasm.memories[0]->imported()) { out << "var mem" << moduleName.str << " = new ArrayBuffer(" - << wasm.memories[0]->initial.addr * Memory::kPageSize << ");\n"; + << (wasm.memories[0]->initial.addr << wasm.memories[0]->pageSizeLog2) + << ");\n"; } // Actually invoke the `asmFunc` generated function, passing in all global diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 673a470607d..68e0670f325 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -101,6 +101,7 @@ function test_features() { console.log("Features.Strings: " + binaryen.Features.Strings); console.log("Features.MultiMemory: " + binaryen.Features.MultiMemory); console.log("Features.RelaxedAtomics: " + binaryen.Features.RelaxedAtomics); + console.log("Features.CustomPageSizes: " + binaryen.Features.CustomPageSizes); console.log("Features.All: " + binaryen.Features.All); } diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index c5ea5bded75..6bf9ae959f8 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -34,7 +34,8 @@ Features.ExtendedConst: 8192 Features.Strings: 16384 Features.MultiMemory: 32768 Features.RelaxedAtomics: 4194304 -Features.All: 8388607 +Features.CustomPageSizes: 8388608 +Features.All: 16777215 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 2499ffff646..1ae179ca265 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -376,6 +376,8 @@ void test_features() { printf("BinaryenFeatureStrings: %d\n", BinaryenFeatureStrings()); printf("BinaryenFeatureRelaxedAtomics: %d\n", BinaryenFeatureRelaxedAtomics()); + printf("BinaryenFeatureCustomPageSizes: %d\n", + BinaryenFeatureCustomPageSizes()); printf("BinaryenFeatureAll: %d\n", BinaryenFeatureAll()); } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 4d68abcddd5..ed09c2b418c 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -48,7 +48,8 @@ BinaryenFeatureRelaxedSIMD: 4096 BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 BinaryenFeatureRelaxedAtomics: 4194304 -BinaryenFeatureAll: 8388607 +BinaryenFeatureCustomPageSizes: 8388608 +BinaryenFeatureAll: 16777215 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index d9f1c51544b..9a59856cbac 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -140,6 +140,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 3ecda78f368..c6a36b5fce9 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -147,6 +147,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 4105874814a..d15ebace641 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -133,6 +133,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 0c94205267c..1200667793b 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -175,6 +175,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index f6837860017..0e2dab535c4 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -163,6 +163,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index b566afd8471..a556ca5bf9a 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -798,6 +798,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic ;; CHECK-NEXT: memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index eff3724891c..58d7221086d 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -830,6 +830,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic ;; CHECK-NEXT: memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 12d258bed49..ad014fa0530 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -223,6 +223,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 102add9dfd7..30b651521b3 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -281,6 +281,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index cd55ac22235..2a67bc1dee2 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -762,6 +762,10 @@ ;; CHECK-NEXT: --disable-relaxed-atomics Disable acquire/release atomic ;; CHECK-NEXT: memory operations ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-custom-page-sizes Enable custom page sizes +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-custom-page-sizes Disable custom page sizes +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index 990c2fa30de..adc19e7e732 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -37,9 +37,9 @@ ;; CHECK-NEXT: (local $step i32) ;; CHECK-NEXT: (local $i i32) ;; CHECK-NEXT: (local.set $end -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (memory.size) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if @@ -135,9 +135,9 @@ ;; CHECK-NEXT: (local.get $dst) ;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (memory.size) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/multi-memory-lowering.wast b/test/lit/passes/multi-memory-lowering.wast index 9b7f5363503..b46b97ee721 100644 --- a/test/lit/passes/multi-memory-lowering.wast +++ b/test/lit/passes/multi-memory-lowering.wast @@ -839,9 +839,9 @@ ;; CHECK: (func $memory1_size (result i32) ;; CHECK-NEXT: (return -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -849,13 +849,13 @@ ;; CHECK: (func $memory2_size (result i32) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory3_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -865,9 +865,9 @@ ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.sub ;; CHECK-NEXT: (memory.size) -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory3_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -898,16 +898,16 @@ ;; CHECK-NEXT: (memory.copy ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory2_byte_offset) ;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $memory_size) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory2_byte_offset) ;; CHECK-NEXT: ) @@ -915,18 +915,18 @@ ;; CHECK-NEXT: (global.set $memory2_byte_offset ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $memory3_byte_offset ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory3_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -958,16 +958,16 @@ ;; CHECK-NEXT: (memory.copy ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory3_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory3_byte_offset) ;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $memory_size) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory3_byte_offset) ;; CHECK-NEXT: ) @@ -975,9 +975,9 @@ ;; CHECK-NEXT: (global.set $memory3_byte_offset ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory3_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1007,9 +1007,9 @@ ;; BOUNDS: (func $memory1_size (result i32) ;; BOUNDS-NEXT: (return -;; BOUNDS-NEXT: (i32.div_u +;; BOUNDS-NEXT: (i32.shr_u ;; BOUNDS-NEXT: (global.get $memory2_byte_offset) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) @@ -1017,13 +1017,13 @@ ;; BOUNDS: (func $memory2_size (result i32) ;; BOUNDS-NEXT: (return ;; BOUNDS-NEXT: (i32.sub -;; BOUNDS-NEXT: (i32.div_u +;; BOUNDS-NEXT: (i32.shr_u ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) -;; BOUNDS-NEXT: (i32.div_u +;; BOUNDS-NEXT: (i32.shr_u ;; BOUNDS-NEXT: (global.get $memory2_byte_offset) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) @@ -1033,9 +1033,9 @@ ;; BOUNDS-NEXT: (return ;; BOUNDS-NEXT: (i32.sub ;; BOUNDS-NEXT: (memory.size) -;; BOUNDS-NEXT: (i32.div_u +;; BOUNDS-NEXT: (i32.shr_u ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) @@ -1066,16 +1066,16 @@ ;; BOUNDS-NEXT: (memory.copy ;; BOUNDS-NEXT: (i32.add ;; BOUNDS-NEXT: (global.get $memory2_byte_offset) -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $page_delta) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: (global.get $memory2_byte_offset) ;; BOUNDS-NEXT: (i32.sub -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $memory_size) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: (global.get $memory2_byte_offset) ;; BOUNDS-NEXT: ) @@ -1083,18 +1083,18 @@ ;; BOUNDS-NEXT: (global.set $memory2_byte_offset ;; BOUNDS-NEXT: (i32.add ;; BOUNDS-NEXT: (global.get $memory2_byte_offset) -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $page_delta) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: (global.set $memory3_byte_offset ;; BOUNDS-NEXT: (i32.add ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $page_delta) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) @@ -1126,16 +1126,16 @@ ;; BOUNDS-NEXT: (memory.copy ;; BOUNDS-NEXT: (i32.add ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $page_delta) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) ;; BOUNDS-NEXT: (i32.sub -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $memory_size) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) ;; BOUNDS-NEXT: ) @@ -1143,9 +1143,9 @@ ;; BOUNDS-NEXT: (global.set $memory3_byte_offset ;; BOUNDS-NEXT: (i32.add ;; BOUNDS-NEXT: (global.get $memory3_byte_offset) -;; BOUNDS-NEXT: (i32.mul +;; BOUNDS-NEXT: (i32.shl ;; BOUNDS-NEXT: (local.get $page_delta) -;; BOUNDS-NEXT: (i32.const 65536) +;; BOUNDS-NEXT: (i32.const 16) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) ;; BOUNDS-NEXT: ) diff --git a/test/lit/wasm-split/multi-memory-lowering-export.wast b/test/lit/wasm-split/multi-memory-lowering-export.wast index 152aaf72310..4411ba78e39 100644 --- a/test/lit/wasm-split/multi-memory-lowering-export.wast +++ b/test/lit/wasm-split/multi-memory-lowering-export.wast @@ -20,9 +20,9 @@ ;; CHECK: (func $memory1_size (type $0) (result i32) ;; CHECK-NEXT: (return -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -31,9 +31,9 @@ ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.sub ;; CHECK-NEXT: (memory.size) -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -64,16 +64,16 @@ ;; CHECK-NEXT: (memory.copy ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory2_byte_offset) ;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $memory_size) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory2_byte_offset) ;; CHECK-NEXT: ) @@ -81,9 +81,9 @@ ;; CHECK-NEXT: (global.set $memory2_byte_offset ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/wasm-split/multi-memory-lowering-import.wast b/test/lit/wasm-split/multi-memory-lowering-import.wast index 37ccfa16cb1..f8d505d89fc 100644 --- a/test/lit/wasm-split/multi-memory-lowering-import.wast +++ b/test/lit/wasm-split/multi-memory-lowering-import.wast @@ -17,9 +17,9 @@ ;; CHECK: (func $memory1_size (type $0) (result i32) ;; CHECK-NEXT: (return -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -28,9 +28,9 @@ ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.sub ;; CHECK-NEXT: (memory.size) -;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (i32.shr_u ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -61,16 +61,16 @@ ;; CHECK-NEXT: (memory.copy ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory2_byte_offset) ;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $memory_size) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $memory2_byte_offset) ;; CHECK-NEXT: ) @@ -78,9 +78,9 @@ ;; CHECK-NEXT: (global.set $memory2_byte_offset ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (global.get $memory2_byte_offset) -;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $page_delta) -;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: (i32.const 16) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 4d80eb6bb81..ce3e9222f7b 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -21,6 +21,7 @@ --enable-call-indirect-overlong --enable-custom-descriptors --enable-relaxed-atomics +--enable-custom-page-sizes (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) diff --git a/test/spec/custom-page-sizes-invalid.wast b/test/spec/custom-page-sizes-invalid.wast new file mode 100644 index 00000000000..5fb121a0aaa --- /dev/null +++ b/test/spec/custom-page-sizes-invalid.wast @@ -0,0 +1,115 @@ +;; Page size that is not a power of two. +(assert_malformed + (module quote "(memory 0 (pagesize 3))") + "invalid custom page size" +) + +(assert_malformed + (module quote "(memory 0 (pagesize 0))") + "invalid custom page size" +) + +;; Power-of-two page sizes that are not 1 or 64KiB. +(assert_invalid + (module (memory 0 (pagesize 2))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 4))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 8))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 16))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 32))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 64))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 128))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 256))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 512))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 1024))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 2048))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 4096))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 8192))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 16384))) + "invalid custom page size" +) +(assert_invalid + (module (memory 0 (pagesize 32768))) + "invalid custom page size" +) + +;; Power-of-two page size that is larger than 64KiB. +(assert_invalid + (module (memory 0 (pagesize 0x20000))) + "invalid custom page size" +) + +;; Power of two page size that cannot fit in a u64 to exercise checks against +;; shift overflow. +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\05\04\01" ;; Memory section + + ;; memory 0 + "\08" ;; flags w/ custom page size + "\00" ;; minimum = 0 + "\41" ;; pagesize = 2**65 + ) + "invalid custom page size" +) + +;; Importing a memory with the wrong page size. + +(module $m + (memory (export "small-pages-memory") 0 (pagesize 1)) + (memory (export "large-pages-memory") 0 (pagesize 65536)) +) +(register "m" $m) + +;; (assert_unlinkable +;; (module +;; (memory (import "m" "small-pages-memory") 0 (pagesize 65536)) +;; ) +;; "memory types incompatible" +;; ) + +;; (assert_unlinkable +;; (module +;; (memory (import "m" "large-pages-memory") 0 (pagesize 1)) +;; ) +;; "memory types incompatible" +;; ) diff --git a/test/spec/custom-page-sizes.wast b/test/spec/custom-page-sizes.wast new file mode 100644 index 00000000000..332051daacb --- /dev/null +++ b/test/spec/custom-page-sizes.wast @@ -0,0 +1,138 @@ +;; Check all the valid custom page sizes. +(module (memory 1 (pagesize 1))) +(module (memory 1 (pagesize 65536))) + +;; Check them all again with maximums specified. +(module (memory 1 2 (pagesize 1))) +(module (memory 1 2 (pagesize 65536))) + +;; Check the behavior of memories with page size 1. +(module + (memory 0 (pagesize 1)) + (func (export "size") (result i32) + memory.size + ) + (func (export "grow") (param i32) (result i32) + (memory.grow (local.get 0)) + ) + (func (export "load") (param i32) (result i32) + (i32.load8_u (local.get 0)) + ) + (func (export "store") (param i32 i32) + (i32.store8 (local.get 0) (local.get 1)) + ) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_trap (invoke "load" (i32.const 0)) "out of bounds memory access") + +(assert_return (invoke "grow" (i32.const 65536)) (i32.const 0)) +(assert_return (invoke "size") (i32.const 65536)) +(assert_return (invoke "load" (i32.const 65535)) (i32.const 0)) +(assert_return (invoke "store" (i32.const 65535) (i32.const 1))) +(assert_return (invoke "load" (i32.const 65535)) (i32.const 1)) +(assert_trap (invoke "load" (i32.const 65536)) "out of bounds memory access") + +(assert_return (invoke "grow" (i32.const 65536)) (i32.const 65536)) +(assert_return (invoke "size") (i32.const 131072)) +(assert_return (invoke "load" (i32.const 131071)) (i32.const 0)) +(assert_return (invoke "store" (i32.const 131071) (i32.const 1))) +(assert_return (invoke "load" (i32.const 131071)) (i32.const 1)) +(assert_trap (invoke "load" (i32.const 131072)) "out of bounds memory access") + +;; Although smaller page sizes let us get to memories larger than 2**16 pages, +;; we can't do that with the default page size, even if we explicitly state it +;; as a custom page size. +(module + (memory 0 (pagesize 65536)) + (func (export "size") (result i32) + memory.size + ) + (func (export "grow") (param i32) (result i32) + (memory.grow (local.get 0)) + ) +) +(assert_return (invoke "size") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 65537)) (i32.const -1)) +(assert_return (invoke "size") (i32.const 0)) + +;; Can copy between memories of different page sizes. +(module + (memory $small 10 (pagesize 1)) + (memory $large 1 (pagesize 65536)) + + (data (memory $small) (i32.const 0) "\11\22\33\44") + (data (memory $large) (i32.const 0) "\55\66\77\88") + + (func (export "copy-small-to-large") (param i32 i32 i32) + (memory.copy $large $small (local.get 0) (local.get 1) (local.get 2)) + ) + + (func (export "copy-large-to-small") (param i32 i32 i32) + (memory.copy $small $large (local.get 0) (local.get 1) (local.get 2)) + ) + + (func (export "load8-small") (param i32) (result i32) + (i32.load8_u $small (local.get 0)) + ) + + (func (export "load8-large") (param i32) (result i32) + (i32.load8_u $large (local.get 0)) + ) +) + +(assert_return (invoke "copy-small-to-large" (i32.const 6) (i32.const 0) (i32.const 2))) +(assert_return (invoke "load8-large" (i32.const 6)) (i32.const 0x11)) +(assert_return (invoke "load8-large" (i32.const 7)) (i32.const 0x22)) + +(assert_return (invoke "copy-large-to-small" (i32.const 4) (i32.const 1) (i32.const 3))) +(assert_return (invoke "load8-small" (i32.const 4)) (i32.const 0x66)) +(assert_return (invoke "load8-small" (i32.const 5)) (i32.const 0x77)) +(assert_return (invoke "load8-small" (i32.const 6)) (i32.const 0x88)) + +;; Can link together modules that export and import memories with custom page +;; sizes. + +(module $m + (memory (export "small-pages-memory") 0 (pagesize 1)) + (memory (export "large-pages-memory") 0 (pagesize 65536)) +) +(register "m" $m) + +(module + (memory (import "m" "small-pages-memory") 0 (pagesize 1)) +) + +(module + (memory (import "m" "large-pages-memory") 0 (pagesize 65536)) +) + +;; Inline data segments + +;; pagesize 0 +(assert_malformed (module quote "(memory (pagesize 0) (data))") "invalid custom page size") + +;; pagesize 1 +(module + (memory (pagesize 1) (data "xyz")) + (func (export "size") (result i32) + memory.size) + (func (export "grow") (param i32) (result i32) + (memory.grow (local.get 0))) + (func (export "load") (param i32) (result i32) + (i32.load8_u (local.get 0)))) + +(assert_return (invoke "size") (i32.const 3)) +(assert_return (invoke "load" (i32.const 0)) (i32.const 120)) +(assert_return (invoke "load" (i32.const 1)) (i32.const 121)) +(assert_return (invoke "load" (i32.const 2)) (i32.const 122)) +(assert_trap (invoke "load" (i32.const 3)) "out of bounds memory access") +(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) + +;; pagesize 65536 +(module + (memory (pagesize 65536) (data "xyz")) + (func (export "size") (result i32) + memory.size)) + +(assert_return (invoke "size") (i32.const 1)) diff --git a/test/spec/memory_max.wast b/test/spec/memory_max.wast new file mode 100644 index 00000000000..dfc89b6696e --- /dev/null +++ b/test/spec/memory_max.wast @@ -0,0 +1,37 @@ +;; Maximum memory sizes. +;; +;; These modules are valid, but instantiating them is unnecessary +;; and would only allocate very large memories and slow down running +;; the spec tests. Therefore, add a missing import so that it cannot +;; be instantiated and use `assert_unlinkable`. This approach +;; enforces that the module itself is still valid, but that its +;; instantiation fails early (hopefully before any memories are +;; actually allocated). + +;; i32 (pagesize 1) +(assert_unlinkable + (module + (import "test" "unknown" (func)) + (memory 0xFFFF_FFFF (pagesize 1))) + "unknown import") + +;; i32 (default pagesize) +(assert_unlinkable + (module + (import "test" "unknown" (func)) + (memory 65536 (pagesize 65536))) + "unknown import") + +;; Memory size just over the maximum. + +;; i32 (pagesize 1) +(assert_invalid + (module + (memory 0x1_0000_0000 (pagesize 1))) + "memory size must be at most") + +;; i32 (default pagesize) +(assert_invalid + (module + (memory 65537 (pagesize 65536))) + "memory size must be at most") diff --git a/test/spec/memory_max_i64.wast b/test/spec/memory_max_i64.wast new file mode 100644 index 00000000000..607642decf8 --- /dev/null +++ b/test/spec/memory_max_i64.wast @@ -0,0 +1,39 @@ +;; Maximum memory sizes. +;; +;; These modules are valid, but instantiating them is unnecessary +;; and would only allocate very large memories and slow down running +;; the spec tests. Therefore, add a missing import so that it cannot +;; be instantiated and use `assert_unlinkable`. This approach +;; enforces that the module itself is still valid, but that its +;; instantiation fails early (hopefully before any memories are +;; actually allocated). + +;; i64 (pagesize 1) +(assert_unlinkable + (module + (import "test" "import" (func)) + (memory i64 0xFFFF_FFFF_FFFF_FFFF (pagesize 1))) + "unknown import") + +;; i64 (default pagesize) +(assert_unlinkable + (module + (import "test" "unknown" (func)) + (memory i64 0x1_0000_0000_0000 (pagesize 65536))) + "unknown import") + +;; Memory size just over the maximum. +;; +;; These are malformed (for pagesize 1) +;; or invalid (for other pagesizes). + +;; i64 (pagesize 1) +(assert_malformed + (module quote "(memory i64 0x1_0000_0000_0000_0000 (pagesize 1))") + "constant out of range") + +;; i64 (default pagesize) +(assert_invalid + (module + (memory i64 0x1_0000_0000_0001 (pagesize 65536))) + "memory size must be at most") diff --git a/test/unit/test_features.py b/test/unit/test_features.py index d44435811fc..35b65199a11 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -456,4 +456,5 @@ def test_emit_all_features(self): '--enable-call-indirect-overlong', '--enable-custom-descriptors', '--enable-relaxed-atomics', + '--enable-custom-page-sizes', ], p2.stdout.splitlines()) diff --git a/test/wasm2js/atomics_32.2asm.js b/test/wasm2js/atomics_32.2asm.js index a97bf17ed26..03ac931053b 100644 --- a/test/wasm2js/atomics_32.2asm.js +++ b/test/wasm2js/atomics_32.2asm.js @@ -143,7 +143,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/atomics_32.2asm.js.opt b/test/wasm2js/atomics_32.2asm.js.opt index deb8ce56186..04da6fac687 100644 --- a/test/wasm2js/atomics_32.2asm.js.opt +++ b/test/wasm2js/atomics_32.2asm.js.opt @@ -139,7 +139,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/bulk-memory.2asm.js b/test/wasm2js/bulk-memory.2asm.js index 4f25b9bd8ba..b1a25080bf6 100644 --- a/test/wasm2js/bulk-memory.2asm.js +++ b/test/wasm2js/bulk-memory.2asm.js @@ -61,7 +61,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -69,7 +69,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); @@ -162,7 +162,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { @@ -239,7 +239,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -247,7 +247,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); @@ -351,7 +351,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -359,7 +359,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/bulk-memory.2asm.js.opt b/test/wasm2js/bulk-memory.2asm.js.opt index 50ddecee8b2..be6ac9d195a 100644 --- a/test/wasm2js/bulk-memory.2asm.js.opt +++ b/test/wasm2js/bulk-memory.2asm.js.opt @@ -61,7 +61,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -69,7 +69,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); @@ -162,7 +162,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { @@ -239,7 +239,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -247,7 +247,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); @@ -304,7 +304,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -312,7 +312,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/deterministic.2asm.js b/test/wasm2js/deterministic.2asm.js index 33cb04a846e..e4c12bc8f78 100644 --- a/test/wasm2js/deterministic.2asm.js +++ b/test/wasm2js/deterministic.2asm.js @@ -34,7 +34,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/deterministic.2asm.js.opt b/test/wasm2js/deterministic.2asm.js.opt index 12ebdd8599d..2c25650a716 100644 --- a/test/wasm2js/deterministic.2asm.js.opt +++ b/test/wasm2js/deterministic.2asm.js.opt @@ -33,7 +33,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/dynamicLibrary.2asm.js b/test/wasm2js/dynamicLibrary.2asm.js index bb6f4dcaff6..cc4103f0774 100644 --- a/test/wasm2js/dynamicLibrary.2asm.js +++ b/test/wasm2js/dynamicLibrary.2asm.js @@ -77,7 +77,7 @@ function asmFunc(imports) { FUNCTION_TABLE[import$tableBase + 0] = foo; FUNCTION_TABLE[import$tableBase + 1] = bar; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/dynamicLibrary.2asm.js.opt b/test/wasm2js/dynamicLibrary.2asm.js.opt index 543f755f37d..d0c8c7b2900 100644 --- a/test/wasm2js/dynamicLibrary.2asm.js.opt +++ b/test/wasm2js/dynamicLibrary.2asm.js.opt @@ -69,7 +69,7 @@ function asmFunc(imports) { FUNCTION_TABLE[import$tableBase + 0] = foo; FUNCTION_TABLE[import$tableBase + 1] = foo; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/emscripten-grow-no.2asm.js b/test/wasm2js/emscripten-grow-no.2asm.js index 79963d6624c..e633392e300 100644 --- a/test/wasm2js/emscripten-grow-no.2asm.js +++ b/test/wasm2js/emscripten-grow-no.2asm.js @@ -52,7 +52,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/emscripten-grow-no.2asm.js.opt b/test/wasm2js/emscripten-grow-no.2asm.js.opt index 79963d6624c..e633392e300 100644 --- a/test/wasm2js/emscripten-grow-no.2asm.js.opt +++ b/test/wasm2js/emscripten-grow-no.2asm.js.opt @@ -52,7 +52,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/emscripten-grow-yes.2asm.js b/test/wasm2js/emscripten-grow-yes.2asm.js index 6ab56747baf..7df826d54ba 100644 --- a/test/wasm2js/emscripten-grow-yes.2asm.js +++ b/test/wasm2js/emscripten-grow-yes.2asm.js @@ -57,7 +57,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -65,7 +65,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/emscripten-grow-yes.2asm.js.opt b/test/wasm2js/emscripten-grow-yes.2asm.js.opt index 6ab56747baf..7df826d54ba 100644 --- a/test/wasm2js/emscripten-grow-yes.2asm.js.opt +++ b/test/wasm2js/emscripten-grow-yes.2asm.js.opt @@ -57,7 +57,7 @@ function asmFunc(imports) { bufferView = HEAPU8; initActiveSegments(imports); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -65,7 +65,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/emscripten.2asm.js b/test/wasm2js/emscripten.2asm.js index 24a556b25d7..acf66476190 100644 --- a/test/wasm2js/emscripten.2asm.js +++ b/test/wasm2js/emscripten.2asm.js @@ -218,7 +218,7 @@ function asmFunc(imports) { FUNCTION_TABLE[2] = bar; FUNCTION_TABLE[3] = tabled; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/emscripten.2asm.js.opt b/test/wasm2js/emscripten.2asm.js.opt index caef2389639..749a7edaef3 100644 --- a/test/wasm2js/emscripten.2asm.js.opt +++ b/test/wasm2js/emscripten.2asm.js.opt @@ -212,7 +212,7 @@ function asmFunc(imports) { FUNCTION_TABLE[2] = bar; FUNCTION_TABLE[3] = internal; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/endianness.2asm.js b/test/wasm2js/endianness.2asm.js index 5e29d67c345..e9f0c8c9396 100644 --- a/test/wasm2js/endianness.2asm.js +++ b/test/wasm2js/endianness.2asm.js @@ -652,7 +652,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -660,7 +660,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/grow-memory-tricky.2asm.js b/test/wasm2js/grow-memory-tricky.2asm.js index 0dc0e1ee110..d75a80c5df5 100644 --- a/test/wasm2js/grow-memory-tricky.2asm.js +++ b/test/wasm2js/grow-memory-tricky.2asm.js @@ -36,7 +36,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -44,7 +44,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/grow-memory-tricky.2asm.js.opt b/test/wasm2js/grow-memory-tricky.2asm.js.opt index d02bfcaa656..abc0b7ef835 100644 --- a/test/wasm2js/grow-memory-tricky.2asm.js.opt +++ b/test/wasm2js/grow-memory-tricky.2asm.js.opt @@ -26,7 +26,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -34,7 +34,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/grow_memory.2asm.js b/test/wasm2js/grow_memory.2asm.js index 83a9b998de3..47cb08ca085 100644 --- a/test/wasm2js/grow_memory.2asm.js +++ b/test/wasm2js/grow_memory.2asm.js @@ -29,7 +29,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -37,7 +37,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/left-to-right.2asm.js b/test/wasm2js/left-to-right.2asm.js index 6553cbc11d5..63bc9225796 100644 --- a/test/wasm2js/left-to-right.2asm.js +++ b/test/wasm2js/left-to-right.2asm.js @@ -2083,7 +2083,7 @@ function asmFunc(imports) { bufferView = HEAPU8; var FUNCTION_TABLE = [i32_t0, i32_t1, i64_t0, i64_t1, f32_t0, f32_t1, f64_t0, f64_t1]; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -2091,7 +2091,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/minified-memory.2asm.js b/test/wasm2js/minified-memory.2asm.js index 1d3e8c942f8..d2aded1bfdb 100644 --- a/test/wasm2js/minified-memory.2asm.js +++ b/test/wasm2js/minified-memory.2asm.js @@ -27,7 +27,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -35,7 +35,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/minified-memory.2asm.js.opt b/test/wasm2js/minified-memory.2asm.js.opt index 6d1dfa47d7f..a7c31ca6858 100644 --- a/test/wasm2js/minified-memory.2asm.js.opt +++ b/test/wasm2js/minified-memory.2asm.js.opt @@ -27,7 +27,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -35,7 +35,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/reinterpret_scratch.2asm.js b/test/wasm2js/reinterpret_scratch.2asm.js index 547206072a9..22969331eae 100644 --- a/test/wasm2js/reinterpret_scratch.2asm.js +++ b/test/wasm2js/reinterpret_scratch.2asm.js @@ -50,7 +50,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/reinterpret_scratch.2asm.js.opt b/test/wasm2js/reinterpret_scratch.2asm.js.opt index bc077cdce1f..63901569ec6 100644 --- a/test/wasm2js/reinterpret_scratch.2asm.js.opt +++ b/test/wasm2js/reinterpret_scratch.2asm.js.opt @@ -45,7 +45,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/start_func.2asm.js b/test/wasm2js/start_func.2asm.js index 0ca4c66dfb8..2eb744f319a 100644 --- a/test/wasm2js/start_func.2asm.js +++ b/test/wasm2js/start_func.2asm.js @@ -25,7 +25,7 @@ function asmFunc(imports) { foo(); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -33,7 +33,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/start_func.2asm.js.opt b/test/wasm2js/start_func.2asm.js.opt index 6a002727208..3b7e05608fa 100644 --- a/test/wasm2js/start_func.2asm.js.opt +++ b/test/wasm2js/start_func.2asm.js.opt @@ -25,7 +25,7 @@ function asmFunc(imports) { foo(); function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -33,7 +33,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/traps.2asm.js b/test/wasm2js/traps.2asm.js index f7a469ebe2c..54f62705314 100644 --- a/test/wasm2js/traps.2asm.js +++ b/test/wasm2js/traps.2asm.js @@ -1660,7 +1660,7 @@ function asmFunc(imports) { } function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } function __wasm_memory_grow(pagesToAdd) { @@ -1668,7 +1668,7 @@ function asmFunc(imports) { var oldPages = __wasm_memory_size() | 0; var newPages = oldPages + pagesToAdd | 0; if ((oldPages < newPages) && (newPages < 65536)) { - var newBuffer = new ArrayBuffer(Math_imul(newPages, 65536)); + var newBuffer = new ArrayBuffer(newPages << 16); var newHEAP8 = new Int8Array(newBuffer); newHEAP8.set(HEAP8); HEAP8 = new Int8Array(newBuffer); diff --git a/test/wasm2js/unaligned.2asm.js b/test/wasm2js/unaligned.2asm.js index bf33c2d789a..d7f03e1b471 100644 --- a/test/wasm2js/unaligned.2asm.js +++ b/test/wasm2js/unaligned.2asm.js @@ -160,7 +160,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { diff --git a/test/wasm2js/unaligned.2asm.js.opt b/test/wasm2js/unaligned.2asm.js.opt index 552868ded2b..ad5f96a4971 100644 --- a/test/wasm2js/unaligned.2asm.js.opt +++ b/test/wasm2js/unaligned.2asm.js.opt @@ -109,7 +109,7 @@ function asmFunc(imports) { bufferView = HEAPU8; function __wasm_memory_size() { - return buffer.byteLength / 65536 | 0; + return buffer.byteLength >> 16; } return { From 5d2e909ebcc10715251c614ca6acd72546c7c97d Mon Sep 17 00:00:00 2001 From: Cai Congcong Date: Fri, 13 Feb 2026 11:18:52 +0800 Subject: [PATCH 2/5] fix review --- scripts/test/shared.py | 1 + src/parser/contexts.h | 23 ++------ src/parser/parsers.h | 25 +++++---- src/passes/MultiMemoryLowering.cpp | 72 +++++++++--------------- src/tools/wasm-split/instrumenter.cpp | 2 +- src/wasm-binary.h | 16 +++--- src/wasm-interpreter.h | 7 +-- src/wasm/wasm-binary.cpp | 80 ++++++++++++++++----------- 8 files changed, 107 insertions(+), 119 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 369f9d19cac..42c41ae18dd 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -388,6 +388,7 @@ def get_tests(test_dir, extensions=[], recursive=False): # Unlinkable module accepted 'linking.wast', + # TODO: failure 'memory_max.wast', 'memory_max_i64.wast', diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 8f2af907193..9fbb63607ff 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -174,8 +174,6 @@ struct NullTypeParserCtx { DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} - MemTypeT makeMemType(Type, LimitsT, bool) { return Ok{}; } - BlockTypeT getBlockTypeFromResult(size_t results) { return Ok{}; } Result<> getBlockTypeFromTypeUse(Index, TypeUseT) { return Ok{}; } @@ -352,11 +350,8 @@ template struct TypeParserCtx { void appendDataString(DataStringT&, std::string_view) {} Result makeLimits(uint64_t, std::optional) { return Ok{}; } - LimitsT getLimitsFromData(DataStringT) { return Ok{}; } - MemTypeT makeMemType(Type, LimitsT, bool, std::optional) { - return Ok{}; - } + MemTypeT makeMemType(Type, LimitsT, bool, uint8_t) { return Ok{}; } HeapType getBlockTypeFromResult(const std::vector results) { assert(results.size() == 1); @@ -1076,18 +1071,16 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { } Limits getLimitsFromData(const std::vector& data, - std::optional pageSizeLog2) { - uint8_t _pageSizeLog2 = pageSizeLog2.value_or(16); + uint8_t pageSizeLog2) { uint64_t size = - (data.size() + (1 << _pageSizeLog2) - 1) / (1 << _pageSizeLog2); + (data.size() + (1 << pageSizeLog2) - 1) / (1 << pageSizeLog2); return {size, size}; } MemType makeMemType(Type addressType, Limits limits, bool shared, - std::optional pageSize) { - uint8_t pageSizeLog2 = pageSize.value_or(16); + uint8_t pageSizeLog2) { return {addressType, limits, pageSizeLog2, shared}; } @@ -1457,12 +1450,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, Type makeTableType(Type addressType, LimitsT, Type type) { return type; } - LimitsT getLimitsFromData(DataStringT, std::optional) { - return Ok{}; - } - MemTypeT makeMemType(Type, LimitsT, bool, std::optional) { - return Ok{}; - } + LimitsT getLimitsFromData(DataStringT, uint8_t) { return Ok{}; } + MemTypeT makeMemType(Type, LimitsT, bool, uint8_t) { return Ok{}; } Result<> addFunc(Name name, const std::vector&, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 90c0f5a2e75..ea911c0bd41 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -20,6 +20,7 @@ #include "common.h" #include "contexts.h" #include "lexer.h" +#include "wasm.h" #include "wat-parser-internal.h" namespace wasm::WATParser { @@ -826,9 +827,9 @@ template Result limits64(Ctx& ctx) { } // mempagesize? ::= ('(' 'pagesize' u64 ')') ? -template Result> mempagesize(Ctx& ctx) { +template MaybeResult mempagesize(Ctx& ctx) { if (!ctx.in.takeSExprStart("pagesize"sv)) { - return std::nullopt; // No pagesize specified + return {}; // No pagesize specified } auto pageSize = ctx.in.takeU64(); if (!pageSize) { @@ -849,7 +850,7 @@ template Result> mempagesize(Ctx& ctx) { return ctx.in.err("memory page size can only be 1 or 64 KiB"); } - return std::make_optional(pageSizeLog2); + return pageSizeLog2; } // memtype ::= (limits32 | 'i32' limits32 | 'i64' limit64) shared? mempagesize? @@ -874,9 +875,11 @@ Result memtypeContinued(Ctx& ctx, Type addressType) { if (ctx.in.takeKeyword("shared"sv)) { shared = true; } - auto pageSize = mempagesize(ctx); - CHECK_ERR(pageSize); - return ctx.makeMemType(addressType, *limits, shared, *pageSize); + MaybeResult mempageSize = mempagesize(ctx); + CHECK_ERR(mempageSize); + const uint8_t pageSizeLog2 = + mempageSize ? *mempageSize : Memory::kDefaultPageSizeLog2; + return ctx.makeMemType(addressType, *limits, shared, pageSizeLog2); } // memorder ::= 'seqcst' | 'acqrel' @@ -3587,7 +3590,7 @@ template MaybeResult<> memory(Ctx& ctx) { std::optional mtype; std::optional data; - auto mempageSize = mempagesize(ctx); + MaybeResult mempageSize = mempagesize(ctx); CHECK_ERR(mempageSize); if (ctx.in.takeSExprStart("data"sv)) { if (import) { @@ -3598,12 +3601,14 @@ template MaybeResult<> memory(Ctx& ctx) { if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of inline data"); } + const uint8_t pageSizeLog2 = + mempageSize.getPtr() ? *mempageSize : Memory::kDefaultPageSizeLog2; mtype = ctx.makeMemType(addressType, - ctx.getLimitsFromData(*datastr, *mempageSize), + ctx.getLimitsFromData(*datastr, pageSizeLog2), false, - *mempageSize); + pageSizeLog2); data = *datastr; - } else if ((*mempageSize).has_value()) { + } else if (mempageSize) { // If we have a memory page size not within a memtype expression, we expect // a memory abbreviation. return ctx.in.err("expected data segment in memory abbreviation"); diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp index 5d0a2f19137..eecfcc2539d 100644 --- a/src/passes/MultiMemoryLowering.cpp +++ b/src/passes/MultiMemoryLowering.cpp @@ -43,6 +43,7 @@ #include "ir/abstract.h" #include "ir/module-utils.h" #include "ir/names.h" +#include "support/utilities.h" #include "wasm-builder.h" #include #include @@ -68,11 +69,9 @@ struct MultiMemoryLowering : public Pass { // properties will be set Name module; Name base; - // The page size of the combined memory - uint8_t pageSizeLog2; - // The initial page count of the combined memory + // The initial page size of the combined memory Address totalInitialPages; - // The max page count of the combined memory + // The max page size of the combined memory Address totalMaxPages; // There is no offset for the first memory, so offsetGlobalNames will always // have a size that is one less than the count of memories at the time this @@ -437,7 +436,7 @@ struct MultiMemoryLowering : public Pass { : Builder::MemoryInfo::Memory64; isShared = getFirstMemory().shared; isImported = getFirstMemory().imported(); - pageSizeLog2 = Memory::kDefaultPageSizeLog2; + uint8_t const pageSizeLog2 = getFirstMemory().pageSizeLog2; for (auto& memory : wasm->memories) { // We are assuming that each memory is configured the same as the first // and assert if any of the memories does not match this configuration @@ -448,26 +447,24 @@ struct MultiMemoryLowering : public Pass { if (memory->name != getFirstMemory().name && memory->imported()) { Fatal() << "MultiMemoryLowering: only the first memory can be imported"; } - - // Calculating the page size of the combined memory. - // This corresponds to the smaller granularity among combined memories - pageSizeLog2 = std::min(pageSizeLog2, memory->pageSizeLog2); + // TODO: handle memory with different page sizes. + if (memory->pageSizeLog2 != pageSizeLog2) { + Fatal() + << "MultiMemoryLowering: all memories must have the same page size"; + } // Calculating the total initial and max page size for the combined memory // by totaling the initial and max page sizes for the memories in the // module - totalInitialPages = - totalInitialPages + - (memory->initial << (memory->pageSizeLog2 - pageSizeLog2)); + totalInitialPages = totalInitialPages + memory->initial; if (memory->hasMax()) { - totalMaxPages = totalMaxPages + - (memory->max << (memory->pageSizeLog2 - pageSizeLog2)); + totalMaxPages = totalMaxPages + memory->max; } } // Ensuring valid initial and max page sizes that do not exceed the number // of pages addressable by the pointerType - Address maxSize = pointerType == Type::i32 ? 1ull << (32 - pageSizeLog2) - : 1ull << (64 - pageSizeLog2); + Address maxSize = + Type::i32 ? getFirstMemory().maxSize32() : getFirstMemory().maxSize64(); if (totalMaxPages > maxSize || totalMaxPages == 0) { totalMaxPages = Memory::kUnlimitedSize; } @@ -514,10 +511,9 @@ struct MultiMemoryLowering : public Pass { Name name = Names::getValidGlobalName( *wasm, memory->name.toString() + "_byte_offset"); offsetGlobalNames.push_back(std::move(name)); - addGlobal(name, offsetRunningTotal << pageSizeLog2); + addGlobal(name, offsetRunningTotal << memory->pageSizeLog2); } - offsetRunningTotal += memory->initial - << (memory->pageSizeLog2 - pageSizeLog2); + offsetRunningTotal += memory->initial; } } @@ -565,17 +561,16 @@ struct MultiMemoryLowering : public Pass { functionName, Signature(pointerType, pointerType), {}); function->setLocalName(0, "page_delta"); auto currPageSizeLog2 = wasm->memories[memIdx]->pageSizeLog2; - auto pageSizeConst = [&]() { - return builder.makeConst(Literal(currPageSizeLog2)); - }; - auto getOffsetDelta = [&]() { + auto makeMulPageSize = [&](Expression* pageCountExpr) -> Expression* { if (currPageSizeLog2 == 0) { - return static_cast(builder.makeLocalGet(0, pointerType)); + return pageCountExpr; } - return static_cast( - builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Shl), - builder.makeLocalGet(0, pointerType), - pageSizeConst())); + return builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Shl), + pageCountExpr, + builder.makeConst(Literal(currPageSizeLog2))); + }; + auto getOffsetDelta = [&]() -> Expression* { + return makeMulPageSize(builder.makeLocalGet(0, pointerType)); }; auto getMoveSource = [&](Name global) { return builder.makeGlobalGet(global, pointerType); @@ -604,14 +599,7 @@ struct MultiMemoryLowering : public Pass { builder.makeBinary( EqInt32, builder.makeMemoryGrow( - currPageSizeLog2 - pageSizeLog2 == 0 - ? static_cast(builder.makeLocalGet(0, pointerType)) - : static_cast(builder.makeBinary( - Abstract::getBinary(pointerType, Abstract::Shl), - builder.makeLocalGet(0, pointerType), - builder.makeConst(Literal(currPageSizeLog2 - pageSizeLog2)))), - combinedMemory, - memoryInfo), + builder.makeLocalGet(0, pointerType), combinedMemory, memoryInfo), builder.makeConst(-1)), builder.makeReturn(builder.makeConst(-1)))); @@ -632,13 +620,7 @@ struct MultiMemoryLowering : public Pass { // size builder.makeBinary( Abstract::getBinary(pointerType, Abstract::Sub), - currPageSizeLog2 == 0 - ? static_cast( - builder.makeLocalGet(sizeLocal, pointerType)) - : static_cast(builder.makeBinary( - Abstract::getBinary(pointerType, Abstract::Shl), - builder.makeLocalGet(sizeLocal, pointerType), - pageSizeConst())), + makeMulPageSize(builder.makeLocalGet(sizeLocal, pointerType)), getMoveSource(offsetGlobalName)), combinedMemory, combinedMemory)); @@ -672,8 +654,9 @@ struct MultiMemoryLowering : public Pass { auto function = Builder::makeFunction( functionName, Signature(Type::none, pointerType), {}); Expression* functionBody; + auto currPageSizeLog2 = wasm->memories[memIdx]->pageSizeLog2; auto pageSizeConst = [&]() { - return builder.makeConst(Literal(pageSizeLog2)); + return builder.makeConst(Literal(currPageSizeLog2)); }; auto getOffsetInPageUnits = [&](Name global) { return builder.makeBinary( @@ -724,7 +707,6 @@ struct MultiMemoryLowering : public Pass { memory->base = base; memory->module = module; } - memory->pageSizeLog2 = pageSizeLog2; wasm->addMemory(std::move(memory)); } diff --git a/src/tools/wasm-split/instrumenter.cpp b/src/tools/wasm-split/instrumenter.cpp index 6cc783a7a8a..56fa67373e3 100644 --- a/src/tools/wasm-split/instrumenter.cpp +++ b/src/tools/wasm-split/instrumenter.cpp @@ -82,7 +82,7 @@ void Instrumenter::addSecondaryMemory(size_t numFuncs) { Names::getValidMemoryName(*wasm, config.secondaryMemoryName); // Create a memory with enough pages to write into // The memory uses the default page size to avoid issues in case custom page - // sizes are not supported + // sizes are not supported. size_t pages = (numFuncs + Memory::kDefaultPageSize - 1) / Memory::kDefaultPageSize; auto mem = Builder::makeMemory(secondaryMemory, pages, pages, true); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 0d1841dbca7..b6bdeac62f4 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -22,6 +22,7 @@ #define wasm_wasm_binary_h #include +#include #include #include @@ -1384,13 +1385,14 @@ class WasmBinaryWriter { void write(); void writeHeader(); int32_t writeU32LEBPlaceholder(); - void writeResizableLimits(Address initial, - Address maximum, - bool hasMaximum, - bool shared, - bool is64, - bool hasPageSize, - Address::address32_t pageSizeLog2); + void writeResizableLimits( + Address initial, Address maximum, bool hasMaximum, bool shared, bool is64); + void writeMemoryResizableLimits(Address initial, + Address maximum, + bool hasMaximum, + bool shared, + bool is64, + uint8_t pageSizeLog2); template int32_t startSection(T code); void finishSection(int32_t start); int32_t startSubsection(BinaryConsts::CustomSections::Subsection code); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 49d72f79958..96824c8671a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3648,12 +3648,7 @@ class ModuleRunnerBase : public ExpressionRunner { } Address::address64_t getMemoryPageSize(Name memory) { - Memory* mem = wasm.getMemoryOrNull(memory); - if (!mem) { - externalInterface->trap( - "getMemoryPageSize called on non-existing memory"); - } - return mem->pageSize(); + return wasm.getMemory(memory)->pageSize(); } public: diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f2fb3340a6f..e01001d3582 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "ir/module-utils.h" #include "ir/names.h" @@ -108,18 +109,11 @@ int32_t WasmBinaryWriter::writeU32LEBPlaceholder() { return o.writeU32LEBPlaceholder(); } -void WasmBinaryWriter::writeResizableLimits(Address initial, - Address maximum, - bool hasMaximum, - bool shared, - bool is64, - bool hasPageSize, - Address::address32_t pageSizeLog2) { - uint32_t flags = - (hasMaximum ? (uint32_t)BinaryConsts::HasMaximum : 0U) | - (shared ? (uint32_t)BinaryConsts::IsShared : 0U) | - (is64 ? (uint32_t)BinaryConsts::Is64 : 0U) | - (hasPageSize ? (uint32_t)BinaryConsts::HasCustomPageSize : 0U); +void WasmBinaryWriter::writeResizableLimits( + Address initial, Address maximum, bool hasMaximum, bool shared, bool is64) { + uint32_t flags = (hasMaximum ? (uint32_t)BinaryConsts::HasMaximum : 0U) | + (shared ? (uint32_t)BinaryConsts::IsShared : 0U) | + (is64 ? (uint32_t)BinaryConsts::Is64 : 0U); o << U32LEB(flags); if (is64) { o << U64LEB(initial); @@ -132,7 +126,33 @@ void WasmBinaryWriter::writeResizableLimits(Address initial, o << U32LEB(maximum); } } - if (hasPageSize) { +} + +void WasmBinaryWriter::writeMemoryResizableLimits(Address initial, + Address maximum, + bool hasMaximum, + bool shared, + bool is64, + uint8_t pageSizeLog2) { + uint32_t flags = (hasMaximum ? (uint32_t)BinaryConsts::HasMaximum : 0U) | + (shared ? (uint32_t)BinaryConsts::IsShared : 0U) | + (is64 ? (uint32_t)BinaryConsts::Is64 : 0U) | + (pageSizeLog2 != Memory::kDefaultPageSizeLog2 + ? (uint32_t)BinaryConsts::HasCustomPageSize + : 0U); + o << U32LEB(flags); + if (is64) { + o << U64LEB(initial); + if (hasMaximum) { + o << U64LEB(maximum); + } + } else { + o << U32LEB(initial); + if (hasMaximum) { + o << U32LEB(maximum); + } + } + if (pageSizeLog2 != Memory::kDefaultPageSizeLog2) { o << U32LEB(pageSizeLog2); } } @@ -215,13 +235,12 @@ void WasmBinaryWriter::writeMemories() { auto num = importInfo->getNumDefinedMemories(); o << U32LEB(num); ModuleUtils::iterDefinedMemories(*wasm, [&](Memory* memory) { - writeResizableLimits(memory->initial, - memory->max, - memory->hasMax(), - memory->shared, - memory->is64(), - memory->pageSizeLog2 != Memory::kDefaultPageSizeLog2, - memory->pageSizeLog2); + writeMemoryResizableLimits(memory->initial, + memory->max, + memory->hasMax(), + memory->shared, + memory->is64(), + memory->pageSizeLog2); }); finishSection(start); } @@ -362,13 +381,12 @@ void WasmBinaryWriter::writeImports() { ModuleUtils::iterImportedMemories(*wasm, [&](Memory* memory) { writeImportHeader(memory); o << U32LEB(int32_t(ExternalKind::Memory)); - writeResizableLimits(memory->initial, - memory->max, - memory->hasMax(), - memory->shared, - memory->is64(), - memory->pageSizeLog2 != Memory::kDefaultPageSizeLog2, - memory->pageSizeLog2); + writeMemoryResizableLimits(memory->initial, + memory->max, + memory->hasMax(), + memory->shared, + memory->is64(), + memory->pageSizeLog2); }); ModuleUtils::iterImportedTables(*wasm, [&](Table* table) { writeImportHeader(table); @@ -378,9 +396,7 @@ void WasmBinaryWriter::writeImports() { table->max, table->hasMax(), /*shared=*/false, - table->is64(), - /*hasPageSize=*/false, - /*PageSize*/ 1); + table->is64()); }); finishSection(start); } @@ -794,9 +810,7 @@ void WasmBinaryWriter::writeTableDeclarations() { table->max, table->hasMax(), /*shared=*/false, - table->is64(), - /*hasPageSize=*/false, - /*PageSize*/ 1); + table->is64()); }); finishSection(start); } From df2240227471b9d375b0e5d04e71a51018972664 Mon Sep 17 00:00:00 2001 From: Cai Congcong Date: Fri, 13 Feb 2026 11:34:17 +0800 Subject: [PATCH 3/5] fix build --- src/passes/MultiMemoryLowering.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp index eecfcc2539d..c22a5db82d7 100644 --- a/src/passes/MultiMemoryLowering.cpp +++ b/src/passes/MultiMemoryLowering.cpp @@ -463,8 +463,8 @@ struct MultiMemoryLowering : public Pass { } // Ensuring valid initial and max page sizes that do not exceed the number // of pages addressable by the pointerType - Address maxSize = - Type::i32 ? getFirstMemory().maxSize32() : getFirstMemory().maxSize64(); + Address maxSize = pointerType == Type::i32 ? getFirstMemory().maxSize32() + : getFirstMemory().maxSize64(); if (totalMaxPages > maxSize || totalMaxPages == 0) { totalMaxPages = Memory::kUnlimitedSize; } From c64d1e372e770cbada550677f69f7aa3a714418c Mon Sep 17 00:00:00 2001 From: Cai Congcong Date: Fri, 13 Feb 2026 14:05:00 +0800 Subject: [PATCH 4/5] fix spectest --- scripts/test/shared.py | 4 - src/ir/memory-utils.cpp | 3 +- src/wasm.h | 10 +- src/wasm/wasm-validator.cpp | 10 ++ test/spec/custom-page-sizes-invalid.wast | 115 ------------------- test/spec/custom-page-sizes.wast | 138 ----------------------- test/spec/memory_max.wast | 37 ------ test/spec/memory_max_i64.wast | 39 ------- 8 files changed, 21 insertions(+), 335 deletions(-) delete mode 100644 test/spec/custom-page-sizes-invalid.wast delete mode 100644 test/spec/custom-page-sizes.wast delete mode 100644 test/spec/memory_max.wast delete mode 100644 test/spec/memory_max_i64.wast diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 42c41ae18dd..b4f8208c678 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -388,9 +388,6 @@ def get_tests(test_dir, extensions=[], recursive=False): # Unlinkable module accepted 'linking.wast', - # TODO: failure - 'memory_max.wast', - 'memory_max_i64.wast', # Invalid module accepted 'unreached-invalid.wast', @@ -399,7 +396,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'elem.wast', ] SPEC_TESTSUITE_PROPOSALS_TO_SKIP = [ - 'custom-page-sizes', 'wide-arithmetic', ] diff --git a/src/ir/memory-utils.cpp b/src/ir/memory-utils.cpp index 5d377dc4bda..4bd629e2cbc 100644 --- a/src/ir/memory-utils.cpp +++ b/src/ir/memory-utils.cpp @@ -22,7 +22,8 @@ namespace wasm::MemoryUtils { bool isSubType(const Memory& a, const Memory& b) { return a.shared == b.shared && a.addressType == b.addressType && - a.initial >= b.initial && a.max <= b.max; + a.initial >= b.initial && a.max <= b.max && + a.pageSizeLog2 == b.pageSizeLog2; } bool flatten(Module& wasm) { diff --git a/src/wasm.h b/src/wasm.h index f4ff1512217..1bc83bc7ccc 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -27,9 +27,12 @@ #include #include #include +#include #include +#include #include #include +#include #include #include @@ -2488,7 +2491,12 @@ class Memory : public Importable { bool hasMax() { return max != kUnlimitedSize; } bool is64() { return addressType == Type::i64; } Address::address64_t maxSize32() const { return 1ull << (32 - pageSizeLog2); } - Address::address64_t maxSize64() const { return 1ull << (64 - pageSizeLog2); } + Address::address64_t maxSize64() const { + if (pageSizeLog2 == 0) { + return std::numeric_limits::max(); + } + return 1ull << (64 - pageSizeLog2); + } Address::address64_t pageSize() const { return 1ull << static_cast(pageSizeLog2); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e48370d1c39..03136f40028 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -4605,6 +4606,15 @@ static void validateMemories(Module& module, ValidationInfo& info) { info.shouldBeTrue(module.features.hasMemory64(), "memory", "64-bit memories require memory64 [--enable-memory64]"); + // TODO: The custom pages sizes proposals do not mention a verification + // method when the memory64 proposal is active simultaneously; the current + // implementation is based on spectest. + info.shouldBeTrue(memory->initial <= memory->maxSize64(), + "memory", + "initial memory must be <= 16EB"); + info.shouldBeTrue(!memory->hasMax() || memory->max <= memory->maxSize64(), + "memory", + "max memory must be <= 16EB, or unlimited"); } else { info.shouldBeTrue(memory->initial <= memory->maxSize32(), "memory", diff --git a/test/spec/custom-page-sizes-invalid.wast b/test/spec/custom-page-sizes-invalid.wast deleted file mode 100644 index 5fb121a0aaa..00000000000 --- a/test/spec/custom-page-sizes-invalid.wast +++ /dev/null @@ -1,115 +0,0 @@ -;; Page size that is not a power of two. -(assert_malformed - (module quote "(memory 0 (pagesize 3))") - "invalid custom page size" -) - -(assert_malformed - (module quote "(memory 0 (pagesize 0))") - "invalid custom page size" -) - -;; Power-of-two page sizes that are not 1 or 64KiB. -(assert_invalid - (module (memory 0 (pagesize 2))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 4))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 8))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 16))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 32))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 64))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 128))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 256))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 512))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 1024))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 2048))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 4096))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 8192))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 16384))) - "invalid custom page size" -) -(assert_invalid - (module (memory 0 (pagesize 32768))) - "invalid custom page size" -) - -;; Power-of-two page size that is larger than 64KiB. -(assert_invalid - (module (memory 0 (pagesize 0x20000))) - "invalid custom page size" -) - -;; Power of two page size that cannot fit in a u64 to exercise checks against -;; shift overflow. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\05\04\01" ;; Memory section - - ;; memory 0 - "\08" ;; flags w/ custom page size - "\00" ;; minimum = 0 - "\41" ;; pagesize = 2**65 - ) - "invalid custom page size" -) - -;; Importing a memory with the wrong page size. - -(module $m - (memory (export "small-pages-memory") 0 (pagesize 1)) - (memory (export "large-pages-memory") 0 (pagesize 65536)) -) -(register "m" $m) - -;; (assert_unlinkable -;; (module -;; (memory (import "m" "small-pages-memory") 0 (pagesize 65536)) -;; ) -;; "memory types incompatible" -;; ) - -;; (assert_unlinkable -;; (module -;; (memory (import "m" "large-pages-memory") 0 (pagesize 1)) -;; ) -;; "memory types incompatible" -;; ) diff --git a/test/spec/custom-page-sizes.wast b/test/spec/custom-page-sizes.wast deleted file mode 100644 index 332051daacb..00000000000 --- a/test/spec/custom-page-sizes.wast +++ /dev/null @@ -1,138 +0,0 @@ -;; Check all the valid custom page sizes. -(module (memory 1 (pagesize 1))) -(module (memory 1 (pagesize 65536))) - -;; Check them all again with maximums specified. -(module (memory 1 2 (pagesize 1))) -(module (memory 1 2 (pagesize 65536))) - -;; Check the behavior of memories with page size 1. -(module - (memory 0 (pagesize 1)) - (func (export "size") (result i32) - memory.size - ) - (func (export "grow") (param i32) (result i32) - (memory.grow (local.get 0)) - ) - (func (export "load") (param i32) (result i32) - (i32.load8_u (local.get 0)) - ) - (func (export "store") (param i32 i32) - (i32.store8 (local.get 0) (local.get 1)) - ) -) - -(assert_return (invoke "size") (i32.const 0)) -(assert_trap (invoke "load" (i32.const 0)) "out of bounds memory access") - -(assert_return (invoke "grow" (i32.const 65536)) (i32.const 0)) -(assert_return (invoke "size") (i32.const 65536)) -(assert_return (invoke "load" (i32.const 65535)) (i32.const 0)) -(assert_return (invoke "store" (i32.const 65535) (i32.const 1))) -(assert_return (invoke "load" (i32.const 65535)) (i32.const 1)) -(assert_trap (invoke "load" (i32.const 65536)) "out of bounds memory access") - -(assert_return (invoke "grow" (i32.const 65536)) (i32.const 65536)) -(assert_return (invoke "size") (i32.const 131072)) -(assert_return (invoke "load" (i32.const 131071)) (i32.const 0)) -(assert_return (invoke "store" (i32.const 131071) (i32.const 1))) -(assert_return (invoke "load" (i32.const 131071)) (i32.const 1)) -(assert_trap (invoke "load" (i32.const 131072)) "out of bounds memory access") - -;; Although smaller page sizes let us get to memories larger than 2**16 pages, -;; we can't do that with the default page size, even if we explicitly state it -;; as a custom page size. -(module - (memory 0 (pagesize 65536)) - (func (export "size") (result i32) - memory.size - ) - (func (export "grow") (param i32) (result i32) - (memory.grow (local.get 0)) - ) -) -(assert_return (invoke "size") (i32.const 0)) -(assert_return (invoke "grow" (i32.const 65537)) (i32.const -1)) -(assert_return (invoke "size") (i32.const 0)) - -;; Can copy between memories of different page sizes. -(module - (memory $small 10 (pagesize 1)) - (memory $large 1 (pagesize 65536)) - - (data (memory $small) (i32.const 0) "\11\22\33\44") - (data (memory $large) (i32.const 0) "\55\66\77\88") - - (func (export "copy-small-to-large") (param i32 i32 i32) - (memory.copy $large $small (local.get 0) (local.get 1) (local.get 2)) - ) - - (func (export "copy-large-to-small") (param i32 i32 i32) - (memory.copy $small $large (local.get 0) (local.get 1) (local.get 2)) - ) - - (func (export "load8-small") (param i32) (result i32) - (i32.load8_u $small (local.get 0)) - ) - - (func (export "load8-large") (param i32) (result i32) - (i32.load8_u $large (local.get 0)) - ) -) - -(assert_return (invoke "copy-small-to-large" (i32.const 6) (i32.const 0) (i32.const 2))) -(assert_return (invoke "load8-large" (i32.const 6)) (i32.const 0x11)) -(assert_return (invoke "load8-large" (i32.const 7)) (i32.const 0x22)) - -(assert_return (invoke "copy-large-to-small" (i32.const 4) (i32.const 1) (i32.const 3))) -(assert_return (invoke "load8-small" (i32.const 4)) (i32.const 0x66)) -(assert_return (invoke "load8-small" (i32.const 5)) (i32.const 0x77)) -(assert_return (invoke "load8-small" (i32.const 6)) (i32.const 0x88)) - -;; Can link together modules that export and import memories with custom page -;; sizes. - -(module $m - (memory (export "small-pages-memory") 0 (pagesize 1)) - (memory (export "large-pages-memory") 0 (pagesize 65536)) -) -(register "m" $m) - -(module - (memory (import "m" "small-pages-memory") 0 (pagesize 1)) -) - -(module - (memory (import "m" "large-pages-memory") 0 (pagesize 65536)) -) - -;; Inline data segments - -;; pagesize 0 -(assert_malformed (module quote "(memory (pagesize 0) (data))") "invalid custom page size") - -;; pagesize 1 -(module - (memory (pagesize 1) (data "xyz")) - (func (export "size") (result i32) - memory.size) - (func (export "grow") (param i32) (result i32) - (memory.grow (local.get 0))) - (func (export "load") (param i32) (result i32) - (i32.load8_u (local.get 0)))) - -(assert_return (invoke "size") (i32.const 3)) -(assert_return (invoke "load" (i32.const 0)) (i32.const 120)) -(assert_return (invoke "load" (i32.const 1)) (i32.const 121)) -(assert_return (invoke "load" (i32.const 2)) (i32.const 122)) -(assert_trap (invoke "load" (i32.const 3)) "out of bounds memory access") -(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) - -;; pagesize 65536 -(module - (memory (pagesize 65536) (data "xyz")) - (func (export "size") (result i32) - memory.size)) - -(assert_return (invoke "size") (i32.const 1)) diff --git a/test/spec/memory_max.wast b/test/spec/memory_max.wast deleted file mode 100644 index dfc89b6696e..00000000000 --- a/test/spec/memory_max.wast +++ /dev/null @@ -1,37 +0,0 @@ -;; Maximum memory sizes. -;; -;; These modules are valid, but instantiating them is unnecessary -;; and would only allocate very large memories and slow down running -;; the spec tests. Therefore, add a missing import so that it cannot -;; be instantiated and use `assert_unlinkable`. This approach -;; enforces that the module itself is still valid, but that its -;; instantiation fails early (hopefully before any memories are -;; actually allocated). - -;; i32 (pagesize 1) -(assert_unlinkable - (module - (import "test" "unknown" (func)) - (memory 0xFFFF_FFFF (pagesize 1))) - "unknown import") - -;; i32 (default pagesize) -(assert_unlinkable - (module - (import "test" "unknown" (func)) - (memory 65536 (pagesize 65536))) - "unknown import") - -;; Memory size just over the maximum. - -;; i32 (pagesize 1) -(assert_invalid - (module - (memory 0x1_0000_0000 (pagesize 1))) - "memory size must be at most") - -;; i32 (default pagesize) -(assert_invalid - (module - (memory 65537 (pagesize 65536))) - "memory size must be at most") diff --git a/test/spec/memory_max_i64.wast b/test/spec/memory_max_i64.wast deleted file mode 100644 index 607642decf8..00000000000 --- a/test/spec/memory_max_i64.wast +++ /dev/null @@ -1,39 +0,0 @@ -;; Maximum memory sizes. -;; -;; These modules are valid, but instantiating them is unnecessary -;; and would only allocate very large memories and slow down running -;; the spec tests. Therefore, add a missing import so that it cannot -;; be instantiated and use `assert_unlinkable`. This approach -;; enforces that the module itself is still valid, but that its -;; instantiation fails early (hopefully before any memories are -;; actually allocated). - -;; i64 (pagesize 1) -(assert_unlinkable - (module - (import "test" "import" (func)) - (memory i64 0xFFFF_FFFF_FFFF_FFFF (pagesize 1))) - "unknown import") - -;; i64 (default pagesize) -(assert_unlinkable - (module - (import "test" "unknown" (func)) - (memory i64 0x1_0000_0000_0000 (pagesize 65536))) - "unknown import") - -;; Memory size just over the maximum. -;; -;; These are malformed (for pagesize 1) -;; or invalid (for other pagesizes). - -;; i64 (pagesize 1) -(assert_malformed - (module quote "(memory i64 0x1_0000_0000_0000_0000 (pagesize 1))") - "constant out of range") - -;; i64 (default pagesize) -(assert_invalid - (module - (memory i64 0x1_0000_0000_0001 (pagesize 65536))) - "memory size must be at most") From 5c7061e72fe8da73fdd83c6888ca63ab16a4b7b5 Mon Sep 17 00:00:00 2001 From: Cai Congcong Date: Sat, 14 Feb 2026 10:07:36 +0800 Subject: [PATCH 5/5] [wip] try to do validation first to avoid OOM --- src/wasm-interpreter.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 96824c8671a..ab1d32069f5 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3208,13 +3208,13 @@ class ModuleRunnerBase : public ExpressionRunner { // (This is separate from the constructor so that it does not occur // synchronously, which makes some code patterns harder to write.) void instantiate(bool validateImports_ = false) { - // initialize the rest of the external interface - externalInterface->init(wasm, *self()); - if (validateImports_) { validateImports(); } + // initialize the rest of the external interface + externalInterface->init(wasm, *self()); + initializeGlobals(); initializeTables(); initializeTags();