I want to expand a little on how the rust pointer types relate to the
corresponding BitC binding types. I think Rust is confusing data types and
region types in its current description of the semantics.

BitC started with by-reference parameters, and soon added something that we
initially called "ref" bindings. What happened was that we had a rewriting
pass anticipating Henderson-style safe GC that created a local on-stack
binding for every parameter, and we needed a way to record the fact that
this binding must not get captured. The result is very similar to - and
strictly more general than - a Rust borrowed pointer.

Internally, we never bothered to introduce (by-ref 'a) as a first-class
type. We treated those as being of type 'a with a marker bit indicating
that the final code emitter needed to add an extra dereference. This
ultimately created a problem, because it meant we couldn't check type class
method type agreement when we went to do input streams, but that's a story
for another day. The point is that until we hit the method overloading
problem we didn't care, because by-ref parameters and by-ref bindings were
guaranteed to be non-escaping by virtue of their lexical and syntactic
structure. So on a parameter, BY-REF implied NOESCAPE, where in the
internal let bindings LET REF would probably have been labeled better as
LET NOESCAPE x = <intiializer> in ....

Now here's the point: when a LET NOESCAPE binding is bound to a new
allocation, it is necessarily the point where the allocated object must go
out of scope. Further: objects allocated in this fashion necessarily obey a
strict stack discipline in their allocations and deallocations. Further, if
the underlying initializer can handle it and the size of the target object
is know, the object can be stack allocated. And then the constraint is that
such a binding can only be passed as a non-escaping parameters, and can
only be captured by bindings that we know will go out of scope sooner than
the dominating binding.

In fact, the only time you *can't* stack-allocate the value is when the
size of the target type is unknown. Or when the target system imposes
constraints on overall stack size or introduces stack red-zone problems,
but those are problems of implementation that can be solved with a separate
allocation stack.

Very shortly thereafter it became apparent that something a bit more
flexible than non-escape was needed. I decided we needed regions, but we
never got to them.

The point I think I'm trying to make is that NOESCAPE is actually a region
constraint, and region constraints are strictly more powerful than the
owning pointer notion. But more importantly, NOESCAPE and OWNED describe
the *region type* of a binding rather than the *data type* of a binding.

The other thing you get when regions are introduced is the ability to name,
bind and pass regions as first-class types. At that point you can allocate
objects within specific regions of the heap, and you can do both local GC's
of that region and bulk deallocation of the region.

Regions aren't a cure-all. It's very common for objects within a region to
become unreferenced in real programs, and there are examples of obvious
real-world programs in which automatically inferred regions grow without
bound as a result.

But if you're going to add them, I think it's better to do them in a
first-class way, not as a bolt-on side effect of an unusual pointer type
annotation *or* an unusual binding type annotation.


Jonathan
_______________________________________________
bitc-dev mailing list
[email protected]
http://www.coyotos.org/mailman/listinfo/bitc-dev

Reply via email to