Skip to content

Commit 5b4ce41

Browse files
committed
Refactor canonical-abi/definitions.py handle Python code
1 parent 1ffc0bd commit 5b4ce41

File tree

3 files changed

+95
-126
lines changed

3 files changed

+95
-126
lines changed

design/mvp/CanonicalABI.md

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,12 @@ definitions via the `cx` parameter:
218218
class Context:
219219
opts: CanonicalOptions
220220
inst: ComponentInstance
221-
call: Call
221+
borrow_scope: BorrowScope
222+
223+
def __init__(self):
224+
self.opts = CanonicalOptions()
225+
self.inst = ComponentInstance()
226+
self.borrow_scope = BorrowScope()
222227
```
223228

224229
The `opts` field represents the [`canonopt`] values supplied to
@@ -277,7 +282,7 @@ class OwnHandle(Handle):
277282

278283
@dataclass
279284
class BorrowHandle(Handle):
280-
pass
285+
scope: BorrowScope
281286
```
282287
The `resource` field points to the resource instance this handle refers to. The
283288
`rt` field points to a runtime value representing the static
@@ -288,41 +293,37 @@ of the outstanding handles that were lent from this handle (by calls to
288293
invariant that a handle cannot be dropped while it has currently lent out a
289294
`borrow`.
290295

291-
The `Call` class represents a single runtime call (activation) of a
292-
component-level function. A `Call` is finished in two steps by `finish_lift`
293-
and `finish_lower`, which are called at the end of `canon lift` and
294-
`canon lower`, resp.
296+
The `BorrowScope` class represents the scope of a single runtime call of a
297+
component-level function during which zero or more handles are borrowed.
295298
```python
296-
class Call:
299+
class BorrowScope:
297300
borrow_count: int
298301
lenders: [OwnHandle]
299302

300303
def __init__(self):
301304
self.borrow_count = 0
302305
self.lenders = []
303306

304-
def finish_lift(self):
305-
trap_if(self.borrow_count != 0)
307+
def add(self, src):
308+
src.lend_count += 1
309+
self.lenders.append(src)
310+
self.borrow_count += 1
306311

307-
def finish_lower(self):
308-
assert(self.borrow_count == 0)
312+
def remove(self):
313+
self.borrow_count -= 1
314+
315+
def exit(self):
316+
trap_if(self.borrow_count != 0)
309317
for h in self.lenders:
310318
h.lend_count -= 1
311319
```
312-
The `borrow_count` field tracks the number of outstanding `borrow` handles that
313-
were lent out for the duration of this call, trapping when the call finishes if
314-
there are any un-dropped `borrow` handles. The `lenders` field maintains a list
315-
of `own` handles that have lent out a `borrow` handle for the duration of the
316-
call. In an optimizing implementation, a `Call` can be stored directly on the
317-
stack with a layout specialized to the function signature which allows a
318-
fixed-size inline `lenders` list in the common case.
319-
320-
`Call` objects serve as an intermediate reference point for both the caller and
321-
callee: the caller keeps lent resources alive *at least as long* as the `Call`;
322-
the callee holds on to borrowed resources *at most as long* as the `Call`. This
323-
decoupling ensures that when *exactly* the callee drops a borrowed handle isn't
324-
observable to the caller thereby avoiding implicit, non-contractual ordering
325-
dependencies.
320+
The `borrow_count` field tracks the number of outstanding `BorrowHandle`s that
321+
were created when lowering the parameters of the call that have not yet been
322+
dropped. The `lenders` field maintains a list of source `Handle`s that have
323+
lent out a `BorrowHandle` and are to be restored when the call finishes and
324+
`exit` is called. In an optimizing implementation, a `BorrowScope` can be
325+
stored inline in the stack frame with a layout specialized to the function
326+
signature, thereby avoiding dynamic allocation of `lenders` in many cases.
326327

327328
Based on these supporting runtime data structures, we can define the
328329
`HandleTable` in pieces, starting with its fields and the `insert` method:
@@ -343,18 +344,14 @@ class HandleTable:
343344
else:
344345
i = len(self.array)
345346
self.array.append(h)
346-
if isinstance(h, BorrowHandle):
347-
cx.call.borrow_count += 1
348347
return i
349348
```
350349
The `HandleTable` class maintains a dense array of handles that can contain
351350
holes created by the `remove` method (defined below). These holes are kept in a
352351
separate Python list here, but an optimizing implementation could instead store
353352
the free list in the free elements of `array`. When inserting a new handle,
354353
`HandleTable` first consults the `free` list, which is popped LIFO to better
355-
detect use-after-free bugs in the guest code. The `insert` method increments
356-
`Call.borrow_count` to guard that this handle has been dropped by the end of
357-
the call.
354+
detect use-after-free bugs in the guest code.
358355

359356
The `get` method is used by other `HandleTable` methods and canonical
360357
definitions below and uses dynamic guards to catch out-of-bounds and
@@ -374,29 +371,19 @@ this check keeps type imports abstract, considering each type import to have a
374371
unique `rt` value distinct from every other type import even if the two imports
375372
happen to be instantiated with the same resource type at runtime.
376373

377-
The `lend` method is called when borrowing a handle. `lend` uses `Call.lenders`
378-
to decrement the handle's `lend_count` at the end of the call.
374+
Finally, the `remove` method is used to drop or transfer a handle out of the
375+
handle table. `remove` adds the removed handle to the `free` list for later
376+
recycling by `insert` (above).
379377
```python
380-
def lend(self, cx, i, rt):
381-
h = self.get(i, rt)
382-
h.lend_count += 1
383-
cx.call.lenders.append(h)
384-
return h
385-
```
386-
387-
Finally, the `remove` method is used when dropping or transferring a handle
388-
out of the handle table. `remove` adds the removed handle to the `free` list
389-
for later recycling by `insert` (above).
390-
```python
391-
def remove(self, cx, i, t):
378+
def remove(self, i, t):
392379
h = self.get(i, t.rt)
393380
trap_if(h.lend_count != 0)
394381
match t:
395382
case Own(_):
396383
trap_if(not isinstance(h, OwnHandle))
397384
case Borrow(_):
398385
trap_if(not isinstance(h, BorrowHandle))
399-
cx.call.borrow_count -= 1
386+
h.scope.remove()
400387
self.array[i] = None
401388
self.free.append(i)
402389
return h
@@ -621,17 +608,15 @@ Finally, `own` and `borrow` handles are lifted by loading their referenced resou
621608
out of the current component instance's handle table:
622609
```python
623610
def lift_own(cx, i, t):
624-
h = cx.inst.handles.remove(cx, i, t)
625-
return h.resource
611+
return cx.inst.handles.remove(i, t)
626612

627613
def lift_borrow(cx, i, t):
628-
h = cx.inst.handles.lend(cx, i, t.rt)
629-
return h.resource
614+
return cx.inst.handles.get(i, t.rt)
630615
```
631-
The `remove` method is used in `lift_own` and thus passing an `own` handle
616+
The `remove` method is used in `lift_own` so that passing an `own` handle
632617
across a component boundary transfers ownership. Note that `remove` checks
633618
*both* the resource type tag *and* that the indexed handle is an `OwnHandle`
634-
while `lend` only checks the resource type, thereby allowing both handle types.
619+
while `get` only checks the resource type, thereby allowing any handle type.
635620

636621

637622
### Storing
@@ -975,12 +960,15 @@ def pack_flags_into_int(v, labels):
975960
Finally, `own` and `borrow` handles are lowered by inserting them into the
976961
current component instance's `HandleTable`:
977962
```python
978-
def lower_own(cx, resource, rt):
979-
h = OwnHandle(resource, rt, 0)
963+
def lower_own(cx, src, rt):
964+
assert(isinstance(src, OwnHandle))
965+
h = OwnHandle(src.resource, rt, 0)
980966
return cx.inst.handles.insert(cx, h)
981967

982-
def lower_borrow(cx, resource, rt):
983-
h = BorrowHandle(resource, rt, 0)
968+
def lower_borrow(cx, src, rt):
969+
assert(isinstance(src, Handle))
970+
cx.borrow_scope.add(src)
971+
h = BorrowHandle(src.resource, rt, 0, cx.borrow_scope)
984972
return cx.inst.handles.insert(cx, h)
985973
```
986974
Note that the `rt` value that is stored in the runtime `Handle` captures what
@@ -1418,11 +1406,11 @@ component*.
14181406

14191407
Given the above closure arguments, `canon_lift` is defined:
14201408
```python
1421-
def canon_lift(cx, callee, ft, call, args):
1409+
def canon_lift(cx, callee, ft, args):
14221410
trap_if(not cx.inst.may_enter)
14231411

1424-
outer_call = cx.call
1425-
cx.call = call
1412+
outer_borrow_scope = cx.borrow_scope
1413+
cx.borrow_scope = BorrowScope()
14261414

14271415
assert(cx.inst.may_leave)
14281416
cx.inst.may_leave = False
@@ -1440,8 +1428,8 @@ def canon_lift(cx, callee, ft, call, args):
14401428
if cx.opts.post_return is not None:
14411429
cx.opts.post_return(flat_results)
14421430

1443-
cx.call.finish_lift()
1444-
cx.call = outer_call
1431+
cx.borrow_scope.exit()
1432+
cx.borrow_scope = outer_borrow_scope
14451433

14461434
return (results, post_return)
14471435
```
@@ -1452,11 +1440,6 @@ boundaries. Thus, if a component wishes to signal an error, it must use some
14521440
sort of explicit type such as `result` (whose `error` case particular language
14531441
bindings may choose to map to and from exceptions).
14541442

1455-
The `call` parameter is assumed to have been created by the caller (the host or
1456-
`canon lower`) for this one call. Since, in `not cx.called_as_export` scenarios
1457-
a single component instance may be reentered (by its children), `cx.call` must
1458-
save-and-restore a possible outer `Call` during an inner call.
1459-
14601443
The contract assumed by `canon_lift` (and ensured by `canon_lower` below) is
14611444
that the caller of `canon_lift` *must* call `post_return` right after lowering
14621445
`result`. This ensures that `post_return` can be used to perform cleanup
@@ -1489,23 +1472,17 @@ def canon_lower(cx, callee, calling_import, ft, flat_args):
14891472
if calling_import:
14901473
cx.inst.may_enter = False
14911474

1492-
outer_call = cx.call
1493-
cx.call = Call()
1494-
14951475
flat_args = ValueIter(flat_args)
14961476
args = lift_values(cx, MAX_FLAT_PARAMS, flat_args, ft.param_types())
14971477

1498-
results, post_return = callee(cx.call, args)
1478+
results, post_return = callee(args)
14991479

15001480
cx.inst.may_leave = False
15011481
flat_results = lower_values(cx, MAX_FLAT_RESULTS, results, ft.result_types(), flat_args)
15021482
cx.inst.may_leave = True
15031483

15041484
post_return()
15051485

1506-
cx.call.finish_lower()
1507-
cx.call = outer_call
1508-
15091486
if calling_import:
15101487
cx.inst.may_enter = True
15111488

@@ -1589,7 +1566,7 @@ be of type `$t` from the handle table and then, for an `own` handle, calls the
15891566
optional destructor.
15901567
```python
15911568
def canon_resource_drop(cx, t, i):
1592-
h = cx.inst.handles.remove(cx, i, t)
1569+
h = cx.inst.handles.remove(i, t)
15931570
if isinstance(t, Own) and t.rt.dtor:
15941571
trap_if(not h.resource.impl.may_enter)
15951572
t.rt.dtor(h.resource.rep)

0 commit comments

Comments
 (0)