-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reference counting in OCaml API #411
Comments
Martin, I agree. Issue #367 exposed this problem and it is systemic and avoided provided expressions are referenced after the calls. I started adding shorthands for functions that decompose and then build expressions using native APIs. I don't have a good solution to pin expressions in a way I can guarantee that the compiler doesn't perform some purity analysis and wipes out the pinning code and so far just use the reference counting facility for this. |
It may be a reasonable idea to introduce auxiliary functions to pin arguments. These functions may then be implemented using inc_ref/dec_ref or something better. Sprinkling the code with inc/dec refs everywhere makes maintainance more difficult. E.g.,
|
Yes, this one's all over the place, mainly because I was under the impression that OCaml isn't allowed to garbage-collect function arguments while the function is still executing, but apparently this is what's happening now (maybe only in 4.0+?). I haven't got a plan of attack to this problem either. Maybe there's a magic function that tells the GC not to collect those objects? |
@NikolajBjorner: Actually, what happens in ML when a function is called? Does that compile to an assignment that calls inc_ref, or can we perhaps make it do that by adding some operator=s or alike? |
Following up on my comment on making things a bit more encapsulated. Here is another idea. E.g.:
Then one can define:
There must be something even better, but at least encapsulating the pinning in auxiliary functions would make post-op surgeries easier to maintain. |
@NikolajBjorner To my opinion, pinning the terms with inc_ref/dec_ref (in whatever form) is not a good solution either, as it produces many calls to the native Z3 library which are almost always unnecessary. I posted a question on the OCaml mailing list, looking for suggestions: |
Perhaps a bigger change than you are interested in, but what I did in http://z3.codeplex.com/SourceControl/network/forks/jjb/Z3/latest#src/api/ml/z3.0.idl was to create ocaml wrapper objects of Z3 native pointers as what ocaml calls 'custom blocks'. The comment at the beginning of that linked file gives a brief overview of the design. Custom blocks are single ocaml values, and so atomic from the perspective of the ocaml GC. The finalizers that call Z3 dec_ref functions (plus pointers to e.g. the enclosing Z3 context object) are installed in the custom block. Calls to the ocaml allocator from C are embraced by calls to Begin_roots and End_roots to keep the temporary pointers to ocaml values live across the alloc call. See e.g. Are you sure that the crashes you are seeing are due to ast objects, or could they be due to context objects? I ended up with two versions of bindings, one that leaked context objects by never calling dec_ref on them, and one that properly cleaned them up. The version that cleaned them up relied on the ocaml Gc module finalizers being guaranteed to be called in reverse-registration order, and was careful to ensure that the finalizers for contexts were registered first. Temporarily pinning objects might be easier done with the C caml_register_generational_global_root and caml_remove_generational_global_root functions, avoiding many calls to the Z3 lib. |
Awesome, thanks for your comments @jberdine! I agree, we could take this down into the stubs where ocaml can't look into the code, so it won't collect anything. I kind of like the idea of using caml_register_generational_global_root, but it's not particularly nice either. Do you know whether that's compile-time and thus cheap or runtime expensive? Formally speaking, it's actually OK if OCaml collects the "high-level" list-of-ast object. By creating a second copy of the native object, we should increment the ref counts, because we do in fact hold another reference, i.e., *_lton should actually call inf_ref on each object, and we delete the list-of-native object after the function call (i.e., call dec_ref on each). We could simply introduce a new data structure which calls the dec_ref for each element when the list/array goes out of scope (e.g., put a finalizer onto the array?), but OCaml users seem not to be partial to object-orientation it seems. As Martin said, this will also create loads of API calls which isn't particularly attractive either. Originally the idea was to create a new luxury, highest-level structure as in the other APIs, for OCaml without the need for camlidl. But, almost nothing of that idea remains, so perhaps it's best if we just revert the native layer to what camlidl used to generate, and perhaps even use it, if it comes with all the different ocaml flavours now. We could still keep the module structure in z3.ml/mli which people seem to like. |
The issue is not so much about C vs OCaml at the language level but at With custom blocks, the header tells the gc that there are no pointers But the main issue seems to be to keep the native pointer to a Z3 object One thing to keep in mind with registering roots is that this technique
Run time. Registering a root adds the address to the set of addresses
Yes, there are two approaches that at some level are equivalent. The
Yeah, we have a real module system.
Flavors? The code it generates uses type names defined in the ocaml Still, with the code camlidl generates as a guide, it is hopefully not
Yes, absolutely, that is a real win. |
I just spent another full working day exclusively on installing an ocaml compiler on my office machine. Out of all the supported Windows ways of compiling and binary packages, only one of them yielded an ocamlopt binary that I could run, but which was unable to link any executable against my system libraries. After 8 hours of pain and cursing I gave in and ssh'ed into our OSX machine, but the ocaml installed there (4.02.1) does not reproduce the problem. Seriously, I don't think there is enough time left in my life to compile even a single ocaml program. |
An update for the others: I recently pushed all of those issues down into the C layer and I keep that in a separate branch called new-ml-api on my fork for now (see here). This works OK, but I'm sure there are some bugs still lurking in all of that, and Martin has sent me some new interaction logs that trigger errors, but so far I don't know whether they are really in this layer, or in Z3 proper. |
The OCaml API has been re-worked in the current master. |
The OCaml API uses Z3's reference counting by registering a finalizer (which is called upon a major garbage collection of OCaml) with all Z3 native objects:
Upon freeing a
z3_native_object
, the finalizer usesdec_ref
to decrement Z3's reference count for them_n_obj
. If it drops to zero, the object is freed by Z3.Now a pattern in the bindings is as follows (e.g. for simplify):
Assume a call to
simplify
with some term and no parameters.When
expr_of_ptr (Expr.gc x) (Z3native.simplify (gnc x) (gno x))
gets evaluated,gnc x
andgno x
are evaluated to obtain the native pointers before callingZ3native.simplify
.If the garbage collector performs a major collection before the call to
Z3native.simplify
, the OCaml valuex
might have already become unreachable: This occurs ifx
is not used at any later point in the program. For the simplify function, this might actually occur.In such a garbage collection, value
x
is freed and its finalizer decrements Z3's reference counter on its native pointer (i.e. the pointer that has previously been obtained ingno x
).If the reference pointer drops to zero, Z3 frees the term and
simplify
is called with an invalid pointer.If the simplify function (which is only one example - the same behavior occurs in many other functions in the OCaml API) is rewritten to force a garbage collection as follows,
a segmentation fault can be provoked by a simple test program (if built as native-code executable):
Note that if the term
term
is used after the invocation ofZ3.Expr.simplify
,the garbage collector does not free it, and no segfault occurs:
Am I correctly suspecting a bug in the way, reference counting is implemented in the OCaml API?
The above example uses
simplify
, but there a plenty of other functions in the API that extractnative pointers from OCaml values such that the latter might become garbage collected, and their native objects being freed before they are used in a Z3 native call.
All of the above has been tested with Z3 at commit e88ac37. The problem might be related to issue #367. A try at fixing the issue is available at https://github.com/martin-neuhaeusser/z3
The text was updated successfully, but these errors were encountered: