https://github.com/sbc100 updated https://github.com/llvm/llvm-project/pull/220111
>From 34e5d02dc5c0fa38e6920b9d5fd561c3be85899b Mon Sep 17 00:00:00 2001 From: Sam Clegg <[email protected]> Date: Mon, 31 Aug 2026 15:20:37 -0700 Subject: [PATCH] [WebAssembly] Add intrinsics and builtins for memory.copy and memory.fill Add `@llvm.wasm.memory.copy` and `@llvm.wasm.memory.fill` intrinsics, along with corresponding `__builtin_wasm_memory_copy` and `__builtin_wasm_memory_fill` Clang builtins. These map directly to the WebAssembly `memory.copy` and `memory.fill` instructions, allowing low-level runtime and libc code (such as Emscripten) to emit bulk memory operations directly in C/C++ without needing out-of-line assembly or going through `llvm.memcpy` / `llvm.memset` (which lower with zero-length branching checks to conform to C semantics). Fixes: #220086 --- clang/docs/LanguageExtensions.md | 38 +++++++++++++++++ clang/docs/ReleaseNotes.md | 3 ++ .../clang/Basic/BuiltinsWebAssembly.def | 4 ++ .../CodeGen/TargetBuiltins/WebAssembly.cpp | 19 +++++++++ clang/test/CodeGen/builtins-wasm.c | 12 ++++++ llvm/docs/ReleaseNotes.md | 2 + llvm/include/llvm/IR/IntrinsicsWebAssembly.td | 20 +++++++++ .../WebAssembly/WebAssemblyInstrBulkMemory.td | 5 ++- .../WebAssembly/bulk-memory-intrinsics.ll | 42 +++++++++++++++++++ .../WebAssembly/bulk-memory-intrinsics64.ll | 42 +++++++++++++++++++ 10 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics.ll create mode 100644 llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics64.ll diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md index 6289a2ddc6c56..e596c9bebb37c 100644 --- a/clang/docs/LanguageExtensions.md +++ b/clang/docs/LanguageExtensions.md @@ -3040,6 +3040,44 @@ void copy(int dst, int src, int nelem) { } ``` +### `__builtin_wasm_memory_copy` + +This builtin function copies bytes from a source memory to a possibly +overlapping destination region using the WebAssembly `memory.copy` instruction. +It takes five arguments: +1. Destination memory index (must be a constant integer) +2. Source memory index (must be a constant integer) +3. Destination pointer (`void *`) +4. Source pointer (`const void *`) +5. Number of bytes to copy (`size_t`) + +It returns nothing. Note that unlike C `memcpy` or `memmove`, `memory.copy` +traps if either pointer is out of bounds even when the number of bytes is zero. + +```c++ +void copy(void *dst, const void *src, size_t n) { + __builtin_wasm_memory_copy(0, 0, dst, src, n); +} +``` + +### `__builtin_wasm_memory_fill` + +This builtin function sets bytes in memory using the WebAssembly `memory.fill` +instruction. It takes four arguments: +1. Memory index (must be a constant integer) +2. Destination pointer (`void *`) +3. Byte value to set (passed as `int`, lowest 8 bits used) +4. Number of bytes to set (`size_t`) + +It returns nothing. Note that unlike C `memset`, `memory.fill` traps if the +pointer is out of bounds even when the number of bytes is zero. + +```c++ +void fill(void *dst, int val, size_t n) { + __builtin_wasm_memory_fill(0, dst, val, n); +} +``` + ## Builtin Functions Clang supports a number of builtin library functions with the same syntax as diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index a616bd41f3560..f6c9cf32c9cc0 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -687,6 +687,9 @@ features cannot lower the translation-unit ABI level; #### WebAssembly Support +- Added `__builtin_wasm_memory_copy` and `__builtin_wasm_memory_fill` builtins + for the WebAssembly `memory.copy` and `memory.fill` bulk memory instructions. + #### AVR Support #### SystemZ Support diff --git a/clang/include/clang/Basic/BuiltinsWebAssembly.def b/clang/include/clang/Basic/BuiltinsWebAssembly.def index 6de620cb22f28..a221f771820eb 100644 --- a/clang/include/clang/Basic/BuiltinsWebAssembly.def +++ b/clang/include/clang/Basic/BuiltinsWebAssembly.def @@ -25,6 +25,10 @@ BUILTIN(__builtin_wasm_memory_size, "zIi", "n") BUILTIN(__builtin_wasm_memory_grow, "zIiz", "n") +// Bulk memory builtins +TARGET_BUILTIN(__builtin_wasm_memory_copy, "vIiIiv*vC*z", "n", "bulk-memory") +TARGET_BUILTIN(__builtin_wasm_memory_fill, "vIiv*iz", "n", "bulk-memory") + // Thread-local storage TARGET_BUILTIN(__builtin_wasm_tls_size, "z", "nc", "bulk-memory") TARGET_BUILTIN(__builtin_wasm_tls_align, "z", "nc", "bulk-memory") diff --git a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp index e7bdb91d49ce8..9ecfc10430266 100644 --- a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp +++ b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp @@ -39,6 +39,25 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID, CGM.getIntrinsic(Intrinsic::wasm_memory_grow, ResultType); return Builder.CreateCall(Callee, Args); } + case WebAssembly::BI__builtin_wasm_memory_copy: { + Value *DstMem = EmitScalarExpr(E->getArg(0)); + Value *SrcMem = EmitScalarExpr(E->getArg(1)); + Value *Dst = EmitScalarExpr(E->getArg(2)); + Value *Src = EmitScalarExpr(E->getArg(3)); + Value *Size = EmitScalarExpr(E->getArg(4)); + Function *Callee = + CGM.getIntrinsic(Intrinsic::wasm_memory_copy, Size->getType()); + return Builder.CreateCall(Callee, {DstMem, SrcMem, Dst, Src, Size}); + } + case WebAssembly::BI__builtin_wasm_memory_fill: { + Value *Mem = EmitScalarExpr(E->getArg(0)); + Value *Dst = EmitScalarExpr(E->getArg(1)); + Value *Val = EmitScalarExpr(E->getArg(2)); + Value *Size = EmitScalarExpr(E->getArg(3)); + Function *Callee = + CGM.getIntrinsic(Intrinsic::wasm_memory_fill, Size->getType()); + return Builder.CreateCall(Callee, {Mem, Dst, Val, Size}); + } case WebAssembly::BI__builtin_wasm_tls_size: { llvm::Type *ResultType = ConvertType(E->getType()); Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_tls_size, ResultType); diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c index 40788b0afeb45..776e73c580d3e 100644 --- a/clang/test/CodeGen/builtins-wasm.c +++ b/clang/test/CodeGen/builtins-wasm.c @@ -27,6 +27,18 @@ __SIZE_TYPE__ memory_grow(__SIZE_TYPE__ delta) { // WEBASSEMBLY64: call i64 @llvm.wasm.memory.grow.i64(i32 0, i64 %{{.*}}) } +void memory_copy(void *dest, const void *src, __SIZE_TYPE__ count) { + __builtin_wasm_memory_copy(0, 0, dest, src, count); + // WEBASSEMBLY32: call void @llvm.wasm.memory.copy.i32(i32 0, i32 0, ptr %{{.*}}, ptr %{{.*}}, i32 %{{.*}}) + // WEBASSEMBLY64: call void @llvm.wasm.memory.copy.i64(i32 0, i32 0, ptr %{{.*}}, ptr %{{.*}}, i64 %{{.*}}) +} + +void memory_fill(void *dest, int value, __SIZE_TYPE__ count) { + __builtin_wasm_memory_fill(0, dest, value, count); + // WEBASSEMBLY32: call void @llvm.wasm.memory.fill.i32(i32 0, ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}}) + // WEBASSEMBLY64: call void @llvm.wasm.memory.fill.i64(i32 0, ptr %{{.*}}, i32 %{{.*}}, i64 %{{.*}}) +} + __SIZE_TYPE__ tls_size(void) { return __builtin_wasm_tls_size(); // WEBASSEMBLY32: call i32 @llvm.wasm.tls.size.i32() diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md index ef90a1f1f1c41..5c288a0ee6fa7 100644 --- a/llvm/docs/ReleaseNotes.md +++ b/llvm/docs/ReleaseNotes.md @@ -222,6 +222,8 @@ Makes programs 10x faster by doing Special New Thing. * Added support for emitting common symbols (.comm) using the WASM_SYMBOL_BINDING_COMMON flag (see https://github.com/WebAssembly/tool-conventions/pull/267) +* Added `@llvm.wasm.memory.copy` and `@llvm.wasm.memory.fill` intrinsics for + the WebAssembly `memory.copy` and `memory.fill` instructions. ### Changes to the Windows Target diff --git a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td index a0e83cee9f055..fbd4b35514ebf 100644 --- a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td +++ b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td @@ -24,6 +24,26 @@ def int_wasm_memory_size : def int_wasm_memory_grow : DefaultAttrsIntrinsic<[llvm_anyint_ty], [llvm_i32_ty, LLVMMatchType<0>], []>; +//===----------------------------------------------------------------------===// +// Bulk memory intrinsics +//===----------------------------------------------------------------------===// + +def int_wasm_memory_copy : + DefaultAttrsIntrinsic<[], + [llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty, llvm_ptr_ty, + llvm_anyint_ty], + [IntrArgMemOnly, NoCapture<ArgIndex<2>>, + NoCapture<ArgIndex<3>>, WriteOnly<ArgIndex<2>>, + ReadOnly<ArgIndex<3>>, ImmArg<ArgIndex<0>>, + ImmArg<ArgIndex<1>>]>; + +def int_wasm_memory_fill : + DefaultAttrsIntrinsic<[], + [llvm_i32_ty, llvm_ptr_ty, llvm_i32_ty, + llvm_anyint_ty], + [IntrWriteMem, IntrArgMemOnly, NoCapture<ArgIndex<1>>, + WriteOnly<ArgIndex<1>>, ImmArg<ArgIndex<0>>]>; + //===----------------------------------------------------------------------===// // ref.null intrinsics //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrBulkMemory.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrBulkMemory.td index ba79867b942f9..f842c2e27b6c0 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrBulkMemory.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrBulkMemory.td @@ -59,7 +59,8 @@ defm COPY_A#B : BULK_I<(outs), (ins i32imm_op:$dst_idx, i32imm_op:$src_idx, rc:$dst, rc:$src, rc:$len), (outs), (ins i32imm_op:$dst_idx, i32imm_op:$src_idx), - [], + [(int_wasm_memory_copy (i32 timm:$dst_idx), (i32 timm:$src_idx), + rc:$dst, rc:$src, rc:$len)], "memory.copy\t$dst_idx, $src_idx, $dst, $src, $len", "memory.copy\t$dst_idx, $src_idx", 0x0a>; @@ -67,7 +68,7 @@ let mayStore = 1 in defm FILL_A#B : BULK_I<(outs), (ins i32imm_op:$idx, rc:$dst, I32:$value, rc:$size), (outs), (ins i32imm_op:$idx), - [], + [(int_wasm_memory_fill (i32 timm:$idx), rc:$dst, I32:$value, rc:$size)], "memory.fill\t$idx, $dst, $value, $size", "memory.fill\t$idx", 0x0b>; } diff --git a/llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics.ll b/llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics.ll new file mode 100644 index 0000000000000..403aefc06d859 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics.ll @@ -0,0 +1,42 @@ +; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mcpu=mvp -mattr=+bulk-memory,+multimemory | FileCheck %s + +target triple = "wasm32-unknown-unknown" + +declare void @llvm.wasm.memory.copy.i32(i32, i32, ptr, ptr, i32) +declare void @llvm.wasm.memory.fill.i32(i32, ptr, i32, i32) + +; CHECK-LABEL: memory_copy: +; CHECK-NEXT: .functype memory_copy (i32, i32, i32) -> () +; CHECK-NEXT: memory.copy 0, 0, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_copy(ptr %dest, ptr %src, i32 %len) { + call void @llvm.wasm.memory.copy.i32(i32 0, i32 0, ptr %dest, ptr %src, i32 %len) + ret void +} + +; CHECK-LABEL: memory_fill: +; CHECK-NEXT: .functype memory_fill (i32, i32, i32) -> () +; CHECK-NEXT: memory.fill 0, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_fill(ptr %dest, i32 %value, i32 %len) { + call void @llvm.wasm.memory.fill.i32(i32 0, ptr %dest, i32 %value, i32 %len) + ret void +} + +; CHECK-LABEL: memory_copy_multi: +; CHECK-NEXT: .functype memory_copy_multi (i32, i32, i32) -> () +; CHECK-NEXT: memory.copy 1, 2, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_copy_multi(ptr %dest, ptr %src, i32 %len) { + call void @llvm.wasm.memory.copy.i32(i32 1, i32 2, ptr %dest, ptr %src, i32 %len) + ret void +} + +; CHECK-LABEL: memory_fill_multi: +; CHECK-NEXT: .functype memory_fill_multi (i32, i32, i32) -> () +; CHECK-NEXT: memory.fill 3, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_fill_multi(ptr %dest, i32 %value, i32 %len) { + call void @llvm.wasm.memory.fill.i32(i32 3, ptr %dest, i32 %value, i32 %len) + ret void +} diff --git a/llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics64.ll b/llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics64.ll new file mode 100644 index 0000000000000..a71c21097ddf5 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/bulk-memory-intrinsics64.ll @@ -0,0 +1,42 @@ +; RUN: llc < %s -asm-verbose=false -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mcpu=mvp -mattr=+bulk-memory,+multimemory | FileCheck %s + +target triple = "wasm64-unknown-unknown" + +declare void @llvm.wasm.memory.copy.i64(i32, i32, ptr, ptr, i64) +declare void @llvm.wasm.memory.fill.i64(i32, ptr, i32, i64) + +; CHECK-LABEL: memory_copy: +; CHECK-NEXT: .functype memory_copy (i64, i64, i64) -> () +; CHECK-NEXT: memory.copy 0, 0, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_copy(ptr %dest, ptr %src, i64 %len) { + call void @llvm.wasm.memory.copy.i64(i32 0, i32 0, ptr %dest, ptr %src, i64 %len) + ret void +} + +; CHECK-LABEL: memory_fill: +; CHECK-NEXT: .functype memory_fill (i64, i32, i64) -> () +; CHECK-NEXT: memory.fill 0, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_fill(ptr %dest, i32 %value, i64 %len) { + call void @llvm.wasm.memory.fill.i64(i32 0, ptr %dest, i32 %value, i64 %len) + ret void +} + +; CHECK-LABEL: memory_copy_multi: +; CHECK-NEXT: .functype memory_copy_multi (i64, i64, i64) -> () +; CHECK-NEXT: memory.copy 1, 2, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_copy_multi(ptr %dest, ptr %src, i64 %len) { + call void @llvm.wasm.memory.copy.i64(i32 1, i32 2, ptr %dest, ptr %src, i64 %len) + ret void +} + +; CHECK-LABEL: memory_fill_multi: +; CHECK-NEXT: .functype memory_fill_multi (i64, i32, i64) -> () +; CHECK-NEXT: memory.fill 3, $0, $1, $2 +; CHECK-NEXT: return +define void @memory_fill_multi(ptr %dest, i32 %value, i64 %len) { + call void @llvm.wasm.memory.fill.i64(i32 3, ptr %dest, i32 %value, i64 %len) + ret void +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
