Skip to content

[WebAssembly] zero-length memcpy does not result in no-op when bulk-memory is enabled #63755

Closed
@Luukdegram

Description

@Luukdegram

According to the LLVM Language Reference:

If <len> is 0, it is no-op modulo the behavior of attributes attached to the arguments. If <len> is not a well-defined value, the behavior is undefined. If <len> is not zero, both <dest> and <src> should be well-defined, otherwise the behavior is undefined.

performing a memory copy with a zero-length results in a no-op module. This means I'd expect for the WebAssembly target with the bulk-memory feature enabled, to result in a no-op when the length is 0. However, this seems to emit a memory.copy instruction with no checks, meaning it can result in a trap when the destination address is out-of-bounds.

i.e. given the following IR:

; Function Attrs: noredzone nounwind
define dso_local void @_start() #0 !dbg !256 {
Entry:
  %0 = alloca { ptr, i32 }, align 4
  %1 = alloca { ptr, i32 }, align 4
  %2 = call fastcc { ptr, i32 } @foo.foo(), !dbg !265
  store { ptr, i32 } %2, ptr %1, align 4, !dbg !265
  call void @llvm.dbg.declare(metadata ptr %1, metadata !262, metadata !DIExpression()), !dbg !265
  %3 = call fastcc { ptr, i32 } @foo.foo(), !dbg !266
  store { ptr, i32 } %3, ptr %0, align 4, !dbg !266
  call void @llvm.dbg.declare(metadata ptr %0, metadata !264, metadata !DIExpression()), !dbg !266
  %4 = load { ptr, i32 }, ptr %0, align 4, !dbg !267
  %5 = load { ptr, i32 }, ptr %1, align 4, !dbg !267
  %6 = extractvalue { ptr, i32 } %5, 0, !dbg !267
  %7 = extractvalue { ptr, i32 } %4, 1, !dbg !267
  %8 = extractvalue { ptr, i32 } %4, 0, !dbg !267
  call void @llvm.memcpy.p0.p0.i32(ptr align 1 %8, ptr align 1 %6, i32 %7, i1 false), !dbg !267
  ret void, !dbg !267
}

; Function Attrs: noredzone nounwind
define internal fastcc { ptr, i32 } @foo.foo() unnamed_addr #0 !dbg !268 {
Entry:
  %0 = alloca i32, align 4
  store i32 -1, ptr %0, align 4, !dbg !274
  call void @llvm.dbg.declare(metadata ptr %0, metadata !272, metadata !DIExpression()), !dbg !274
  ret { ptr, i32 } { ptr inttoptr (i32 -1 to ptr), i32 0 }, !dbg !275
}

Will emit a memory.copy instruction although the length of the operand is 0. In this case, the destination address is out-of-bounds, therefore resulting in a trap. According to the WebAssembly specification any out-of-bounds memory operation will result in a trap, regardless of the fact we have a zero-length operand. This is not the behavior I'd expect according to the LLVM Language Reference.
This behavior only occurs when the bulk-memory feature is enabled as without this feature, we don't have access to the above mentioned instruction and LLVM will emit a loop instead (Which is safe for zero-length copies).

This issue also occurs for the llvm.memset intrinsic.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions