Hi!
I'm working on a LLVM wrapper and I think that I've found a bug in the deadCodeElim pragma.
This is an example test program to reproduce it:
test.h
#define HELLO hello
test.nim
{.deadCodeElim: off.}
var HELLO {.importc, header: "\"../test.h\"".}: proc () {.nimcall.}
proc hello {.extern: "hello".} =
echo "Hello"
HELLO()
When doing a debug build everything is fine, but when doing a release build I get:
$ nim -d:release c test [..snip..] [Linking] /tmp/ccdy7Hzb.ltrans3.ltrans.o: In function `testInit': ccdy7Hzb.ltrans3.o:(.text+0x13): undefined reference to `hello' collect2: error: ld returned 1 exit status Error: execution of an external program failed
Am I doing something wrong or this is effectively a deadCodeElim pragma bug? I'm using Nim 0.11.2
Note that -d:release implies --deadCodeElim:on, which is why you are seeing this effect.
In order to prevent a procedure that you want to call from C from being removed, use {.exportc.} instead of {.extern.}. The extern pragma only sets the external name, the exportc pragma also marks the procedure so that the compiler knows that it will be called from foreign code and can't be eliminated.
The pragma should override what's specified on the command line, at least that's what I infer from reading the documentation, and in fact, it works the other way around:
test.h
#define HELLO hello
test.nim
{.deadCodeElim: on.}
var HELLO {.importc, header: "\"../test.h\"".}: proc () {.nimcall.}
proc hello {.extern: "hello".} =
echo "Hello"
HELLO()
This is the (correct) expected result:
$ nim --deadCodeElim:off c test [..snip..] [Linking] /home/fomoto/src/nim/llvm/examples/nimcache/llvm_test.o: In function `testInit': llvm_test.c:(.text+0x1b5): undefined reference to `hello' collect2: error: ld returned 1 exit status Error: execution of an external program failed
CLI | Pragma | Result |
---|---|---|
--deadCodeElim:off | {.deadCodeElim: on.} | Pragma is honored |
--deadCodeElim:on | {.deadCodeElim: off.} | Pragma is NOT honored |
The use case for why I'd want to force it off for individual modules is the example I gave in the first post. Right now I'm using the exportc pragma as a workaround because I don't have the need to generate a library from my code, but if that would be the case, I don't want the function to be exported in the generated library; that's why I see the extern pragma as the best fit and not exportc.
{.exportc.} is not a workaround. It is the intended way for making a Nim function callable from C. The {.extern.} pragma is for name mangling and can be used in conjunction with either {.exportc.} or [.importc.}. It is generally not necessary, but can be used to override the naming chosen by {.exportc.} and {.importc.}. For example, the stdlib defines (inter alia):
when defined(createNimRtl):
{.pragma: rtl, exportc: "nimrtl_$1", dynlib, gcsafe.}
...
elif defined(useNimRtl):
...
{.pragma: rtl, importc: "nimrtl_$1", dynlib: nimrtl, gcsafe.}
...
else:
{.pragma: rtl, gcsafe.}
...
And then, at some places, overrides the choice made by {.rtl.} through {.extern.}, e.g.:
proc toLower*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.}
This is useful in order to have a general naming scheme, and then to use the extern pragma to situationally give sane names to operators. to change capitalization, or to resolve naming conflicts when identically named functions in multiple modules would export to/import from the same C identifier.
The behavior is also not inconsistent. It's the logical or of the pragma and command line option. It may not be your preferred choice, but it's perfectly consistent.
CLI | Pragma | Result |
---|---|---|
--deadCodeElim:off | {.deadCodeElim: on.} | on |
--deadCodeElim:on | {.deadCodeElim: off.} | on |
FedeOmoto1: I'm not generating a library from my code (--app:lib), the Nim proc being exported is NOT called from C
Yes, it is being called from C (through a #define hack, but same difference). If you do not need access to the procedure from C, then there is no reason why it can't be optimized away, because if it isn't called from C, only Nim can access it and the compiler can safely throw it away when not needed.