Another embedded programming issue.
Manual says: The implicit initialization can also be prevented by the requiresInit type pragma.
But the compiler generates an unnecessary zero initialization for the object, although it is marked with a {.requiresInit.}.
# init.nim
import std/volatile
type
Foo {.requiresInit.} = object
address: int
proc set(x: static[Foo]) =
volatileStore(cast[ptr uint32](x.address), 0x123)
Foo(address: 0x4002_0018).set()
nim c -d:danger --opt:speed --mm:arc --opt:speed --nimcache:. init.nim
N_LIB_PRIVATE N_NIMCALL(void, set__init_7)(void) {
tyObject_Foo__pvtzp2VDgR9cpwdnHyH5GFg T1_;
nimZeroMem((void*)(&T1_), sizeof(tyObject_Foo__pvtzp2VDgR9cpwdnHyH5GFg)); // <-- unnecessary zero init
T1_.address = ((NI) 1073872920);
*((NU32 volatile*)(((NU32*) (T1_.address)))) = ((NI) 291);
}
But if you change arc to something other then arc/orc, then it will generate perfect code.
nim c -d:danger --opt:speed --mm:arc --opt:speed --nimcache:. init.nim
static NIM_CONST tyObject_Foo__pvtzp2VDgR9cpwdnHyH5GFg TM__vr5WO0GXUWbIR9bpgoofstA_2 = {((NI) 1073872920)};
N_LIB_PRIVATE N_NIMCALL(void, set__init_7)(void) {
*((NU32 volatile*)(((NU32*) (TM__vr5WO0GXUWbIR9bpgoofstA_2.address)))) = ((NI) 291);
}
What is the problem of zero initialization for objects? In the case of your code, it seems nimZeroMem is removed by GCC.
I just changed magic numbers in your code so that I can easily find them in assembly output:
# testzero.nim
import std/volatile
type
Foo {.requiresInit.} = object
address: int
proc set(x: static[Foo]) =
volatileStore(cast[ptr uint32](x.address), 987654321)
Foo(address: 123454321).set()
Generate assembly code with:
nim c --passC:"-masm=intel -S" -d:danger --mm:arc testzero.nim
I run it on x86_64 CPU, that might be different from your target CPU.
A part of generated C code:
N_LIB_PRIVATE N_NIMCALL(void, set__testzero_7)(void) {
tyObject_Foo__ey9bHnLMu3ZG6twjhnl5mKQ T1_;
nimZeroMem((void*)(&T1_), sizeof(tyObject_Foo__ey9bHnLMu3ZG6twjhnl5mKQ));
T1_.address = ((NI) 123454321);
*((NU32 volatile*)(((NU32*) (T1_.address)))) = ((NI) 987654321);
}
A part of generated assembly code (nimcache/danger/testzero/@mtestzero.nim.c.o):
main:
.LFB21:
.cfi_startproc
sub rsp, 8
.cfi_def_cfa_offset 16
mov QWORD PTR cmdLine[rip], rsi
mov DWORD PTR cmdCount[rip], edi
mov QWORD PTR gEnv[rip], rdx
call atmdotdotatsdotdotatsdotdotatsdotdotatsusratslibatsnimatssystemdotnim_Init000
mov DWORD PTR ds:123454321, 987654321
call nimTestErrorFlag
mov eax, DWORD PTR nim_program_result[rip]
add rsp, 8
.cfi_def_cfa_offset 8
ret
So above Nim code compiled to mov DWORD PTR ds:123454321, 987654321. But I cannot say nimZeroMem is always optimized out only from this example code.
If object types contains padding, nimZeroMem might be necessary. Even if all fields of objects are initialized, bits in padding are not initialized and can contains random bits when nimZeroMem are not used. If these variables are read by cmpMem, writeBuffer, send, or some C functions that reads all bits of an object, random bits in padding might appears in the output or affect the behavior of the program. I used noinit pragma in following code so that nimZeroMem is not used for variable a and b.
type
Padding = object
x: int8
y: int64
proc test =
var
a {.noinit.}: Padding
b {.noinit.}: Padding
a.x = 0
a.y = 0
b.x = 0
b.y = 0
doAssert a == b
doAssert cmpMem(addr a, addr b, sizeof(a)) == 0
test()
When I run above code on Nim Playground, doAssert cmpMem(addr a, addr b, sizeof(a)) == 0 raises AssertionDefect. https://play.nim-lang.org/#ix=4h8J
What is the problem of zero initialization for objects? In the case of your code, it seems nimZeroMem is removed by GCC.
This is a reduced version. Optimization does not always work. I discovered this problem when I was looking through the generated assembly and noticed pointless operations.
I run it on x86_64 CPU, that might be different from your target CPU.
Yes, it's different. I compile for Cortex-M4 (-march=armv7)
If object types contains padding, nimZeroMem might be necessary. Even if all fields of objects are initialized, bits in padding are not initialized and can contains random bits when nimZeroMem are not used.
Nice catch! Thanks.
It looks like GCC optimizes zero initialization if there is no padding in the object. x86_64
with padding:
mov DWORD PTR [rsp-4], 0
mov DWORD PTR [rsp-8], 1073877016
mov eax, DWORD PTR ds:1073877016
or eax, 8
mov DWORD PTR ds:1073877016, eax
ret
without padding
mov eax, DWORD PTR ds:1073877016
or eax, 8
mov DWORD PTR ds:1073877016, eax
ARMv7 with padding
sub sp, sp, #8
mov r3, #4096
movt r3, 16386
movs r1, #0
movw r2, #4120
movt r2, 16386
strd r2, r1, [sp]
ldr r2, [r3, #24]
orr r2, r2, #8
str r2, [r3, #24]
add sp, sp, #8
bx lr
without padding
mov r3, #4096
movt r3, 16386
ldr r2, [r3, #24]
orr r2, r2, #8
str r2, [r3, #24]
bx lr
I believe this might be related to/caused by https://github.com/nim-lang/Nim/issues/20854
I agree, it looks very much like that.