-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
What happened?
While developing a sensor library with Ratel I ran into some trouble with static objects. Essentially Ratel uses a lot of static objects in order to create a nice interface for users, while still being very stingy with program memory. In this case I had a sensor object which contains a reference to the empty static I2C object (just used for overloading and extensibility) and the address of the device (this is assumed to not change on runtime because changing the address of a sensor would require soldering on the sensor). Now this is all well and good, I've done similar things in the past by simply declaring that my procs take a static[Sensor]. This means that only the address (a uint8) would enter the code, and all the objects simply vanish. The sensor I'm working on at the moment requires some calibration data to be read once from the chip. I decided to create a new object with this calibration data and a CalibratedSensor[raw: static[RawSensor]] = object kind of mapping. This would mean that calling myCalibratedSensor.raw.read() for example would still pass along a static object and nothing would enter the runtime. Much to my surprise however the program size ballooned and on inspecting why I found references to memory collection in my symbols table. Since none of the rest of the project uses objects that needs collection this seemed weird. Investigating the cause I found that the RawSensor object was indeed created every single time it wanted to use it. So not only did I not save any data on this, but rather I spent more than I would with just a simple object with the lifetime of the program. Not great. Turns out Nim will lose static information, or not properly do constant folding, when ARC/ORC is enabled. The below sample shows my issue:
import os
type
StaticInfo = object
x: int
Info[s: static[StaticInfo]] = object
y: string
block:
const i = StaticInfo(x: 42)
proc testX(s: int): bool =
if $s == paramStr(1):
true
else:
false
if testX(i.x):
echo "Hello world"
block:
var i = Info[StaticInfo(x: 42)](y: "Hello world")
proc testX(s: static[int]): bool =
if $s == paramStr(1):
true
else:
false
if testX(i.s.x):
echo i.yThis code runs fine and there is no reference to the StaticInfo object anywhere in the generated C code. The testX_ministatic_8 function is simply called with the number 42 in the first block, and testX__ministatic_17 is called without any arguments and simply compares to the string literal "42" in the second block. With ARC enabled it does the same thing. Now by removing the static part from the second block I would expect that block to behave the same as the first block. Simply passing in the static number 42 to a testX function. But this is not the case. For the refc garbage collector we now have a static NIM_CONST tyObject_StaticInfo object in the code, which the C compiler is able to optimize out, so not a big issue. But by turning on ARC we now have a tyObject_StaticInfo which is declared, nimZeroMem-ed, assigned the number 42 to, and testX__ministatic_13 is now called with T11_.x where T11_ is the StaticInfo object. So instead of putting a neat number 42 in there, this causes a lot of extra overhead with nimZeroMem. In my code it also caused memory allocation/freeing to occur, but this doesn't happen in this tiny reproduction.
TLDR; ARC fails to do constant folding properly and ends up introducing an object which is clearly labelled as static into the code.
Nim Version
Devel (8e1181b), 1.6.8, and 1.6.10rc2