@@ -218,13 +218,25 @@ definitions via the `cx` parameter:
218
218
class Context :
219
219
opts: CanonicalOptions
220
220
inst: ComponentInstance
221
- borrow_scope: BorrowScope
221
+ lenders: [Handle]
222
+ borrow_count: int
222
223
223
224
def __init__ (self , opts , inst ):
224
225
self .opts = opts
225
226
self .inst = inst
226
- self .borrow_scope = BorrowScope()
227
+ self .lenders = []
228
+ self .borrow_count = 0
227
229
```
230
+ One ` Context ` is created for each call for both the caller and callee. Thus, a
231
+ cross-component call will create 2 ` Context ` objects for the call, while a
232
+ component-to-host or host-to-component call will create a single ` Context ` for
233
+ the wasm caller or callee, resp.
234
+
235
+ The ` lenders ` and ` borrow_count ` fields will be used below for dynamically
236
+ enforcing the rules of borrow handles. The one thing to mention here is that
237
+ the ` lenders ` list usually has a fixed size (in all cases except when a
238
+ function signature has ` borrow ` s in ` list ` s) and thus can be stored inline in
239
+ the native stack frame.
228
240
229
241
The ` opts ` field represents the [ ` canonopt ` ] values supplied to
230
242
currently-executing ` canon lift ` or ` canon lower ` :
@@ -282,48 +294,22 @@ class OwnHandle(Handle):
282
294
283
295
@dataclass
284
296
class BorrowHandle (Handle ):
285
- scope: BorrowScope
297
+ cx: Optional[Context]
286
298
```
287
299
The ` lend_count ` field maintains a count of the outstanding handles that were
288
300
lent from this handle (by calls to ` borrow ` -taking functions). This count is
289
301
used below to dynamically enforce the invariant that a handle cannot be
290
302
dropped while it has currently lent out a ` borrow ` .
291
303
292
- The ` BorrowScope ` class represents the scope of a single runtime call of a
293
- component-level function during which zero or more handles are borrowed.
294
- ``` python
295
- class BorrowScope :
296
- borrow_count: int
297
- lenders: [Handle]
298
-
299
- def __init__ (self ):
300
- self .borrow_count = 0
301
- self .lenders = []
302
-
303
- def add (self , src ):
304
- src.lend_count += 1
305
- self .lenders.append(src)
306
- self .borrow_count += 1
307
-
308
- def remove (self ):
309
- self .borrow_count -= 1
310
-
311
- def exit (self ):
312
- trap_if(self .borrow_count != 0 )
313
- for h in self .lenders:
314
- h.lend_count -= 1
315
- ```
316
- The ` borrow_count ` field tracks the number of outstanding ` BorrowHandle ` s that
317
- were created when lowering the parameters of the call that have not yet been
318
- dropped. The ` lenders ` field maintains a list of source ` Handle ` s that have
319
- lent out a ` BorrowHandle ` and are to be restored when the call finishes and
320
- ` exit ` is called. In an optimizing implementation, a ` BorrowScope ` can be
321
- stored inline in the stack frame with a layout specialized to the function
322
- signature, thereby avoiding dynamic allocation of ` lenders ` in many cases.
304
+ The ` BorrowHandle ` class additionally stores the ` Context ` of the call that
305
+ created this borrow for the purposes of ` borrow_count ` bookkeeping below.
306
+ Until async is added to the Component Model, because of the non-reentrancy
307
+ of components, there is at most one callee ` Context ` alive for a given
308
+ component and thus the ` cx ` field of ` BorrowHandle ` can be optimized away.
323
309
324
310
` HandleTable ` (singular) encapsulates a single mutable, growable array
325
311
of handles that all share the same ` ResourceType ` . Defining ` HandleTable ` in
326
- chunks, we start with the fields and ` insert ` method:
312
+ chunks, we start with the fields and ` add ` method:
327
313
``` python
328
314
class HandleTable :
329
315
array: [Optional[Handle]]
@@ -333,20 +319,23 @@ class HandleTable:
333
319
self .array = []
334
320
self .free = []
335
321
336
- def insert (self , h ):
322
+ def add (self , h , t ):
337
323
if self .free:
338
324
i = self .free.pop()
339
325
assert (self .array[i] is None )
340
326
self .array[i] = h
341
327
else :
342
328
i = len (self .array)
343
329
self .array.append(h)
330
+ match t:
331
+ case Borrow():
332
+ h.cx.borrow_count += 1
344
333
return i
345
334
```
346
335
The ` HandleTable ` class maintains a dense array of handles that can contain
347
336
holes created by the ` remove ` method (defined below). These holes are kept in a
348
337
separate Python list here, but an optimizing implementation could instead store
349
- the free list in the free elements of ` array ` . When inserting a new handle,
338
+ the free list in the free elements of ` array ` . When adding a new handle,
350
339
` HandleTable ` first consults the ` free ` list, which is popped LIFO to better
351
340
detect use-after-free bugs in the guest code.
352
341
@@ -362,17 +351,20 @@ use-after-free:
362
351
363
352
The last method of ` HandleTable ` , ` remove ` , is used to drop or transfer a
364
353
handle out of the handle table. ` remove ` adds the removed handle to the ` free `
365
- list for later recycling by ` insert ` (above).
354
+ list for later recycling by ` add ` (above).
366
355
``` python
367
- def remove (self , i , t ):
356
+ def remove_or_drop (self , i , t , drop ):
368
357
h = self .get(i)
369
358
trap_if(h.lend_count != 0 )
370
359
match t:
371
- case Own(_ ):
360
+ case Own():
372
361
trap_if(not isinstance (h, OwnHandle))
373
- case Borrow(_):
362
+ if drop and t.rt.dtor:
363
+ trap_if(not t.rt.impl.may_enter)
364
+ t.rt.dtor(h.rep)
365
+ case Borrow():
374
366
trap_if(not isinstance (h, BorrowHandle))
375
- h.scope.remove()
367
+ h.cx.borrow_count -= 1
376
368
self .array[i] = None
377
369
self .free.append(i)
378
370
return h
@@ -400,17 +392,19 @@ class HandleTables:
400
392
self .rt_to_table[id (rt)] = HandleTable()
401
393
return self .rt_to_table[id (rt)]
402
394
403
- def insert (self , h , rt ):
404
- return self .table(rt).insert(h )
395
+ def add (self , h , t ):
396
+ return self .table(t. rt).add(h, t )
405
397
def get (self , i , rt ):
406
398
return self .table(rt).get(i)
407
399
def remove (self , i , t ):
408
- return self .table(t.rt).remove(i, t)
400
+ return self .table(t.rt).remove_or_drop(i, t, drop = False )
401
+ def drop (self , i , t ):
402
+ self .table(t.rt).remove_or_drop(i, t, drop = True )
409
403
```
410
404
While this Python code performs a dynamic hash-table lookup on each handle
411
405
table access, as we'll see below, the ` rt ` parameter is always statically
412
406
known such that a normal implementation can statically enumerate all
413
- ` HandleTable ` objects at compile time and then route the calls to ` insert ` ,
407
+ ` HandleTable ` objects at compile time and then route the calls to ` add ` ,
414
408
` get ` and ` remove ` to the correct ` HandleTable ` at the callsite. The net
415
409
result is that each component instance will contain one handle table per
416
410
resource type used by the component, with each compiled adapter function
@@ -445,8 +439,8 @@ def load(cx, ptr, t):
445
439
case Record(fields) : return load_record(cx, ptr, fields)
446
440
case Variant(cases) : return load_variant(cx, ptr, cases)
447
441
case Flags(labels) : return load_flags(cx, ptr, labels)
448
- case Own(_) : return lift_own(cx, load_int(opts, ptr, 4 ), t)
449
- case Borrow(_) : return lift_borrow(cx, load_int(opts, ptr, 4 ), t)
442
+ case Own() : return lift_own(cx, load_int(opts, ptr, 4 ), t)
443
+ case Borrow() : return lift_borrow(cx, load_int(opts, ptr, 4 ), t)
450
444
```
451
445
452
446
Integers are loaded directly from memory, with their high-order bit interpreted
@@ -623,19 +617,31 @@ def unpack_flags_from_int(i, labels):
623
617
return record
624
618
```
625
619
626
- Finally, ` own ` and ` borrow ` handles are lifted by loading their referenced resources
627
- out of the current component instance's handle table:
620
+ Next, ` own ` handles are lifted removing the ` OwnHandle ` from the handle table
621
+ and passing the underlying representation value as the lifted value. The other
622
+ side will wrap this representation value in its own new ` OwnHandle ` in its on
623
+ handle table.
628
624
``` python
629
625
def lift_own (cx , i , t ):
630
- return cx.inst.handles.remove(i, t)
626
+ h = cx.inst.handles.remove(i, t)
627
+ return OwnHandle(h.rep, 0 )
628
+ ```
629
+ Note that ` t ` refers to an ` own ` type and thus ` HandleTable.remove ` will, as
630
+ shown above, ensure that the handle at index ` i ` is an ` OwnHandle ` .
631
631
632
+ Lastly, ` borrow ` handles are lifted by getting the handle out of the
633
+ appropriate resource type's handle table.
634
+ ``` python
632
635
def lift_borrow (cx , i , t ):
633
- return cx.inst.handles.get(i, t.rt)
636
+ h = cx.inst.handles.get(i, t.rt)
637
+ h.lend_count += 1
638
+ cx.lenders.append(h)
639
+ return BorrowHandle(h.rep, 0 , None )
634
640
```
635
- The ` remove ` method is used in ` lift_own ` so that passing an ` own ` handle
636
- across a component boundary transfers ownership. Note that ` remove ` checks
637
- * both * the resource type tag * and * that the indexed handle is an ` OwnHandle `
638
- while ` get ` only checks the resource type, thereby allowing any handle type .
641
+ Thus, ` borrow ` lifting allows all handle types to be supplied for a ` borrow ` ,
642
+ as long as they have a matching resource type ( ` t.rt ` ). The lending handle is
643
+ passed as the lifted value so that the receiver can increment and decrement
644
+ its ` lend_count ` .
639
645
640
646
641
647
### Storing
@@ -665,8 +671,8 @@ def store(cx, v, t, ptr):
665
671
case Record(fields) : store_record(cx, v, ptr, fields)
666
672
case Variant(cases) : store_variant(cx, v, ptr, cases)
667
673
case Flags(labels) : store_flags(cx, v, ptr, labels)
668
- case Own(rt ) : store_int(cx, lower_own(opts, v, rt ), ptr, 4 )
669
- case Borrow(rt ) : store_int(cx, lower_borrow(opts, v, rt ), ptr, 4 )
674
+ case Own() : store_int(cx, lower_own(opts, v, t ), ptr, 4 )
675
+ case Borrow() : store_int(cx, lower_borrow(opts, v, t ), ptr, 4 )
670
676
```
671
677
672
678
Integers are stored directly into memory. Because the input domain is exactly
@@ -979,18 +985,16 @@ def pack_flags_into_int(v, labels):
979
985
Finally, ` own ` and ` borrow ` handles are lowered by inserting them into the
980
986
current component instance's ` HandleTable ` :
981
987
``` python
982
- def lower_own (cx , src , rt ):
983
- assert (isinstance (src, OwnHandle))
984
- h = OwnHandle(src.rep, 0 )
985
- return cx.inst.handles.insert(h, rt)
986
-
987
- def lower_borrow (cx , src , rt ):
988
- assert (isinstance (src, Handle))
989
- if cx.inst is rt.impl:
990
- return src.rep
991
- cx.borrow_scope.add(src)
992
- h = BorrowHandle(src.rep, 0 , cx.borrow_scope)
993
- return cx.inst.handles.insert(h, rt)
988
+ def lower_own (cx , h , t ):
989
+ assert (isinstance (h, OwnHandle))
990
+ return cx.inst.handles.add(h, t)
991
+
992
+ def lower_borrow (cx , h , t ):
993
+ assert (isinstance (h, BorrowHandle))
994
+ if cx.inst is t.rt.impl:
995
+ return h.rep
996
+ h.cx = cx
997
+ return cx.inst.handles.add(h, t)
994
998
```
995
999
The special case in ` lower_borrow ` is an optimization, recognizing that, when
996
1000
a borrowed handle is passed to the component that implemented the resource
@@ -1151,8 +1155,8 @@ def lift_flat(cx, vi, t):
1151
1155
case Record(fields) : return lift_flat_record(cx, vi, fields)
1152
1156
case Variant(cases) : return lift_flat_variant(cx, vi, cases)
1153
1157
case Flags(labels) : return lift_flat_flags(vi, labels)
1154
- case Own(_) : return lift_own(cx, vi.next(' i32' ), t)
1155
- case Borrow(_) : return lift_borrow(cx, vi.next(' i32' ), t)
1158
+ case Own() : return lift_own(cx, vi.next(' i32' ), t)
1159
+ case Borrow() : return lift_borrow(cx, vi.next(' i32' ), t)
1156
1160
```
1157
1161
1158
1162
Integers are lifted from core ` i32 ` or ` i64 ` values using the signedness of the
@@ -1275,8 +1279,8 @@ def lower_flat(cx, v, t):
1275
1279
case Record(fields) : return lower_flat_record(cx, v, fields)
1276
1280
case Variant(cases) : return lower_flat_variant(cx, v, cases)
1277
1281
case Flags(labels) : return lower_flat_flags(v, labels)
1278
- case Own(rt ) : return [Value(' i32' , lower_own(cx, v, rt ))]
1279
- case Borrow(rt ) : return [Value(' i32' , lower_borrow(cx, v, rt ))]
1282
+ case Own() : return [Value(' i32' , lower_own(cx, v, t ))]
1283
+ case Borrow() : return [Value(' i32' , lower_borrow(cx, v, t ))]
1280
1284
```
1281
1285
1282
1286
Since component-level values are assumed in-range and, as previously stated,
@@ -1452,7 +1456,7 @@ def canon_lift(opts, inst, callee, ft, args):
1452
1456
def post_return ():
1453
1457
if opts.post_return is not None :
1454
1458
opts.post_return(flat_results)
1455
- cx.borrow_scope.exit( )
1459
+ trap_if( cx.borrow_count != 0 )
1456
1460
1457
1461
return (results, post_return)
1458
1462
```
@@ -1505,6 +1509,9 @@ def canon_lower(opts, inst, callee, calling_import, ft, flat_args):
1505
1509
1506
1510
post_return()
1507
1511
1512
+ for h in cx.lenders:
1513
+ h.lend_count -= 1
1514
+
1508
1515
if calling_import:
1509
1516
inst.may_enter = True
1510
1517
@@ -1566,11 +1573,11 @@ validation specifies:
1566
1573
currently fixed to be ` i32 ` .
1567
1574
1568
1575
Calling ` $f ` invokes the following function, which creates a resource object
1569
- and inserts it into the current instance's handle table:
1576
+ and adds it into the current instance's handle table:
1570
1577
``` python
1571
1578
def canon_resource_new (inst , rt , rep ):
1572
1579
h = OwnHandle(rep, 0 )
1573
- return inst.handles.insert (h, rt )
1580
+ return inst.handles.add (h, Own(rt) )
1574
1581
```
1575
1582
1576
1583
### ` canon resource.drop `
@@ -1588,10 +1595,7 @@ be of type `$t` from the handle table and then, for an `own` handle, calls the
1588
1595
optional destructor.
1589
1596
``` python
1590
1597
def canon_resource_drop (inst , t , i ):
1591
- h = inst.handles.remove(i, t)
1592
- if isinstance (t, Own) and t.rt.dtor:
1593
- trap_if(not t.rt.impl.may_enter)
1594
- t.rt.dtor(h.rep)
1598
+ inst.handles.drop(i, t)
1595
1599
```
1596
1600
The ` may_enter ` guard ensures the non-reentrance [ component invariant] , since
1597
1601
a destructor call is analogous to a call to an export.
@@ -1613,7 +1617,8 @@ matches. Note that the "locally-defined" requirement above ensures that only
1613
1617
the component instance defining a resource can access its representation.
1614
1618
``` python
1615
1619
def canon_resource_rep (inst , rt , i ):
1616
- return inst.handles.get(i, rt).rep
1620
+ h = inst.handles.get(i, rt)
1621
+ return h.rep
1617
1622
```
1618
1623
1619
1624
0 commit comments