StanzaOrg / lbstanza-old

L.B. Stanza Programming Language
Other
216 stars 23 forks source link

lostanza - passing pointer to double causes `fatal` during build. #173

Closed callendorph closed 1 year ago

callendorph commented 1 year ago

Hi,

I'm trying to write a lostanza wrapper around the gsl_vector_minmax function. This function takes a pointer to the underlying vector and then two additional pointers to doubles to store the resulting min and max values. In the process of trying to build the wrapper, the compiler seems to fault out.

I've created a small test case:

lostanza_ptrprob.zip

When I attempt to build this with stanza build I get this stack trace: https://gist.github.com/callendorph/2bbb7ef2bcfc461bdba3e797ec6ae2d6

I'm running the linux stanza v0.17.35 in WSL in windows.

Thoughts ?

OlegPliss commented 1 year ago

Yeah, it is a known limitation that you cannot get an address of a local variable. At different times a local variable can be located in different registers or in different stack frame slots.

CuppoJava commented 1 year ago

Thank you isolating the bug. The limitation is known, but we need to fix lack of error message that is generated.

There are two workarounds you can use for connecting to C functions that return results in this way.

1) Use global variables instead of local variable.

lostanza var MINV:double = 0.0
lostanza var MAXV:double = 0.0
public lostanza defn simple-minmax () -> ref<False> :
  val ret = call-c simple_minmax(addr(MINV), addr(MAXV))
  return false

This will work in the vast majority of cases.

2) Use heap-allocated boxes to store the results.

lostanza deftype DoubleBox :
  var value:double

public lostanza defn simple-minmax () -> ref<False> :
  val minv = new DoubleBox{0.0}
  val maxv = new DoubleBox{0.0}
  val ret = call-c simple_minmax(addr!(minv.value), addr!(maxv.value))
  return false

This is only necessary in the rare case where you expect the C function to call back into Stanza which then calls the same C function again. In that case, global variables won't work because they will get overwritten on the second time the C function is called.

callendorph commented 1 year ago

Got it - I was able to work around.

This is only necessary in the rare case where you expect the C function to call back into Stanza which then calls the same C function again. In that case, global variables won't work because they will get overwritten on the second time the C function is called.

I think this more generally applies to any function that you expect to be reentrant, correct ?

CuppoJava commented 1 year ago

Yes that's right. It's for any reentrant function.

callendorph commented 1 year ago

Alright - so I got that work around to compile - but I have a slightly tangential question. I used the following approach:

extern gsl_vector_minmax : (ptr<?>, ptr<?>, ptr<?>) -> int

lostanza deftype GVMinMaxDouble:
  var minV:double
  var maxV:double

public lostanza defn gsl-vector-minmax (A:ref<GVector>) -> ref<Tuple> :
  val mm = new GVMinMaxDouble{0.0, 0.0}
  call-c gsl_vector_minmax(A.value, addr!(mm.minV), addr!(mm.maxV))
  val ret = new Tuple{2}
  ret.items[0] = new Double{mm.minV}
  ret.items[1] = new Double{mm.maxV}
  return ret

public defn minmax (A:GVector) -> Tuple :
  gsl-vector-minmax(A)

Then I tried to use this in a unit test:

deftest gsl-minmax-vector-operator :
  val A = GVector(3)
  set-all(A, 1.0)
  A[1] = 0.5
  A[2] = -1.0

  val res = minmax(A)
  val minV = res[0]
  val maxV = res[1]
  ; This does not work with the result object
  ; val [minV2, maxV2] = res

  #EXPECT(minV == -1.0)
  #EXPECT(maxV == 1.0)

I can unpack the returned tuple object with explicit get operations without issue.

If I attempt to unpack the tuple with the val [minV2, maxV2] = res line - the compiler throws this error:

tests/GVector_tests.stanza:251.23: Cannot deconstruct expression of type Tuple<?> into tuple of
length 2.

So are the lostanza Tuple<?> and the hi-stanza tuple different objects? Is there a way to convert between the two ?

CuppoJava commented 1 year ago

Hi Carl,

That's pretty close. You just have to return a tuple of finite length by casting it explicitly.

public lostanza defn gsl-vector-minmax (A:ref<GVector>) -> ref<[Double,Double]> :
  val mm = new GVMinMaxDouble{0.0, 0.0}
  call-c gsl_vector_minmax(A.value, addr!(mm.minV), addr!(mm.maxV))
  val ret = core/void-tuple(2)
  ret.items[0] = new Double{mm.minV}
  ret.items[1] = new Double{mm.maxV}
  return ret as ref<[Double, Double]>

The above should work for what you want to do.

The only other change is calling core/void-tuple instead of using new Tuple{...} directly. The reason why is that, for performance reasons, the LoStanza new operator does not automatically initialize the memory of a variable-sized struct. This means it is your responsibility to ensure that the struct is fully initialized before running any code that may trigger the GC. These are low-level concerns, so it's easier to just call void-tuple, which will initialize all the fields of tuple for you.

callendorph commented 1 year ago

Got it - thank you!

callendorph commented 1 year ago

This error case of FATAL ERROR: No appropriate branch for arguments of type (Int). is extraordinarily difficult to debug. Do you have any suggestions on how to determine the cause of this error in a sane way. I've attempted to looking at the -verbose flag output and there isn't really anything there. I basically had to start just commenting out large sections of code until I could find the culprit. From a developer productivity perspective - this error is a nightmare.