Closed
Description
const spi_tdr = packed struct {
unused1: u7,
lastxfr: u1,
unused2: u4,
pcs: u4,
td: u16,
};
export fn entry(ptr: *volatile spi_tdr) void {
var tmp = ptr.*;
tmp.lastxfr = 1;
ptr.* = tmp;
}
generates
define void @entry(%spi_tdr* nonnull) #2 !dbg !80 {
Entry:
%ptr = alloca %spi_tdr*, align 8
%tmp = alloca %spi_tdr, align 1
store %spi_tdr* %0, %spi_tdr** %ptr, align 8
call void @llvm.dbg.declare(metadata %spi_tdr** %ptr, metadata !96, metadata !DIExpression()), !dbg !100
%1 = load %spi_tdr*, %spi_tdr** %ptr, align 8, !dbg !101
%2 = bitcast %spi_tdr* %1 to i8*, !dbg !102
%3 = bitcast %spi_tdr* %tmp to i8*, !dbg !102
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %3, i8* align 1 %2, i64 4, i1 false), !dbg !102
call void @llvm.dbg.declare(metadata %spi_tdr* %tmp, metadata !97, metadata !DIExpression()), !dbg !102
%4 = getelementptr inbounds %spi_tdr, %spi_tdr* %tmp, i32 0, i32 0, !dbg !103
%5 = load i8, i8* %4, align 1, !dbg !105
%6 = and i8 %5, 127, !dbg !105
%7 = or i8 -128, %6, !dbg !105
store i8 %7, i8* %4, align 1, !dbg !105
%8 = load %spi_tdr*, %spi_tdr** %ptr, align 8, !dbg !106
%9 = bitcast %spi_tdr* %tmp to i8*, !dbg !107
%10 = bitcast %spi_tdr* %8 to i8*, !dbg !107
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %10, i8* align 1 %9, i64 4, i1 true), !dbg !107
ret void, !dbg !108
}
The problem here is call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %3, i8* align 1 %2, i64 4, i1 false), !dbg !102
and specifically the last arg i1 false
. That's a non-volatile load.
This load should be volatile.
Arguably Zig should generate a ptrcast from the struct pointer to a pointer to u32 since that is <= register size, but if LLVM is doing the same thing when lowering memcpys - which it appears to be doing - then it's fine just to set the volatile bit.
We don't really have a way to test this other than pattern-matching the resulting LLVM IR, so I think we'll have to add some tests that do that. I already need something like that for #1682.