The problem that Eric identified yesterday is disturbing, and I have
spent some time thinking about what might be done. The difficulty on the
one hand is that we absolutely need mutable unions. The difficulty on
the other is that unboxed mutable cells within a union are not safe in
the presence of any sort of reference to an element.

Worst of all, the problem is considerably broader than Eric identified.
Simply removing STACK-REF would not resolve the problem. Consider:

(defunion (list 'a) (cons 'a) nil)

(let ((m-u (mutable (cons #\c nil))))
  (case m-u
    ((cons hd tl)  (begin
                     (set! m-u nil)
                     hd))
    (x  #\{space})))

Note carefully a fact about CASE: the variables that are pattern matched
are matched as *references* into the argument. CASE is not a lambda
binding form -- or if you wish to think of it as a lambda binding, it is
a lambda binding using "by reference" parameters.

The use of by-reference bindings for this purpose is necessary, because
if we do *not* do this, the code on the right of the case cannot mutate
elements of the union. Unfortunately, *if* by-reference bindings are
used, we have precisely the same issue that Eric raised, as illustrated
above.

At this point, I am not prepared to advocate any particular resolution
to this problem, but here is *one* possibility: change the definition of
CASE so that it does NOT use by-reference bindings into the original
expression, but instead uses by-reference bindings into a *copy* of the
expression. [Yes, we could use traditional not-by-reference bindings,
but there is a reason that I am defining it this way.]

Note that because the by-reference bindings are made to a local,
immutable copy, there is no possibility of a safety hazard from STACK-
REF or the example I gave above -- the copy has no name and therefore
cannot be the target of a SET! that might change the union tag. Note
also that if the expression that is input to the CASE is known to be
local, the copy can be elided.

So now, let me introduce a notional new form: CASE!  The behavior of
this notional CASE! is as follows:

CASE! creates a *copy* of its argument expression, and pattern matches
the bindings **against the copy** just as CASE does. These bindings are
by-reference bindings to the actual elements of the copy. [This is
necessary for mutability to work]. Execution proceeds as before, but
*after* the execution of the selected RHS form, the copy is assigned
back to the original input location (think of this as occurring within
something a bit like a FINALLY block). The input expression to CASE!
must be mutable.

The effect of CASE! is to ensure that updates are applied only at the
*end* of the form, and to ensure that any internal references are made
only to local copies so that errant use of SET! do not create a safety
problem. Once again, the copy has no name and therefore cannot be the
target of a SET! that might change the union tag.

The main problem I can see with this approach is illustrated by the
following code:

(defunion (mu-list 'a) (mu-cons (mutable 'a)) mu-nil)

(let ((m-u (mutable (mu-cons #\c mu-nil))))
  (case! m-u
    ((cons hd tl)  (begin
                     (set! m-u mu-nil)
                     (set! hd #a)))
    (x  #\{space}))
  m-u)
=> (mu-cons #a nil)

Note that the effect of the SET! to m-u is lost, which is potentially
surprising, but not disastrous. This is, conceptually, no different from
something like:

(define i (mutable int32))
...
(set! i (begin (set! i 5)  4))
i
=> 4

Just in passing, let me note that if a solution of this form is adopted
for CASE, it probably needs to be adopted for TYPECASE for the same
reason.

Finally, let me note that CASE! and TYPECASE! need to perform the
(logical) copy even if the input expression is not a union. This is
necessary in order to preserve consistency of behavior in the presence
of internal SET! invocations.


shap

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

Reply via email to