diff options
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/MultiMemoryLowering.cpp | 421 | ||||
-rw-r--r-- | src/passes/pass.cpp | 3 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | src/wasm.h | 2 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 3 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 3 | ||||
-rw-r--r-- | test/lit/passes/multi-memory-lowering.wast | 247 |
8 files changed, 681 insertions, 0 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 152594431..cedb7d764 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -62,6 +62,7 @@ set(passes_SOURCES MergeLocals.cpp Metrics.cpp MinifyImportsAndExports.cpp + MultiMemoryLowering.cpp NameList.cpp NameTypes.cpp OnceReduction.cpp diff --git a/src/passes/MultiMemoryLowering.cpp b/src/passes/MultiMemoryLowering.cpp new file mode 100644 index 000000000..9e9637b36 --- /dev/null +++ b/src/passes/MultiMemoryLowering.cpp @@ -0,0 +1,421 @@ +/* + * Copyright 2022 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Condensing a module with multiple memories into a module with a single memory +// for browsers that don’t support multiple memories. +// +// This pass also disables multi-memories so that the target features section in +// the emitted module does not report the use of MultiMemories. Disabling the +// multi-memories feature also prevents later passes from adding additional +// memories. +// +// Also worth noting that we are diverging from the spec with regards to +// handling load and store instructions. We are not trapping if the offset + +// write size is larger than the length of the memory's data. Warning: +// out-of-bounds loads and stores can read junk out of or corrupt other +// memories instead of trapping. + +#include "ir/module-utils.h" +#include "ir/names.h" +#include "wasm-builder.h" +#include <pass.h> +#include <wasm.h> + +namespace wasm { + +struct MultiMemoryLowering : public Pass { + Module* wasm = nullptr; + // The name of the single memory that exists after this pass is run + Name combinedMemory; + // The type of the single memory + Type pointerType; + // Used to indicate the type of the single memory when creating instructions + // (memory.grow, memory.size) for that memory + Builder::MemoryInfo memoryInfo; + // If the combined memory is shared + bool isShared; + // The initial page size of the combined memory + Address totalInitialPages; + // 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 + // pass is run. Use helper getOffsetGlobal(Index) to index the vector + // conveniently without having to manipulate the index directly + std::vector<Name> offsetGlobalNames; + // Maps from the name of the memory to its index as seen in the + // module->memories vector + std::unordered_map<Name, Index> memoryIdxMap; + // A vector of the memory size function names that were created proactively + // for each memory + std::vector<Name> memorySizeNames; + // A vector of the memory grow functions that were created proactively for + // each memory + std::vector<Name> memoryGrowNames; + + void run(Module* module) override { + module->features.disable(FeatureSet::MultiMemories); + + // If there are no memories or 1 memory, skip this pass + if (module->memories.size() <= 1) { + return; + } + + this->wasm = module; + + prepCombinedMemory(); + addOffsetGlobals(); + adjustActiveDataSegmentOffsets(); + createMemorySizeFunctions(); + createMemoryGrowFunctions(); + removeExistingMemories(); + addCombinedMemory(); + + struct Replacer : public WalkerPass<PostWalker<Replacer>> { + MultiMemoryLowering& parent; + Builder builder; + Replacer(MultiMemoryLowering& parent, Module& wasm) + : parent(parent), builder(wasm) {} + // Avoid visiting the custom functions added by the parent pass + // MultiMemoryLowering + void walkFunction(Function* func) { + for (Name funcName : parent.memorySizeNames) { + if (funcName == func->name) { + return; + } + } + for (Name funcName : parent.memoryGrowNames) { + if (funcName == func->name) { + return; + } + } + super::walkFunction(func); + } + + void visitMemoryGrow(MemoryGrow* curr) { + auto idx = parent.memoryIdxMap.at(curr->memory); + Name funcName = parent.memoryGrowNames[idx]; + replaceCurrent(builder.makeCall(funcName, {curr->delta}, curr->type)); + } + + void visitMemorySize(MemorySize* curr) { + auto idx = parent.memoryIdxMap.at(curr->memory); + Name funcName = parent.memorySizeNames[idx]; + replaceCurrent(builder.makeCall(funcName, {}, curr->type)); + } + + // TODO: Add an option to add bounds checks. + void visitLoad(Load* curr) { + auto idx = parent.memoryIdxMap.at(curr->memory); + auto global = parent.getOffsetGlobal(idx); + curr->memory = parent.combinedMemory; + if (!global) { + return; + } + curr->ptr = builder.makeBinary( + Abstract::getBinary(parent.pointerType, Abstract::Add), + builder.makeGlobalGet(global, parent.pointerType), + curr->ptr); + } + + // We diverge from the spec here and are not trapping if the offset + type + // / 8 is larger than the length of the memory's data. Warning, + // out-of-bounds loads and stores can read junk out of or corrupt other + // memories instead of trapping + void visitStore(Store* curr) { + auto idx = parent.memoryIdxMap.at(curr->memory); + auto global = parent.getOffsetGlobal(idx); + curr->memory = parent.combinedMemory; + if (!global) { + return; + } + curr->ptr = builder.makeBinary( + Abstract::getBinary(parent.pointerType, Abstract::Add), + builder.makeGlobalGet(global, parent.pointerType), + curr->ptr); + } + }; + Replacer(*this, *wasm).run(getPassRunner(), wasm); + } + + // Returns the global name for the given idx. There is no global for the first + // idx, so an empty name is returned + Name getOffsetGlobal(Index idx) { + // There is no offset global for the first memory + if (idx == 0) { + return Name(); + } + + // Since there is no offset global for the first memory, we need to + // subtract one when indexing into the offsetGlobalName vector + return offsetGlobalNames[idx - 1]; + } + + // Whether the idx represents the last memory. Since there is no offset global + // for the first memory, the last memory is represented by the size of + // offsetGlobalNames + bool isLastMemory(Index idx) { return idx == offsetGlobalNames.size(); } + + void prepCombinedMemory() { + pointerType = wasm->memories[0]->indexType; + memoryInfo = pointerType == Type::i32 ? Builder::MemoryInfo::Memory32 + : Builder::MemoryInfo::Memory64; + isShared = wasm->memories[0]->shared; + 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 + assert(memory->shared == isShared); + assert(memory->indexType == pointerType); + + // 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; + if (memory->hasMax()) { + 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 ? Memory::kMaxSize32 : Memory::kMaxSize64; + if (totalMaxPages > maxSize || totalMaxPages == 0) { + totalMaxPages = Memory::kUnlimitedSize; + } + if (totalInitialPages > totalMaxPages) { + totalInitialPages = totalMaxPages; + } + + // Creating the combined memory name so we can reference the combined memory + // in subsequent instructions before it is added to the module + combinedMemory = Names::getValidMemoryName(*wasm, "combined_memory"); + } + + void addOffsetGlobals() { + auto addGlobal = [&](Name name, size_t offset) { + auto global = Builder::makeGlobal( + name, + pointerType, + Builder(*wasm).makeConst(Literal::makeFromInt64(offset, pointerType)), + Builder::Mutable); + wasm->addGlobal(std::move(global)); + }; + + size_t offsetRunningTotal = 0; + for (Index i = 0; i < wasm->memories.size(); i++) { + auto& memory = wasm->memories[i]; + memoryIdxMap[memory->name] = i; + // We don't need a page offset global for the first memory as it's always + // 0 + if (i != 0) { + Name name = Names::getValidGlobalName( + *wasm, memory->name.toString() + "_byte_offset"); + offsetGlobalNames.push_back(std::move(name)); + addGlobal(name, offsetRunningTotal * Memory::kPageSize); + } + offsetRunningTotal += memory->initial; + } + } + + void adjustActiveDataSegmentOffsets() { + Builder builder(*wasm); + ModuleUtils::iterActiveDataSegments(*wasm, [&](DataSegment* dataSegment) { + assert(dataSegment->offset->is<Const>() && + "TODO: handle non-const segment offsets"); + auto idx = memoryIdxMap.at(dataSegment->memory); + dataSegment->memory = combinedMemory; + // No need to update the offset of data segments for the first memory + if (idx != 0) { + auto offsetGlobalName = getOffsetGlobal(idx); + assert(wasm->features.hasExtendedConst()); + dataSegment->offset = builder.makeBinary( + Abstract::getBinary(pointerType, Abstract::Add), + builder.makeGlobalGet(offsetGlobalName, pointerType), + dataSegment->offset); + } + }); + } + + void createMemorySizeFunctions() { + for (Index i = 0; i < wasm->memories.size(); i++) { + auto function = memorySize(i, wasm->memories[i]->name); + memorySizeNames.push_back(function->name); + wasm->addFunction(std::move(function)); + } + } + + void createMemoryGrowFunctions() { + for (Index i = 0; i < wasm->memories.size(); i++) { + auto function = memoryGrow(i, wasm->memories[i]->name); + memoryGrowNames.push_back(function->name); + wasm->addFunction(std::move(function)); + } + } + + // This function replaces memory.grow instruction calls in the wasm module. + // Because the multiple discrete memories are lowered into a single memory, + // we need to adjust offsets as a particular memory receives an + // instruction to grow. + std::unique_ptr<Function> memoryGrow(Index memIdx, Name memoryName) { + Builder builder(*wasm); + Name name = memoryName.toString() + "_grow"; + Name functionName = Names::getValidFunctionName(*wasm, name); + auto function = Builder::makeFunction( + functionName, Signature(pointerType, pointerType), {}); + function->setLocalName(0, "page_delta"); + auto pageSizeConst = [&]() { + return builder.makeConst(Literal(Memory::kPageSize)); + }; + auto getOffsetDelta = [&]() { + return builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Mul), + builder.makeLocalGet(0, pointerType), + pageSizeConst()); + }; + auto getMoveSource = [&](Name global) { + return builder.makeGlobalGet(global, pointerType); + }; + Expression* functionBody; + Index sizeLocal = -1; + + Index returnLocal = + Builder::addVar(function.get(), "return_size", pointerType); + functionBody = builder.blockify(builder.makeLocalSet( + returnLocal, builder.makeCall(memorySizeNames[memIdx], {}, pointerType))); + + if (!isLastMemory(memIdx)) { + sizeLocal = Builder::addVar(function.get(), "memory_size", pointerType); + functionBody = builder.blockify( + functionBody, + builder.makeLocalSet( + sizeLocal, builder.makeMemorySize(combinedMemory, memoryInfo))); + } + + // TODO: Check the result of makeMemoryGrow for errors and return the error + // instead + functionBody = builder.blockify( + functionBody, + builder.makeDrop(builder.makeMemoryGrow( + builder.makeLocalGet(0, pointerType), combinedMemory, memoryInfo))); + + // If we are not growing the last memory, then we need to copy data, + // shifting it over to accomodate the increase from page_delta + if (!isLastMemory(memIdx)) { + // This offset is the starting pt for copying + auto offsetGlobalName = getOffsetGlobal(memIdx + 1); + functionBody = builder.blockify( + functionBody, + builder.makeMemoryCopy( + // destination + builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Add), + getMoveSource(offsetGlobalName), + getOffsetDelta()), + // source + getMoveSource(offsetGlobalName), + // size + builder.makeBinary( + Abstract::getBinary(pointerType, Abstract::Sub), + builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Mul), + builder.makeLocalGet(sizeLocal, pointerType), + pageSizeConst()), + getMoveSource(offsetGlobalName)), + combinedMemory, + combinedMemory)); + } + + // Adjust the offsets of the globals impacted by the memory.grow call + for (Index i = memIdx; i < offsetGlobalNames.size(); i++) { + auto& offsetGlobalName = offsetGlobalNames[i]; + functionBody = builder.blockify( + functionBody, + builder.makeGlobalSet( + offsetGlobalName, + builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Add), + getMoveSource(offsetGlobalName), + getOffsetDelta()))); + } + + functionBody = builder.blockify( + functionBody, builder.makeLocalGet(returnLocal, pointerType)); + + function->body = functionBody; + return function; + } + + // This function replaces memory.size instructions with a function that can + // return the size of each memory as if each was discrete and separate. + std::unique_ptr<Function> memorySize(Index memIdx, Name memoryName) { + Builder builder(*wasm); + Name name = memoryName.toString() + "_size"; + Name functionName = Names::getValidFunctionName(*wasm, name); + auto function = Builder::makeFunction( + functionName, Signature(Type::none, pointerType), {}); + Expression* functionBody; + auto pageSizeConst = [&]() { + return builder.makeConst(Literal(Memory::kPageSize)); + }; + auto getOffsetInPageUnits = [&](Name global) { + return builder.makeBinary( + Abstract::getBinary(pointerType, Abstract::DivU), + builder.makeGlobalGet(global, pointerType), + pageSizeConst()); + }; + + // offsetGlobalNames does not keep track of a global for the offset of + // wasm->memories[0] because it's always 0. As a result, the below + // calculations that involve offsetGlobalNames are intrinsically "offset". + // Thus, offsetGlobalNames[0] is the offset for wasm->memories[1] and + // the size of wasm->memories[0]. + if (memIdx == 0) { + auto offsetGlobalName = getOffsetGlobal(1); + functionBody = builder.blockify( + builder.makeReturn(getOffsetInPageUnits(offsetGlobalName))); + } else if (isLastMemory(memIdx)) { + auto offsetGlobalName = getOffsetGlobal(memIdx); + functionBody = builder.blockify(builder.makeReturn( + builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Sub), + builder.makeMemorySize(combinedMemory, memoryInfo), + getOffsetInPageUnits(offsetGlobalName)))); + } else { + auto offsetGlobalName = getOffsetGlobal(memIdx); + auto nextOffsetGlobalName = getOffsetGlobal(memIdx + 1); + functionBody = builder.blockify(builder.makeReturn( + builder.makeBinary(Abstract::getBinary(pointerType, Abstract::Sub), + getOffsetInPageUnits(nextOffsetGlobalName), + getOffsetInPageUnits(offsetGlobalName)))); + } + + function->body = functionBody; + return function; + } + + void removeExistingMemories() { + wasm->removeMemories([&](Memory* curr) { return true; }); + } + + void addCombinedMemory() { + auto memory = Builder::makeMemory(combinedMemory); + memory->shared = isShared; + memory->indexType = pointerType; + memory->initial = totalInitialPages; + memory->max = totalMaxPages; + wasm->addMemory(std::move(memory)); + } +}; + +Pass* createMultiMemoryLoweringPass() { return new MultiMemoryLowering(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index ae9f34584..c6d94ddf7 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -272,6 +272,9 @@ void PassRegistry::registerPasses() { registerPass("mod-asyncify-never-unwind", "apply the assumption that asyncify never unwinds", createModAsyncifyNeverUnwindPass); + registerPass("multi-memory-lowering", + "combines multiple memories into a single memory", + createMultiMemoryLoweringPass); registerPass("nm", "name list", createNameListPass); registerPass("name-types", "(re)name all heap types", createNameTypesPass); registerPass("once-reduction", diff --git a/src/passes/passes.h b/src/passes/passes.h index 99ee87553..20dfe0249 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -84,6 +84,7 @@ Pass* createMinifyImportsPass(); Pass* createMinifyImportsAndExportsPass(); Pass* createMinifyImportsAndExportsAndModulesPass(); Pass* createMetricsPass(); +Pass* createMultiMemoryLoweringPass(); Pass* createNameListPass(); Pass* createNameTypesPass(); Pass* createOnceReductionPass(); diff --git a/src/wasm.h b/src/wasm.h index 46b5040c2..2b890177e 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2069,6 +2069,8 @@ public: // 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); Address initial = 0; // sizes are in pages Address max = kMaxSize32; diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index b8679a62d..7ee35ab1a 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -273,6 +273,9 @@ ;; CHECK-NEXT: --mod-asyncify-never-unwind apply the assumption that ;; CHECK-NEXT: asyncify never unwinds ;; CHECK-NEXT: +;; CHECK-NEXT: --multi-memory-lowering combines multiple memories into +;; CHECK-NEXT: a single memory +;; CHECK-NEXT: ;; CHECK-NEXT: --name-types (re)name all heap types ;; CHECK-NEXT: ;; CHECK-NEXT: --nm name list diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 4df281803..0d61348b8 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -232,6 +232,9 @@ ;; CHECK-NEXT: --mod-asyncify-never-unwind apply the assumption that ;; CHECK-NEXT: asyncify never unwinds ;; CHECK-NEXT: +;; CHECK-NEXT: --multi-memory-lowering combines multiple memories into +;; CHECK-NEXT: a single memory +;; CHECK-NEXT: ;; CHECK-NEXT: --name-types (re)name all heap types ;; CHECK-NEXT: ;; CHECK-NEXT: --nm name list diff --git a/test/lit/passes/multi-memory-lowering.wast b/test/lit/passes/multi-memory-lowering.wast new file mode 100644 index 000000000..6bf1ecc7c --- /dev/null +++ b/test/lit/passes/multi-memory-lowering.wast @@ -0,0 +1,247 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s --enable-multi-memories --multi-memory-lowering --enable-bulk-memory --enable-extended-const -S -o - | filecheck %s + +(module + (memory $memory1 1) + (memory $memory2 2) + (memory $memory3 3) + (data (memory $memory1) (i32.const 0) "a") + (data (memory $memory3) (i32.const 1) "123") + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (global $memory2_byte_offset (mut i32) (i32.const 65536)) + + ;; CHECK: (global $memory3_byte_offset (mut i32) (i32.const 196608)) + + ;; CHECK: (memory $combined_memory 6) + + ;; CHECK: (data (i32.const 0) "a") + + ;; CHECK: (data (i32.add + ;; CHECK-NEXT: (global.get $memory3_byte_offset) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) "123") + + ;; CHECK: (func $loads + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $memory2_byte_offset) + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $memory3_byte_offset) + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loads + (drop + (i32.load $memory1 + (i32.const 10) + ) + ) + (drop + (i32.load $memory2 + (i32.const 11) + ) + ) + (drop + (i32.load $memory3 + (i32.const 12) + ) + ) + ) + ;; CHECK: (func $stores + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 115) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $memory2_byte_offset) + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 115) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $memory3_byte_offset) + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 115) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stores + (i32.store $memory1 + (i32.const 10) + (i32.const 115) + ) + (i32.store $memory2 + (i32.const 11) + (i32.const 115) + ) + (i32.store $memory3 + (i32.const 12) + (i32.const 115) + ) + ) +) + +;; CHECK: (func $memory1_size (result i32) +;; CHECK-NEXT: (return +;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (global.get $memory2_byte_offset) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $memory2_size (result i32) +;; CHECK-NEXT: (return +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (global.get $memory3_byte_offset) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (global.get $memory2_byte_offset) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $memory3_size (result i32) +;; CHECK-NEXT: (return +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: (i32.div_u +;; CHECK-NEXT: (global.get $memory3_byte_offset) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $memory1_grow (param $page_delta i32) (result i32) +;; CHECK-NEXT: (local $return_size i32) +;; CHECK-NEXT: (local $memory_size i32) +;; CHECK-NEXT: (local.set $return_size +;; CHECK-NEXT: (call $memory1_size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $memory_size +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (memory.grow +;; CHECK-NEXT: (local.get $page_delta) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (memory.copy +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (global.get $memory2_byte_offset) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (local.get $page_delta) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.get $memory2_byte_offset) +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (local.get $memory_size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.get $memory2_byte_offset) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $memory2_byte_offset +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (global.get $memory2_byte_offset) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (local.get $page_delta) +;; CHECK-NEXT: (i32.const 65536) +;; 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: (local.get $page_delta) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $return_size) +;; CHECK-NEXT: ) + +;; CHECK: (func $memory2_grow (param $page_delta i32) (result i32) +;; CHECK-NEXT: (local $return_size i32) +;; CHECK-NEXT: (local $memory_size i32) +;; CHECK-NEXT: (local.set $return_size +;; CHECK-NEXT: (call $memory2_size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $memory_size +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (memory.grow +;; CHECK-NEXT: (local.get $page_delta) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (memory.copy +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (global.get $memory3_byte_offset) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (local.get $page_delta) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.get $memory3_byte_offset) +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (local.get $memory_size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.get $memory3_byte_offset) +;; 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: (local.get $page_delta) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $return_size) +;; CHECK-NEXT: ) + +;; CHECK: (func $memory3_grow (param $page_delta i32) (result i32) +;; CHECK-NEXT: (local $return_size i32) +;; CHECK-NEXT: (local.set $return_size +;; CHECK-NEXT: (call $memory3_size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (memory.grow +;; CHECK-NEXT: (local.get $page_delta) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $return_size) +;; CHECK-NEXT: ) |