authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-07-04 20:12:48+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-07-08 17:45:05+02:00
logd54ebf4356eaeeab4d256d0da4f81678226f81a6
tree69129636312bc7c8fa02db2ad63f7cbaa74e1959
parent836f9fceab03c7de56eba7a9c2e810206e7e8469
signaturelock-open Commit is signed but in an unrecognized format.

llvm: add safety-check for Wasm memset

When lowering the `memset` instruction, LLVM will lower it to WebAssembly's `memory.fill` instruction when the bulk-memory feature is enabled. This instruction will trap when the destination address is out-of-bounds. By Zig's semantics, it is valid to have an invalid pointer when the length is 0. To prevent runtimes from trapping, we add a safety-check for slices to only lower to a memset instruction when the length is larger than 0.

1 files changed, 48 insertions(+), 3 deletions(-)

src/codegen/llvm.zig+48-3
......@@ -8511,6 +8511,14 @@ pub const FuncGen = struct {
85118511 const dest_ptr = self.sliceOrArrayPtr(dest_slice, ptr_ty);
85128512 const is_volatile = ptr_ty.isVolatilePtr(mod);
85138513
8514 // Any WebAssembly runtime will trap when the destination pointer is out-of-bounds, regardless
8515 // of the length. This means we need to emit a check where we skip the memset when the length
8516 // is 0 as we allow for undefined pointers in 0-sized slices.
8517 const needs_wasm_safety_check = safety and
8518 o.target.isWasm() and
8519 ptr_ty.isSlice(mod) and
8520 std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory);
8521
85148522 if (try self.air.value(bin_op.rhs, mod)) |elem_val| {
85158523 if (elem_val.isUndefDeep(mod)) {
85168524 // Even if safety is disabled, we still emit a memset to undefined since it conveys
......@@ -8521,7 +8529,11 @@ pub const FuncGen = struct {
85218529 else
85228530 u8_llvm_ty.getUndef();
85238531 const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8524 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8532 if (needs_wasm_safety_check) {
8533 try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8534 } else {
8535 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8536 }
85258537
85268538 if (safety and mod.comp.bin_file.options.valgrind) {
85278539 self.valgrindMarkUndef(dest_ptr, len);
......@@ -8539,7 +8551,12 @@ pub const FuncGen = struct {
85398551 .val = byte_val,
85408552 });
85418553 const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8542 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8554
8555 if (needs_wasm_safety_check) {
8556 try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8557 } else {
8558 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8559 }
85438560 return null;
85448561 }
85458562 }
......@@ -8551,7 +8568,12 @@ pub const FuncGen = struct {
85518568 // In this case we can take advantage of LLVM's intrinsic.
85528569 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
85538570 const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8554 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8571
8572 if (needs_wasm_safety_check) {
8573 try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8574 } else {
8575 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8576 }
85558577 return null;
85568578 }
85578579
......@@ -8622,6 +8644,29 @@ pub const FuncGen = struct {
86228644 return null;
86238645 }
86248646
8647 fn safeWasmMemset(
8648 self: *FuncGen,
8649 dest_ptr: *llvm.Value,
8650 fill_byte: *llvm.Value,
8651 len: *llvm.Value,
8652 dest_ptr_align: u32,
8653 is_volatile: bool,
8654 ) !void {
8655 const parent_block = self.context.createBasicBlock("Block");
8656 const llvm_usize_ty = self.context.intType(self.dg.object.target.ptrBitWidth());
8657 const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .eq);
8658 const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
8659 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
8660 _ = self.builder.buildCondBr(cond, then_block, else_block);
8661 self.builder.positionBuilderAtEnd(then_block);
8662 _ = self.builder.buildBr(parent_block);
8663 self.builder.positionBuilderAtEnd(else_block);
8664 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8665 _ = self.builder.buildBr(parent_block);
8666 self.llvm_func.appendExistingBasicBlock(parent_block);
8667 self.builder.positionBuilderAtEnd(parent_block);
8668 }
8669
86258670 fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
86268671 const o = self.dg.object;
86278672 const mod = o.module;