I thought I understood this, but clearly I don't.
First, too much context:
=======================================================
I'm porting parts of a C project to nim.
altera-jtag.h --> tapdriver.h
struct altera_jtag{
altera_jtag_state drstop_state;
...
}
int altera_jinit(altera_jtag *js);
int altera_set_drstop(altera_jtag *js, altera_jtag_state state);
...
this has become:
type
TapState*{.exportc:"altera_jtag_state",size:4.} = enum
IllegalTapState = -1, Reset=0, Idle, ...
Bits* = object
data: seq[byte]
len:int
TapDriver*{.exportc:"altera_jtag",bycopy.} = object
drstop_state,irstop_state: TapState # just an enum
ir_pre,ir_post,dr_pre,dr_post,ir,dr: Bits
which is called by altera.c
int altera_execute(...){
struct altera_jtag _js, *js = &_js;
...
altera_jinit(js);
finally, at top level, altera_gpio.c --> jbc_player.nim
import tapdriver
import usb_blaster
#so that altera.c can link against them
{.compile: "altera.c".}
proc altera_execute(...){.importc,header:"altera.h".}
...
=============================================
#My actual question is:
how do i write that altera_jinit proc? I've tried:
proc altera_jinit*(js: var TapDriver) =
js = TapDriver(drstop_state: Idle, ...)
but that segfaults with orc.
i've tried
proc altera_jinit*(js: var TapDriver) =
var t = TapDriver(drstop_state: Idle,...)
copyMem(js.addr,t.addr,sizeof(TapDriver)
and that works, but surely it shouldn't; wont those seq's get destroyed out from under me at some point? I can add a GC_ref; what's the equivalent in orc? oh, now i'm typing, im thinking is it {.nodestroy.}?
but really, i thought, you can't just allocate a gc object on the stack naively like that, it's always going to be a problem. luckily i have access to the source code for altera.c so i'll change it:
int altera_execute(...){
struct altera_jtag _js= altera_jinit();
struct altera_jtag *js = &_js;
proc altera_jinit*():TapDriver{.exportc.} =
TapDriver(drstop_state: Idle, ...
But now all compilation flavours segfault.
I thought {.bycopy.} was the magic word to make that work, but the generated c code for altera_jinit is
N_LIB_PRIVATE N_CDECL(void, altera_jinit)(altera_jtag* Result);
so wtf, what are the rules? this project is going to be a nim/c/cpp layer cake, i would love a straightforward method that doesn't require reading the generated c every time. what do y'all do?
You are likely overthinking things. Your copyMem approach is technically correct but is equivalent to the simplest way, which is:
proc altera_jinit*(js: var TapDriver) {.exportc.} =
js.drstop_state = Idle
...
The var parameter tells Nim to modify the memory at the address of the variable from the calling code. The calling code has already initialized the struct altera_jtag on the stack.
The C code that initializes the struct
struct altera_jtag _js, *js = &_js;
// _js is a stack allocated object with enough memory to fit all fields, memory is not zeroed
is equivalent to doing the following in Nim (with the exception of Nim zeroing out the memory)
var u_js = TapDriver() # Nim will zero out fields, but still allocates an object on the stack
var js = u_js.addr
The C library expects the memory to be initialized (via the stack or the heap), so just setting the fields to what you need should be all that is required.
If you want to be more "C correct", the exported Nim proc should be:
proc altera_jinit*(js: ptr TapDriver) {.exportc.} =
js[].drstop_state = Idle
...
but the effect should be the same.
I am definitely overthinking things. And this should be a stackoverflow post instead. I'll do that later.
However.
My first try was
nim altera_jinit(js: var TapDriver) =
js = TapDriver(...)
and that segfaulted with orc somewhere in =sink. Maybe that's a bug I should reduce and report, but that's why I tried the "return value object" method.
I can say: The return-value-object method is unstable, by which I mean Nim can choose to turn it into an out-param method (or not) if the object changes size, contains GC members, or between compiler/mm versions.
I can't find any combination of pragmas that can stabilize this api, is that not what's expected from {.exportc, dynlib.} and {.bycopy.}?
Nim generally expects an object to be in an initialized state (0 typically), particularly for destructor-related hooks. Your C code does not seem to initialize js, so Nim will try to destroy random data (most probably access of a random address). A fix would be to 0-init the structure beforehand:
proc altera_jinit*(js: var TapDriver) =
zeroMem(js.addr, sizeof(js))
js = TapDriver(drstop_state: Idle, ...)
Another potential issue is a mismatched size between struct altera_jtag and TapDriver.
Why would you ever come up with
Bits* = object
data: seq[byte]
len:int
when wrapping C code? C has no seq type.
@shirleyquirk here is a working minimal example in Nim:
type
TapState* {.exportc: "altera_jtag_state", size: 4.} = enum
ILLEGAL_TAP_STATE = -1,
RESET = 0,
IDLE = 1,
DRSELECT = 2,
DRCAPTURE = 3,
DRSHIFT = 4,
DREXIT1 = 5,
DRPAUSE = 6,
DREXIT2 = 7,
DRUPDATE = 8,
IRSELECT = 9,
IRCAPTURE = 10,
IRSHIFT = 11,
IREXIT1 = 12,
IRPAUSE = 13,
IREXIT2 = 14,
IRUPDATE = 15
Bits* = object
data: seq[byte]
len:int
TapDriver* {.exportc: "altera_jtag", bycopy.} = object
tap_state, drstop_state, irstop_state: TapState
ir_pre,ir_post,dr_pre,dr_post,ir,dr: Bits
proc altera_jinit(js: var TapDriver) {.exportc.} =
zeroMem(js.addr, sizeof(js))
js.tap_state = ILLEGAL_TAP_STATE
js.drstop_state = IDLE
js.irstop_state = IDLE
{.emit: """
void altera_execute() {
struct altera_jtag _js, *js = &_js;
altera_jinit(js);
printf("%d\n", js->ir_pre.len);
}
""".}
proc altera_execute() {.importc.}
proc main() =
altera_execute()
main()
Although, as Araq said, you might want to replace Bits with this for better C compatibility:
type
Bits* = object
data: ptr UncheckedArray[byte]
len: int