summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/MemoryCopyFillLowering.cpp260
-rw-r--r--src/passes/pass.cpp4
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/exec/memory-copy.wat267
-rw-r--r--test/lit/exec/memory-fill.wat184
-rw-r--r--test/lit/help/wasm-metadce.test4
-rw-r--r--test/lit/help/wasm-opt.test4
-rw-r--r--test/lit/help/wasm2js.test4
-rw-r--r--test/lit/passes/memory-copy-fill-lowering.wast168
10 files changed, 897 insertions, 0 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index a219438b4..6053c5522 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -63,6 +63,7 @@ set(passes_SOURCES
LogExecution.cpp
LoopInvariantCodeMotion.cpp
Memory64Lowering.cpp
+ MemoryCopyFillLowering.cpp
MemoryPacking.cpp
MergeBlocks.cpp
MergeSimilarFunctions.cpp
diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp
new file mode 100644
index 000000000..5855e5450
--- /dev/null
+++ b/src/passes/MemoryCopyFillLowering.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 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.
+ */
+
+#include "ir/names.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+// Replace memory.copy and memory.fill with a call to a function that
+// implements the same semantics. This is intended to be used with LLVM output,
+// so anything considered undefined behavior in LLVM is ignored. (In
+// particular, pointer overflow is UB and not handled here).
+
+namespace wasm {
+struct MemoryCopyFillLowering
+ : public WalkerPass<PostWalker<MemoryCopyFillLowering>> {
+ bool needsMemoryCopy = false;
+ bool needsMemoryFill = false;
+ Name memCopyFuncName;
+ Name memFillFuncName;
+
+ void visitMemoryCopy(MemoryCopy* curr) {
+ assert(curr->destMemory ==
+ curr->sourceMemory); // multi-memory not supported.
+ Builder builder(*getModule());
+ replaceCurrent(builder.makeCall(
+ "__memory_copy", {curr->dest, curr->source, curr->size}, Type::none));
+ needsMemoryCopy = true;
+ }
+
+ void visitMemoryFill(MemoryFill* curr) {
+ Builder builder(*getModule());
+ replaceCurrent(builder.makeCall(
+ "__memory_fill", {curr->dest, curr->value, curr->size}, Type::none));
+ needsMemoryFill = true;
+ }
+
+ void run(Module* module) override {
+ if (!module->features.hasBulkMemory()) {
+ return;
+ }
+ if (module->features.hasMemory64() || module->features.hasMultiMemory()) {
+ Fatal()
+ << "Memory64 and multi-memory not supported by memory.copy lowering";
+ }
+
+ // Check for the presence of any passive data or table segments.
+ for (auto& segment : module->dataSegments) {
+ if (segment->isPassive) {
+ Fatal() << "memory.copy lowering should only be run on modules with "
+ "no passive segments";
+ }
+ }
+ for (auto& segment : module->elementSegments) {
+ if (!segment->table.is()) {
+ Fatal() << "memory.copy lowering should only be run on modules with"
+ " no passive segments";
+ }
+ }
+
+ // In order to introduce a call to a function, it must first exist, so
+ // create an empty stub.
+ Builder b(*module);
+
+ memCopyFuncName = Names::getValidFunctionName(*module, "__memory_copy");
+ memFillFuncName = Names::getValidFunctionName(*module, "__memory_fill");
+ auto memCopyFunc = b.makeFunction(
+ memCopyFuncName,
+ {{"dst", Type::i32}, {"src", Type::i32}, {"size", Type::i32}},
+ Signature({Type::i32, Type::i32, Type::i32}, {Type::none}),
+ {{"start", Type::i32},
+ {"end", Type::i32},
+ {"step", Type::i32},
+ {"i", Type::i32}});
+ memCopyFunc->body = b.makeBlock();
+ module->addFunction(memCopyFunc.release());
+ auto memFillFunc = b.makeFunction(
+ memFillFuncName,
+ {{"dst", Type::i32}, {"val", Type::i32}, {"size", Type::i32}},
+ Signature({Type::i32, Type::i32, Type::i32}, {Type::none}),
+ {});
+ memFillFunc->body = b.makeBlock();
+ module->addFunction(memFillFunc.release());
+
+ Super::run(module);
+
+ if (needsMemoryCopy) {
+ createMemoryCopyFunc(module);
+ } else {
+ module->removeFunction(memCopyFuncName);
+ }
+
+ if (needsMemoryFill) {
+ createMemoryFillFunc(module);
+ } else {
+ module->removeFunction(memFillFuncName);
+ }
+ module->features.disable(FeatureSet::BulkMemory);
+ }
+
+ 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;
+ 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))));
+ // if dst + size > memsize or src + size > memsize, then trap.
+ body->list.push_back(b.makeIf(
+ b.makeBinary(BinaryOp::OrInt32,
+ b.makeBinary(BinaryOp::GtUInt32,
+ b.makeBinary(BinaryOp::AddInt32,
+ b.makeLocalGet(dst, Type::i32),
+ b.makeLocalGet(size, Type::i32)),
+ b.makeLocalGet(end, Type::i32)),
+ b.makeBinary(BinaryOp::GtUInt32,
+ b.makeBinary(BinaryOp::AddInt32,
+ b.makeLocalGet(src, Type::i32),
+ b.makeLocalGet(size, Type::i32)),
+ b.makeLocalGet(end, Type::i32))),
+ b.makeUnreachable()));
+ // start and end are the starting and past-the-end indexes
+ // if src < dest: start = size - 1, end = -1, step = -1
+ // else: start = 0, end = size, step = 1
+ body->list.push_back(
+ b.makeIf(b.makeBinary(BinaryOp::LtUInt32,
+ b.makeLocalGet(src, Type::i32),
+ b.makeLocalGet(dst, Type::i32)),
+ b.makeBlock({
+ b.makeLocalSet(start,
+ b.makeBinary(BinaryOp::SubInt32,
+ b.makeLocalGet(size, Type::i32),
+ b.makeConst(1))),
+ b.makeLocalSet(end, b.makeConst(-1U)),
+ b.makeLocalSet(step, b.makeConst(-1U)),
+ }),
+ b.makeBlock({
+ b.makeLocalSet(start, b.makeConst(0)),
+ b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)),
+ b.makeLocalSet(step, b.makeConst(1)),
+ })));
+ // i = start
+ body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32)));
+ body->list.push_back(b.makeBlock(
+ "out",
+ b.makeLoop(
+ "copy",
+ b.makeBlock(
+ {// break if i == end
+ b.makeBreak("out",
+ nullptr,
+ b.makeBinary(BinaryOp::EqInt32,
+ b.makeLocalGet(i, Type::i32),
+ b.makeLocalGet(end, Type::i32))),
+ // dst[i] = src[i]
+ b.makeStore(1,
+ 0,
+ 1,
+ b.makeBinary(BinaryOp::AddInt32,
+ b.makeLocalGet(dst, Type::i32),
+ b.makeLocalGet(i, Type::i32)),
+ b.makeLoad(1,
+ false,
+ 0,
+ 1,
+ b.makeBinary(BinaryOp::AddInt32,
+ b.makeLocalGet(src, Type::i32),
+ b.makeLocalGet(i, Type::i32)),
+ Type::i32,
+ memory),
+ Type::i32,
+ memory),
+ // i += step
+ b.makeLocalSet(i,
+ b.makeBinary(BinaryOp::AddInt32,
+ b.makeLocalGet(i, Type::i32),
+ b.makeLocalGet(step, Type::i32))),
+ // loop
+ b.makeBreak("copy", nullptr)}))));
+ module->getFunction(memCopyFuncName)->body = body;
+ }
+
+ void createMemoryFillFunc(Module* module) {
+ Builder b(*module);
+ Index dst = 0, val = 1, size = 2;
+ Name memory = module->memories.front()->name;
+ 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.makeBlock(
+ "out",
+ b.makeLoop(
+ "copy",
+ b.makeBlock(
+ {// break if size == 0
+ b.makeBreak(
+ "out",
+ nullptr,
+ b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))),
+ // size--
+ b.makeLocalSet(size,
+ b.makeBinary(BinaryOp::SubInt32,
+ b.makeLocalGet(size, Type::i32),
+ b.makeConst(1))),
+ // *(dst+size) = val
+ b.makeStore(1,
+ 0,
+ 1,
+ b.makeBinary(BinaryOp::AddInt32,
+ b.makeLocalGet(dst, Type::i32),
+ b.makeLocalGet(size, Type::i32)),
+ b.makeLocalGet(val, Type::i32),
+ Type::i32,
+ memory),
+ b.makeBreak("copy", nullptr)}))));
+ module->getFunction(memFillFuncName)->body = body;
+ }
+
+ void VisitTableCopy(TableCopy* curr) {
+ Fatal() << "table.copy instruction found. Memory copy lowering is not "
+ "designed to work on modules with bulk table operations";
+ }
+ void VisitTableFill(TableCopy* curr) {
+ Fatal() << "table.fill instruction found. Memory copy lowering is not "
+ "designed to work on modules with bulk table operations";
+ }
+};
+
+Pass* createMemoryCopyFillLoweringPass() {
+ return new MemoryCopyFillLowering();
+}
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index d55e22111..fcefb89ad 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -272,6 +272,10 @@ void PassRegistry::registerPasses() {
registerPass("table64-lowering",
"lower 64-bit tables 32-bit ones",
createTable64LoweringPass);
+ registerPass("memory-copy-fill-lowering",
+ "Lower memory.copy and memory.fill to wasm mvp and disable "
+ "the bulk-memory feature.",
+ createMemoryCopyFillLoweringPass);
registerPass("memory-packing",
"packs memory into separate segments, skipping zeros",
createMemoryPackingPass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index c100cd2f9..121dbcd9d 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -116,6 +116,7 @@ Pass* createOptimizeForJSPass();
Pass* createOutliningPass();
#endif
Pass* createPickLoadSignsPass();
+Pass* createMemoryCopyFillLoweringPass();
Pass* createModAsyncifyAlwaysOnlyUnwindPass();
Pass* createModAsyncifyNeverUnwindPass();
Pass* createPoppifyPass();
diff --git a/test/lit/exec/memory-copy.wat b/test/lit/exec/memory-copy.wat
new file mode 100644
index 000000000..793f94656
--- /dev/null
+++ b/test/lit/exec/memory-copy.wat
@@ -0,0 +1,267 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+;; Tests derived from bulk-memory.wast spec tests
+
+;; memory.copy
+(module
+ (import "fuzzing-support" "log-i32" (func $log-i32 (param i32)))
+ (memory 1 1)
+ (data (i32.const 0) "\aa\bb\cc\dd")
+
+ (func $assert_load (param i32 i32)
+ (if (i32.ne (local.get 1) (i32.load8_u (local.get 0)))
+ (then (unreachable)))
+ )
+
+ (func $print_memory
+ (local $i i32)
+ (local.set 0 (i32.const 9))
+ (loop $loop
+ (call $log-i32 (local.get 0))
+ (call $log-i32 (i32.load8_u (local.get 0)))
+ (local.set 0 (i32.add (local.get 0) (i32.const 1)))
+ (br_if $loop (i32.ne (local.get 0) (i32.const 17)))
+ )
+ )
+
+ ;; non-overlapping copy
+ ;; CHECK: [fuzz-exec] calling test1
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ (func $test1 (export "test1")
+ (call $print_memory)
+ (memory.copy (i32.const 10) (i32.const 0) (i32.const 4))
+ (call $print_memory)
+ (call $assert_load (i32.const 9) (i32.const 0))
+ (call $assert_load (i32.const 10) (i32.const 0xaa))
+ (call $assert_load (i32.const 11) (i32.const 0xbb))
+ (call $assert_load (i32.const 12) (i32.const 0xcc))
+ (call $assert_load (i32.const 13) (i32.const 0xdd))
+ (call $assert_load (i32.const 14) (i32.const 0))
+ )
+ ;; Overlap, src > dst
+ ;; CHECK: [fuzz-exec] calling test2
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ (func $test2 (export "test2")
+ (memory.copy (i32.const 8) (i32.const 10) (i32.const 4))
+ (call $print_memory)
+ (call $assert_load (i32.const 8) (i32.const 0xaa))
+ (call $assert_load (i32.const 9) (i32.const 0xbb))
+ (call $assert_load (i32.const 10) (i32.const 0xcc))
+ (call $assert_load (i32.const 11) (i32.const 0xdd))
+ (call $assert_load (i32.const 12) (i32.const 0xcc))
+ (call $assert_load (i32.const 13) (i32.const 0xdd))
+ )
+ ;; Overlap, src < dst
+ ;; CHECK: [fuzz-exec] calling test3
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ (func $test3 (export "test3")
+ (memory.copy (i32.const 10) (i32.const 7) (i32.const 6))
+ (call $print_memory)
+ (call $assert_load (i32.const 10) (i32.const 0x0))
+ (call $assert_load (i32.const 11) (i32.const 0xaa))
+ (call $assert_load (i32.const 12) (i32.const 0xbb))
+ (call $assert_load (i32.const 13) (i32.const 0xcc))
+ (call $assert_load (i32.const 14) (i32.const 0xdd))
+ (call $assert_load (i32.const 15) (i32.const 0xcc))
+ (call $assert_load (i32.const 16) (i32.const 0))
+ )
+ ;; Overlap src < dst but size is OOB (but the address does not overflow)
+ ;; CHECK: [fuzz-exec] calling test4a
+ ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy]
+ (func $test4a (export "test4a")
+ (memory.copy (i32.const 13) (i32.const 11) (i32.const 0x10000))
+ (call $print_memory) ;; not reached.
+ )
+ ;; CHECK: [fuzz-exec] calling test4b
+ (func $test4b (export "test4b")
+ (call $assert_load (i32.const 10) (i32.const 0x0))
+ (call $assert_load (i32.const 11) (i32.const 0xaa))
+ (call $assert_load (i32.const 12) (i32.const 0xbb))
+ (call $assert_load (i32.const 13) (i32.const 0xcc))
+ (call $assert_load (i32.const 14) (i32.const 0xdd))
+ (call $assert_load (i32.const 15) (i32.const 0xcc))
+ (call $assert_load (i32.const 16) (i32.const 0))
+ )
+ ;; Copy ending at memory limit is ok
+ ;; CHECK: [fuzz-exec] calling test5
+ (func $test5 (export "test5")
+ (memory.copy (i32.const 0xff00) (i32.const 0) (i32.const 0x100))
+ (memory.copy (i32.const 0xfe00) (i32.const 0xff00) (i32.const 0x100))
+ )
+ ;; Succeed when copying 0 bytes at the end of the region
+ ;; CHECK: [fuzz-exec] calling test6
+ (func $test6 (export "test6")
+ (memory.copy (i32.const 0x10000) (i32.const 0) (i32.const 0))
+ (memory.copy (i32.const 0x0) (i32.const 0x10000) (i32.const 0))
+ )
+ ;; copying 0 bytes outside of the limit is not allowed.
+ ;; CHECK: [fuzz-exec] calling test7
+ ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy]
+ (func $test7 (export "test7")
+ (memory.copy (i32.const 0x10001) (i32.const 0) (i32.const 0x0))
+ )
+ ;; CHECK: [fuzz-exec] calling test8
+ ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy]
+ (func $test8 (export "test8")
+ (memory.copy (i32.const 0x0) (i32.const 0x10001) (i32.const 0))
+ )
+)
+
+
+
+;; CHECK: [fuzz-exec] calling test1
+;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+
+;; CHECK: [fuzz-exec] calling test2
+;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+
+;; CHECK: [fuzz-exec] calling test3
+;; CHECK-NEXT: [LoggingExternalInterface logging 9]
+;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+;; CHECK-NEXT: [LoggingExternalInterface logging 10]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 11]
+;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+;; CHECK-NEXT: [LoggingExternalInterface logging 12]
+;; CHECK-NEXT: [LoggingExternalInterface logging 187]
+;; CHECK-NEXT: [LoggingExternalInterface logging 13]
+;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+;; CHECK-NEXT: [LoggingExternalInterface logging 14]
+;; CHECK-NEXT: [LoggingExternalInterface logging 221]
+;; CHECK-NEXT: [LoggingExternalInterface logging 15]
+;; CHECK-NEXT: [LoggingExternalInterface logging 204]
+;; CHECK-NEXT: [LoggingExternalInterface logging 16]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+
+;; CHECK: [fuzz-exec] calling test4a
+;; CHECK-NEXT: [trap unreachable]
+
+;; CHECK: [fuzz-exec] calling test4b
+
+;; CHECK: [fuzz-exec] calling test5
+
+;; CHECK: [fuzz-exec] calling test6
+
+;; CHECK: [fuzz-exec] calling test7
+;; CHECK-NEXT: [trap unreachable]
+
+;; CHECK: [fuzz-exec] calling test8
+;; CHECK-NEXT: [trap unreachable]
+;; CHECK-NEXT: [fuzz-exec] comparing test1
+;; CHECK-NEXT: [fuzz-exec] comparing test2
+;; CHECK-NEXT: [fuzz-exec] comparing test3
+;; CHECK-NEXT: [fuzz-exec] comparing test4a
+;; CHECK-NEXT: [fuzz-exec] comparing test4b
+;; CHECK-NEXT: [fuzz-exec] comparing test5
+;; CHECK-NEXT: [fuzz-exec] comparing test6
+;; CHECK-NEXT: [fuzz-exec] comparing test7
+;; CHECK-NEXT: [fuzz-exec] comparing test8
diff --git a/test/lit/exec/memory-fill.wat b/test/lit/exec/memory-fill.wat
new file mode 100644
index 000000000..dc9aead6d
--- /dev/null
+++ b/test/lit/exec/memory-fill.wat
@@ -0,0 +1,184 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited.
+
+;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s
+
+;; Tests derived from bulk-memory.wast spec tests
+
+;; memory.fill
+(module
+ (import "fuzzing-support" "log-i32" (func $log-i32 (param i32)))
+ (memory 1)
+
+
+ (func $assert_load (param i32 i32)
+ (if (i32.ne (local.get 1) (i32.load8_u (local.get 0)))
+ (then (unreachable)))
+ )
+
+ (func $print_memory (param i32 i32)
+ (loop $loop
+ (call $log-i32 (local.get 0))
+ (call $log-i32 (i32.load8_u (local.get 0)))
+ (local.set 0 (i32.add (local.get 0) (i32.const 1)))
+ (br_if $loop (i32.ne (local.get 0) (local.get 1)))
+ )
+ )
+ ;; basic fill test
+ ;; CHECK: [fuzz-exec] calling test1
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 255]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 255]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 255]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ (func $test1 (export "test1")
+ (call $print_memory (i32.const 0) (i32.const 5))
+ (memory.fill (i32.const 1) (i32.const 0xff) (i32.const 3))
+ (call $print_memory (i32.const 0) (i32.const 5))
+ (call $assert_load (i32.const 0) (i32.const 0))
+ (call $assert_load (i32.const 1) (i32.const 0xff))
+ (call $assert_load (i32.const 2) (i32.const 0xff))
+ (call $assert_load (i32.const 3) (i32.const 0xff))
+ (call $assert_load (i32.const 4) (i32.const 0x0))
+ )
+ ;; Fill value is stored as a byte
+ ;; CHECK: [fuzz-exec] calling test2
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+ (func $test2 (export "test2")
+ (memory.fill (i32.const 0) (i32.const 0xbbaa) (i32.const 2))
+ (call $print_memory (i32.const 0) (i32.const 2))
+ (call $assert_load (i32.const 0) (i32.const 0xaa))
+ (call $assert_load (i32.const 1) (i32.const 0xaa))
+ )
+ ;; Fill all of memory
+ ;; CHECK: [fuzz-exec] calling test3
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 5]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ (func $test3 (export "test3")
+ (memory.fill (i32.const 0) (i32.const 0) (i32.const 0x10000))
+ ;; let's not print all of memory; just beyond what we filled before
+ (call $print_memory (i32.const 0) (i32.const 6))
+ )
+ ;; Succeed when writing 0 bytes at the end of the region
+ ;; CHECK: [fuzz-exec] calling test4
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 5]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 6]
+ ;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+ (func $test4 (export "test4")
+ (memory.fill (i32.const 0x10000) (i32.const 0) (i32.const 0))
+ ;; let's not print all of memory; just beyond what we filled before
+ (call $print_memory (i32.const 0) (i32.const 7))
+ )
+ ;; Writing 0 bytes outside of memory is not allowed
+ ;; CHECK: [fuzz-exec] calling test5
+ ;; CHECK-NEXT: [trap out of bounds memory access in memory.fill]
+ (func $test5 (export "test5")
+ (memory.fill (i32.const 0x10001) (i32.const 0) (i32.const 0))
+ ;; should not be reached
+ (call $print_memory (i32.const 0) (i32.const 6))
+ )
+ ;; again we do not test negative/overflowing addresses as the spec test does.
+)
+;; CHECK: [fuzz-exec] calling test1
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+;; CHECK-NEXT: [LoggingExternalInterface logging 255]
+;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+;; CHECK-NEXT: [LoggingExternalInterface logging 255]
+;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+;; CHECK-NEXT: [LoggingExternalInterface logging 255]
+;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+
+;; CHECK: [fuzz-exec] calling test2
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+;; CHECK-NEXT: [LoggingExternalInterface logging 170]
+
+;; CHECK: [fuzz-exec] calling test3
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 5]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+
+;; CHECK: [fuzz-exec] calling test4
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 1]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 2]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 3]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 4]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 5]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+;; CHECK-NEXT: [LoggingExternalInterface logging 6]
+;; CHECK-NEXT: [LoggingExternalInterface logging 0]
+
+;; CHECK: [fuzz-exec] calling test5
+;; CHECK-NEXT: [trap unreachable]
+;; CHECK-NEXT: [fuzz-exec] comparing test1
+;; CHECK-NEXT: [fuzz-exec] comparing test2
+;; CHECK-NEXT: [fuzz-exec] comparing test3
+;; CHECK-NEXT: [fuzz-exec] comparing test4
+;; CHECK-NEXT: [fuzz-exec] comparing test5
diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test
index 50295b1f0..908b381e8 100644
--- a/test/lit/help/wasm-metadce.test
+++ b/test/lit/help/wasm-metadce.test
@@ -239,6 +239,10 @@
;; CHECK-NEXT: --log-execution instrument the build with
;; CHECK-NEXT: logging of where execution goes
;; CHECK-NEXT:
+;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and
+;; CHECK-NEXT: memory.fill to wasm mvp and
+;; CHECK-NEXT: disable the bulk-memory feature.
+;; CHECK-NEXT:
;; CHECK-NEXT: --memory-packing packs memory into separate
;; CHECK-NEXT: segments, skipping zeros
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 0691d1c18..7d173b084 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -248,6 +248,10 @@
;; CHECK-NEXT: --log-execution instrument the build with
;; CHECK-NEXT: logging of where execution goes
;; CHECK-NEXT:
+;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and
+;; CHECK-NEXT: memory.fill to wasm mvp and
+;; CHECK-NEXT: disable the bulk-memory feature.
+;; CHECK-NEXT:
;; CHECK-NEXT: --memory-packing packs memory into separate
;; CHECK-NEXT: segments, skipping zeros
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 89dcaa028..514f638d9 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -202,6 +202,10 @@
;; CHECK-NEXT: --log-execution instrument the build with
;; CHECK-NEXT: logging of where execution goes
;; CHECK-NEXT:
+;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and
+;; CHECK-NEXT: memory.fill to wasm mvp and
+;; CHECK-NEXT: disable the bulk-memory feature.
+;; CHECK-NEXT:
;; CHECK-NEXT: --memory-packing packs memory into separate
;; CHECK-NEXT: segments, skipping zeros
;; CHECK-NEXT:
diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast
new file mode 100644
index 000000000..540237ff7
--- /dev/null
+++ b/test/lit/passes/memory-copy-fill-lowering.wast
@@ -0,0 +1,168 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: wasm-opt --enable-bulk-memory %s --memory-copy-fill-lowering -S -o - | filecheck %s
+
+(module
+ (memory 0)
+ ;; CHECK: (type $0 (func (param i32 i32 i32)))
+
+ ;; CHECK: (memory $0 0)
+
+ ;; CHECK: (func $memcpy (param $dst i32) (param $src i32) (param $size i32)
+ ;; CHECK-NEXT: (call $__memory_copy
+ ;; CHECK-NEXT: (local.get $dst)
+ ;; CHECK-NEXT: (local.get $src)
+ ;; CHECK-NEXT: (local.get $size)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $memcpy (param $dst i32) (param $src i32) (param $size i32)
+ (memory.copy (local.get $dst) (local.get $src) (local.get $size))
+ )
+ ;; CHECK: (func $memfill (param $dst i32) (param $val i32) (param $size i32)
+ ;; CHECK-NEXT: (call $__memory_fill
+ ;; CHECK-NEXT: (local.get $dst)
+ ;; CHECK-NEXT: (local.get $val)
+ ;; CHECK-NEXT: (local.get $size)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $memfill (param $dst i32) (param $val i32) (param $size i32)
+ (memory.fill (local.get $dst) (local.get $val) (local.get $size))
+ )
+)
+;; CHECK: (func $__memory_copy (param $dst i32) (param $src i32) (param $size i32)
+;; CHECK-NEXT: (local $start i32)
+;; CHECK-NEXT: (local $end i32)
+;; CHECK-NEXT: (local $step i32)
+;; CHECK-NEXT: (local $i i32)
+;; CHECK-NEXT: (local.set $end
+;; CHECK-NEXT: (i32.mul
+;; CHECK-NEXT: (memory.size)
+;; CHECK-NEXT: (i32.const 65536)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (if
+;; CHECK-NEXT: (i32.or
+;; CHECK-NEXT: (i32.gt_u
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $dst)
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.get $end)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (i32.gt_u
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $src)
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.get $end)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (then
+;; CHECK-NEXT: (unreachable)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (if
+;; CHECK-NEXT: (i32.lt_u
+;; CHECK-NEXT: (local.get $src)
+;; CHECK-NEXT: (local.get $dst)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (then
+;; CHECK-NEXT: (local.set $start
+;; CHECK-NEXT: (i32.sub
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: (i32.const 1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $end
+;; CHECK-NEXT: (i32.const -1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $step
+;; CHECK-NEXT: (i32.const -1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (else
+;; CHECK-NEXT: (local.set $start
+;; CHECK-NEXT: (i32.const 0)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $end
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $step
+;; CHECK-NEXT: (i32.const 1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $i
+;; CHECK-NEXT: (local.get $start)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (block $out
+;; CHECK-NEXT: (loop $copy
+;; CHECK-NEXT: (br_if $out
+;; CHECK-NEXT: (i32.eq
+;; CHECK-NEXT: (local.get $i)
+;; CHECK-NEXT: (local.get $end)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (i32.store8
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $dst)
+;; CHECK-NEXT: (local.get $i)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (i32.load8_u
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $src)
+;; CHECK-NEXT: (local.get $i)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $i
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $i)
+;; CHECK-NEXT: (local.get $step)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (br $copy)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+
+;; CHECK: (func $__memory_fill (param $dst i32) (param $val i32) (param $size i32)
+;; CHECK-NEXT: (if
+;; CHECK-NEXT: (i32.gt_u
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $dst)
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (i32.mul
+;; CHECK-NEXT: (memory.size)
+;; CHECK-NEXT: (i32.const 65536)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (then
+;; CHECK-NEXT: (unreachable)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (block $out
+;; CHECK-NEXT: (loop $copy
+;; CHECK-NEXT: (br_if $out
+;; CHECK-NEXT: (i32.eqz
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.set $size
+;; CHECK-NEXT: (i32.sub
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: (i32.const 1)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (i32.store8
+;; CHECK-NEXT: (i32.add
+;; CHECK-NEXT: (local.get $dst)
+;; CHECK-NEXT: (local.get $size)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (local.get $val)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: (br $copy)
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )
+;; CHECK-NEXT: )