@@ -218,7 +218,12 @@ definitions via the `cx` parameter:
218
218
class Context :
219
219
opts: CanonicalOptions
220
220
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()
222
227
```
223
228
224
229
The ` opts ` field represents the [ ` canonopt ` ] values supplied to
@@ -277,7 +282,7 @@ class OwnHandle(Handle):
277
282
278
283
@dataclass
279
284
class BorrowHandle (Handle ):
280
- pass
285
+ scope: BorrowScope
281
286
```
282
287
The ` resource ` field points to the resource instance this handle refers to. The
283
288
` 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
288
293
invariant that a handle cannot be dropped while it has currently lent out a
289
294
` borrow ` .
290
295
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.
295
298
``` python
296
- class Call :
299
+ class BorrowScope :
297
300
borrow_count: int
298
301
lenders: [OwnHandle]
299
302
300
303
def __init__ (self ):
301
304
self .borrow_count = 0
302
305
self .lenders = []
303
306
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
306
311
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 )
309
317
for h in self .lenders:
310
318
h.lend_count -= 1
311
319
```
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.
326
327
327
328
Based on these supporting runtime data structures, we can define the
328
329
` HandleTable ` in pieces, starting with its fields and the ` insert ` method:
@@ -343,18 +344,14 @@ class HandleTable:
343
344
else :
344
345
i = len (self .array)
345
346
self .array.append(h)
346
- if isinstance (h, BorrowHandle):
347
- cx.call.borrow_count += 1
348
347
return i
349
348
```
350
349
The ` HandleTable ` class maintains a dense array of handles that can contain
351
350
holes created by the ` remove ` method (defined below). These holes are kept in a
352
351
separate Python list here, but an optimizing implementation could instead store
353
352
the free list in the free elements of ` array ` . When inserting a new handle,
354
353
` 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.
358
355
359
356
The ` get ` method is used by other ` HandleTable ` methods and canonical
360
357
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
374
371
unique ` rt ` value distinct from every other type import even if the two imports
375
372
happen to be instantiated with the same resource type at runtime.
376
373
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).
379
377
``` 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 ):
392
379
h = self .get(i, t.rt)
393
380
trap_if(h.lend_count != 0 )
394
381
match t:
395
382
case Own(_):
396
383
trap_if(not isinstance (h, OwnHandle))
397
384
case Borrow(_):
398
385
trap_if(not isinstance (h, BorrowHandle))
399
- cx.call.borrow_count -= 1
386
+ h.scope.remove()
400
387
self .array[i] = None
401
388
self .free.append(i)
402
389
return h
@@ -621,17 +608,15 @@ Finally, `own` and `borrow` handles are lifted by loading their referenced resou
621
608
out of the current component instance's handle table:
622
609
``` python
623
610
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)
626
612
627
613
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)
630
615
```
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
632
617
across a component boundary transfers ownership. Note that ` remove ` checks
633
618
* 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 .
635
620
636
621
637
622
### Storing
@@ -975,12 +960,15 @@ def pack_flags_into_int(v, labels):
975
960
Finally, ` own ` and ` borrow ` handles are lowered by inserting them into the
976
961
current component instance's ` HandleTable ` :
977
962
``` 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 )
980
966
return cx.inst.handles.insert(cx, h)
981
967
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)
984
972
return cx.inst.handles.insert(cx, h)
985
973
```
986
974
Note that the ` rt ` value that is stored in the runtime ` Handle ` captures what
@@ -1418,11 +1406,11 @@ component*.
1418
1406
1419
1407
Given the above closure arguments, ` canon_lift ` is defined:
1420
1408
``` python
1421
- def canon_lift (cx , callee , ft , call , args ):
1409
+ def canon_lift (cx , callee , ft , args ):
1422
1410
trap_if(not cx.inst.may_enter)
1423
1411
1424
- outer_call = cx.call
1425
- cx.call = call
1412
+ outer_borrow_scope = cx.borrow_scope
1413
+ cx.borrow_scope = BorrowScope()
1426
1414
1427
1415
assert (cx.inst.may_leave)
1428
1416
cx.inst.may_leave = False
@@ -1440,8 +1428,8 @@ def canon_lift(cx, callee, ft, call, args):
1440
1428
if cx.opts.post_return is not None :
1441
1429
cx.opts.post_return(flat_results)
1442
1430
1443
- cx.call.finish_lift ()
1444
- cx.call = outer_call
1431
+ cx.borrow_scope.exit ()
1432
+ cx.borrow_scope = outer_borrow_scope
1445
1433
1446
1434
return (results, post_return)
1447
1435
```
@@ -1452,11 +1440,6 @@ boundaries. Thus, if a component wishes to signal an error, it must use some
1452
1440
sort of explicit type such as ` result ` (whose ` error ` case particular language
1453
1441
bindings may choose to map to and from exceptions).
1454
1442
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
-
1460
1443
The contract assumed by ` canon_lift ` (and ensured by ` canon_lower ` below) is
1461
1444
that the caller of ` canon_lift ` * must* call ` post_return ` right after lowering
1462
1445
` 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):
1489
1472
if calling_import:
1490
1473
cx.inst.may_enter = False
1491
1474
1492
- outer_call = cx.call
1493
- cx.call = Call()
1494
-
1495
1475
flat_args = ValueIter(flat_args)
1496
1476
args = lift_values(cx, MAX_FLAT_PARAMS , flat_args, ft.param_types())
1497
1477
1498
- results, post_return = callee(cx.call, args)
1478
+ results, post_return = callee(args)
1499
1479
1500
1480
cx.inst.may_leave = False
1501
1481
flat_results = lower_values(cx, MAX_FLAT_RESULTS , results, ft.result_types(), flat_args)
1502
1482
cx.inst.may_leave = True
1503
1483
1504
1484
post_return()
1505
1485
1506
- cx.call.finish_lower()
1507
- cx.call = outer_call
1508
-
1509
1486
if calling_import:
1510
1487
cx.inst.may_enter = True
1511
1488
@@ -1589,7 +1566,7 @@ be of type `$t` from the handle table and then, for an `own` handle, calls the
1589
1566
optional destructor.
1590
1567
``` python
1591
1568
def canon_resource_drop (cx , t , i ):
1592
- h = cx.inst.handles.remove(cx, i, t)
1569
+ h = cx.inst.handles.remove(i, t)
1593
1570
if isinstance (t, Own) and t.rt.dtor:
1594
1571
trap_if(not h.resource.impl.may_enter)
1595
1572
t.rt.dtor(h.resource.rep)
0 commit comments