StanzaOrg / lbstanza-old

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

`c-autofree-marker` commented out #171

Open callendorph opened 1 year ago

callendorph commented 1 year ago

Hi,

In your documentation for the Tetris example, you talk about c-autofree-marker and the LivenessMarker object.

In the stanza code here:

https://github.com/StanzaOrg/lbstanza/blob/229ee4e013e0f411742390f31bf97af159ddc1f1/core/core.stanza#L9771

This implementation is commented out. This causes the tetris example to not build on relatively new versions of stanza because it can't resolve LivenessMarker or c-autofree-marker.

What is the status of this code? I attempted to follow what was going on in the git history. From what I can tell - that code gets commented out in 79805853dd4.

It seems to me like this is a pretty critical feature for creating sane wrappers around moderately complicated C libraries.

Is there an alternate or work around for this functionality?

CuppoJava commented 1 year ago

Ah, the LivenessTracker has been replaced by a simpler finalizer system, but the c-autofree-xyz utilities hasn't been updated.

We'll push out a new version of the utilities. In the meantime, here is a description of how the finalizer utility works.

;Represents a finalizer that calls 'free' on a pointer
;when it executes.
public lostanza deftype MyCFinalizer <: Finalizer :
  c-pointer:ptr<?>

;The 'run' method just calls 'free' on the pointer.
lostanza defmethod run (f:ref<MyCFinalizer>) -> ref<False> :
  call-c clib/free(f.c-pointer)
  return false

With this finalizer, you can attach it to all objects that are declared as subtypes of Unique.

So the QBrush example would have to be updated like this:

;Make QBrush as subtype of Unique so that we can attach
;a finalizer to it.
public lostanza deftype QBrush <: Unique :
  value:ptr<?>

...

;The QBrush constructor automatically creates a finalizer
;that deletes the pointer when the QBrush is garbage-collected.
public lostanza defn QBrush () -> ref<QBrush> :
  val ptr = call-c QBrush_new()
  val brush = new QBrush{ptr}
  add-finalizer(new MyCFinalizer{ptr}, brush)
  return brush
callendorph commented 1 year ago

Another instance where the documentation is coming up short.

For the lbstanza.org website - how is the Reference content generated?

callendorph commented 1 year ago

OK - I tried to use that in the following manner:

lostanza deftype GVectorFinalizer <: Finalizer :
  value:ptr<?>

lostanza defmethod run (v:ref<GVectorFinalizer>) -> ref<False> :
  fatal("running finarlizer")
  call-c gsl_vector_free(v.value)
  return false

public lostanza deftype GVector <: Unique :
  value:ptr<?>
  size:int

public lostanza defn GVector (size:ref<Int>) -> ref<GVector> :
  val ptr = call-c gsl_vector_alloc(size.value)
  val null = 0L as ptr<?>
  if ptr == null :
    throw(GSLException(gsl-ENOMEM()))
  val ret = new GVector{ptr,size.value}
  add-finalizer(new GVectorFinalizer{ptr}, ret)
  return ret

I then created a test like this:

#use-added-syntax(tests)
defpackage gsl/GVector/tests :
  import core
  import gsl/GVector

defn scoped-vector () :
  val uut = GVector(3)
  uut[0] = 100.0
  var obs = uut[0]
  #EXPECT(obs == 100.0)

deftest gsl-gvector-test :
  scoped-vector()

  val uut = GVector(3)

  uut[0] = 10.0
  uut[1] = -1.0
  uut[2] = 0.0

  var obs = uut[0]
  #EXPECT(obs == 10.0)

  obs = uut[1]
  #EXPECT(obs == -1.0)

  obs = uut[2]
  #EXPECT(obs == 0.0)

When I run this:

gsl$ ./gsl-tests.exe gsl-gvector-test                
[Test 1] gsl-gvector-test                                                                                               
[PASS]                                                                                                                                                                                                                                          

Tests Finished: 1/1 tests passed. 0 tests skipped. 0 tests failed.                                                                                                                                                                              

Longest Running Tests:                                                                                                  
[PASS] gsl-gvector-test (792 us)           

I'm using stanza version 0.17.33 in WSL.

I expect the fatal to trigger in the finalizer's run method but it never does.

Is there some aspect of the unit test framework that I'm misunderstanding ? Can you replicate this behavior?

I see the finalizer used in the Process object in core.stanza so it seems like this is in use.

CuppoJava commented 1 year ago

Hi Carl,

As far as I can tell, your code is correct, and the testing framework is also doing what it should.

Are you expecting the fatal to trigger at a very specific moment?

The finalizer is executed when the GVector is eventually garbage-collected, but the test is probably too short to trigger the GC.

If you execute a loop afterwards that does nothing except allocate a bunch of memory:

for i in 0 to 10000 do :
  Array<?>(1000)

This will likely trigger the GC, and cause your finalizer to run.

callendorph commented 1 year ago

Are you expecting the fatal to trigger at a very specific moment? The finalizer is executed when the GVector is eventually garbage-collected, but the test is probably too short to trigger the GC.

OK - I probably don't understand the GC very well. I was expecting the GC to collect the GVector object at two places:

  1. At the end of the scoped-vector function where uut comes into and out of scope.
  2. At the end of the deftest body when the other uut goes out of a scope.

When does the GC run ? Is there a heuristic for the GC ?

CuppoJava commented 1 year ago

Oh I see.

Stanza works off of a different mental model for the GC, that is conceptually closer to Ruby/Java than to C++ destructors. The mental model is that Stanza starts up with a small heap of about 4MB, and begins executing your program. All allocations are taken from this heap, and so the heap gradually fills up until it is full. When it is full, it triggers a GC which sweeps through the 4MB and deletes the dead objects, and continues executing your program afterwards. We play tricks in the backend to make sure that the sweeping and deleting can be done very quickly.

If the GC wasn’t able to clear up much space, then the heap is enlarged, and your program is resumed with a larger heap.

For something like your GVector example, your usage of the finalizer will work well. But it does mean that you shouldn’t rely upon any specific timing of when the GC runs.

callendorph commented 1 year ago

OK - that is very helpful - thanks!

For something like your GVector example, your usage of the finalizer will work well.

I agree that the finalizer implementation in this case does seem appropriate for the GVector object.