The scheme is pretty simple has a pretty simple basis, but well… you'll see. To set the stage, notes about how Hylo differs from Swift so you can read the code.
- In Hylo,
let means “immutable borrow,” like what Swift calls borrowing.
- Hylo uses the
let and inout keywords for the equivalent of Swift’s _readand _modify accesors, respectively
- Hylo supports named
inout and let bindings to projection results.
- Hylo guarantees that lifetimes end at the first opportunity.
- Pointers in Hylo are not unsafe, only access to their pointees is
So, you can have code like:
fun f(x: Array<Int>, y: inout Array<Array<String>>) {
// The next three lines are equivalent to y[1][x[1]] = "Hello World".
let x1 = x[1]
inout y1 = &y[1]
y1[x1] = "Hello World"
// lifetime of x1 projection ends here.
print(y1)
// lifetime of y1 projection ends here.
}
Array element access will be done with an addressors for efficiency, but we need to support this pattern for yielding accessors, e.g. for types like Swift’s Dictionary whose _modify has to construct an Optional<Value>, yield it, and then take action after the yield based on the Optional's value to update the Dictionary. We call the accessor’s code before the yield its “ramp” and the code after the yield its “slide.”
The model starts with the realization that for most use cases an inoutor let accessor can be simply transformed into a higher-order function, so internally you could rewrite:
subscript(j: Int): String {
inout {
var q: String = ramp_code(j)
yield &q
slide_code(q)
}
...
into
fun subscript_inout(j: Int, access: (inout String)->Void) {
var q: String = ramp_code(j)
access(&q)
slide_code(q)
}
The body of the accessor is just transplanted into the function and the yield is replaced by the call to access. At the call site you wrap the code that uses the yielded in a closure, and pass that to the subscriptInout function as its access parameter.
However, this doesn’t cover cases shaped like our first example, where the ramps and slides don’t nest: you have x1's ramp, y1's ramp, x1's slide, and finally y1's slide. To handle that, you need to change the transformation slightly:
(I hope I didn’t make too many errors in this, I didn’t pass it through a real compiler and this is the first time I’ve tried to put it in code.)
fun subscript_inout<Caller>(
j: Int,
continue: InoutScopeContinuation<String, Caller>,
caller_locals: Locals<Caller>
) {
var q: String = rampCode(j)
continue(
&q,
fun (my_locals: SlideLocals)->Void {
slideCode(&Locals<MyLocals>(my_locals).unsafe_pointee.q)
},
my_locals, // <=== synthesized pointer to local stack frame.
caller_locals)
}
where:
/// Captured access to a functions's local variables, where
/// `X` has the layout of its parameters and local variables.
type Locals<X> = MutablePointer<X>
/// Contextually defined in each function the layout of its
/// parameters and local variables.
type MyLocals = ...
/// Type erased `Locals<Slide>` where `Slide` is `MyLocals` for some
/// slide function.
type SlideLocals = RawPointer
/// Post-yield code of an accessor projecting `inout T`
type InoutSlide<Accessor> = (Locals<Accessor>)->Void
/// The rest of a caller up to the end of the enclosing block.
type InoutScopeContinuation<T, Caller>
= (projected: inout T,
slide: InoutSlide<T>, slideLocals: RawPointer,
caller_locals: Locals<Caller>)->Void
The transformation in the caller is mostly implied by the names and comments above, but I’ll spell it out:
- the remainder of the enclosing block containing the access is represented as an
InoutScopeContinuation<String, MyLocals>
- Since the caller knows exactly where the slide needs to execute, a call to the
slide parameter of that continuation is made at that point, passing slideLocals.
- If there is another projection inside the continuation, the transformation is repeated.
- All the local variables stay accessible because the chain of continuations doesn’t return until the end of scope.
- You don’t actually need to use the end of the scope in most cases; you can trivially compute where a chain of overlapping projections ends and return from continuations at that point.
That’s it; LMK if you have any questions. I’m interested to hear what @Joe_Groff may be doing in this space.