Skip to content

Commit 15fd263

Browse files
committed
Refactor CABI Python code to generalize HandleTable into Table[HandleElem]; no change in behavior
1 parent 34dfef3 commit 15fd263

File tree

2 files changed

+127
-128
lines changed

2 files changed

+127
-128
lines changed

design/mvp/CanonicalABI.md

Lines changed: 89 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ def alignment_flags(labels):
152152
return 4
153153
```
154154

155-
Handle types are passed as `i32` indices into the `HandleTable` introduced
156-
below.
155+
Handle types are passed as `i32` indices into the `Table[HandleElem]`
156+
introduced below.
157157

158158

159159
### Size
@@ -286,12 +286,41 @@ class ComponentInstance:
286286
self.may_enter = True
287287
self.handles = HandleTables()
288288
```
289-
`HandleTables` is defined in terms of a collection of supporting runtime
290-
bookkeeping classes that we'll go through first.
289+
The `HandleTables` object held by `ComponentInstance` contains a mapping
290+
from `ResourceType` to `Table`s of `HandleElem`s (defined next), establishing
291+
a separate `i32`-indexed array per resource type.
292+
```python
293+
class HandleTables:
294+
rt_to_table: MutableMapping[ResourceType, Table[HandleElem]]
291295

292-
The `ResourceType` class represents a resource type that has been defined by
293-
the specific component instance pointed to by `impl` with a particular
294-
function closure as the `dtor`.
296+
def __init__(self):
297+
self.rt_to_table = dict()
298+
299+
def table(self, rt):
300+
if rt not in self.rt_to_table:
301+
self.rt_to_table[rt] = Table[HandleElem]()
302+
return self.rt_to_table[rt]
303+
304+
def get(self, rt, i):
305+
return self.table(rt).get(i)
306+
def add(self, rt, h):
307+
return self.table(rt).add(h)
308+
def remove(self, rt, i):
309+
return self.table(rt).remove(i)
310+
```
311+
While this Python code performs a dynamic hash-table lookup on each handle
312+
table access, as we'll see below, the `rt` parameter is always statically known
313+
such that a normal implementation can statically enumerate all `Table` objects
314+
at compile time and then route the calls to `get`, `add` and `remove` to the
315+
correct `Table` at the callsite. The net result is that each component instance
316+
will contain one handle table per resource type used by the component, with
317+
each compiled adapter function accessing the correct handle table as-if it were
318+
a global variable.
319+
320+
The `ResourceType` class represents a concrete resource type that has been
321+
created by the component instance `impl`. `ResourceType` objects are used as
322+
keys by `HandleTables` above and thus we assume that Python object identity
323+
corresponds to resource type equality, as defined by [type checking] rules.
295324
```python
296325
class ResourceType(Type):
297326
impl: ComponentInstance
@@ -301,9 +330,60 @@ class ResourceType(Type):
301330
self.impl = impl
302331
self.dtor = dtor
303332
```
333+
The `Table` class, used by `HandleTables` above, encapsulates a single
334+
mutable, growable array of generic elements, indexed by Core WebAssembly
335+
`i32`s.
336+
```python
337+
ElemT = TypeVar('ElemT')
338+
class Table(Generic[ElemT]):
339+
array: list[Optional[ElemT]]
340+
free: list[int]
304341

305-
The `HandleElem` class represents the elements of the per-component-instance
306-
handle tables (defined next).
342+
def __init__(self):
343+
self.array = [None]
344+
self.free = []
345+
346+
def get(self, i):
347+
trap_if(i >= len(self.array))
348+
trap_if(self.array[i] is None)
349+
return self.array[i]
350+
351+
def add(self, e):
352+
if self.free:
353+
i = self.free.pop()
354+
assert(self.array[i] is None)
355+
self.array[i] = e
356+
else:
357+
i = len(self.array)
358+
trap_if(i >= 2**30)
359+
self.array.append(e)
360+
return i
361+
362+
def remove(self, i):
363+
e = self.get(i)
364+
self.array[i] = None
365+
self.free.append(i)
366+
return e
367+
```
368+
`Table` maintains a dense array of elements that can contain holes created by
369+
the `remove` method (defined below). When table elements are accessed (e.g., by
370+
`canon_lift` and `resource.rep`, below), there are thus both a bounds check and
371+
hole check necessary. Upon initialization, table element `0` is allocated and
372+
set to `None`, effectively reserving index `0` which is both useful for
373+
catching null/uninitialized accesses and allowing `0` to serve as a sentinel
374+
value.
375+
376+
The `add` and `remove` methods work together to maintain a free list of holes
377+
that are used in preference to growing the table. The free list is represented
378+
as a Python list here, but an optimizing implementation could instead store the
379+
free list in the free elements of `array`.
380+
381+
The limit of `2**30` ensures that the high 2 bits of table indices are unset
382+
and available for other use in guest code (e.g., for tagging, packed words or
383+
sentinel values).
384+
385+
The `HandleElem` class defines the elements of the per-resource-type `Table`s
386+
stored in `HandleTables`:
307387
```python
308388
class HandleElem:
309389
rep: int
@@ -340,86 +420,6 @@ table only contains `own` or `borrow` handles and then, based on this,
340420
statically eliminate the `own` and the `lend_count` xor `scope` fields,
341421
and guards thereof.
342422

343-
`HandleTable` (singular) encapsulates a single mutable, growable array
344-
of handles that all share the same `ResourceType`. Defining `HandleTable` in
345-
chunks, we start with the fields and `get` method:
346-
```python
347-
class HandleTable:
348-
array: list[Optional[HandleElem]]
349-
free: list[int]
350-
351-
def __init__(self):
352-
self.array = [None]
353-
self.free = []
354-
355-
def get(self, i):
356-
trap_if(i >= len(self.array))
357-
trap_if(self.array[i] is None)
358-
return self.array[i]
359-
```
360-
The `HandleTable` class maintains a dense array of handles that can contain
361-
holes created by the `remove` method (defined below). When handles are accessed
362-
(by lifting and `resource.rep`), there are thus both a bounds check and hole
363-
check necessary. Upon initialization, table element `0` is allocated and set to
364-
`None`, effectively reserving index `0` which is both useful for catching
365-
null/uninitialized accesses and allowing `0` to serve as a sentinel value.
366-
367-
The `add` and `remove` methods work together to maintain a free list of holes
368-
that are used in preference to growing the table. The free list is represented
369-
as a Python list here, but an optimizing implementation could instead store the
370-
free list in the free elements of `array`.
371-
```python
372-
def add(self, h):
373-
if self.free:
374-
i = self.free.pop()
375-
assert(self.array[i] is None)
376-
self.array[i] = h
377-
else:
378-
i = len(self.array)
379-
trap_if(i >= 2**30)
380-
self.array.append(h)
381-
return i
382-
383-
def remove(self, i):
384-
h = self.get(i)
385-
self.array[i] = None
386-
self.free.append(i)
387-
return h
388-
```
389-
The handle index limit of `2**30` ensures that the high 2 bits of handle
390-
indices are unset and available for other use in guest code (e.g., for tagging,
391-
packed words or sentinel values).
392-
393-
Finally, we can define `HandleTables` (plural) as simply a wrapper around
394-
a mutable mapping from `ResourceType` to `HandleTable`:
395-
```python
396-
class HandleTables:
397-
rt_to_table: MutableMapping[ResourceType, HandleTable]
398-
399-
def __init__(self):
400-
self.rt_to_table = dict()
401-
402-
def table(self, rt):
403-
if rt not in self.rt_to_table:
404-
self.rt_to_table[rt] = HandleTable()
405-
return self.rt_to_table[rt]
406-
407-
def get(self, rt, i):
408-
return self.table(rt).get(i)
409-
def add(self, rt, h):
410-
return self.table(rt).add(h)
411-
def remove(self, rt, i):
412-
return self.table(rt).remove(i)
413-
```
414-
While this Python code performs a dynamic hash-table lookup on each handle
415-
table access, as we'll see below, the `rt` parameter is always statically
416-
known such that a normal implementation can statically enumerate all
417-
`HandleTable` objects at compile time and then route the calls to `get`,
418-
`add` and `remove` to the correct `HandleTable` at the callsite. The
419-
net result is that each component instance will contain one handle table per
420-
resource type used by the component, with each compiled adapter function
421-
accessing the correct handle table as-if it were a global variable.
422-
423423

424424
### Loading
425425

design/mvp/canonical-abi/definitions.py

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
### Boilerplate
66

77
from __future__ import annotations
8+
from dataclasses import dataclass
9+
from typing import Optional, Callable, MutableMapping, TypeVar, Generic
810
import math
911
import struct
1012
import random
11-
from dataclasses import dataclass
12-
from typing import Optional
13-
from typing import Callable
14-
from typing import MutableMapping
1513

1614
class Trap(BaseException): pass
1715
class CoreWebAssemblyException(BaseException): pass
@@ -311,6 +309,24 @@ def __init__(self):
311309
self.may_enter = True
312310
self.handles = HandleTables()
313311

312+
class HandleTables:
313+
rt_to_table: MutableMapping[ResourceType, Table[HandleElem]]
314+
315+
def __init__(self):
316+
self.rt_to_table = dict()
317+
318+
def table(self, rt):
319+
if rt not in self.rt_to_table:
320+
self.rt_to_table[rt] = Table[HandleElem]()
321+
return self.rt_to_table[rt]
322+
323+
def get(self, rt, i):
324+
return self.table(rt).get(i)
325+
def add(self, rt, h):
326+
return self.table(rt).add(h)
327+
def remove(self, rt, i):
328+
return self.table(rt).remove(i)
329+
314330
class ResourceType(Type):
315331
impl: ComponentInstance
316332
dtor: Optional[Callable[[int],None]]
@@ -319,20 +335,9 @@ def __init__(self, impl, dtor = None):
319335
self.impl = impl
320336
self.dtor = dtor
321337

322-
class HandleElem:
323-
rep: int
324-
own: bool
325-
scope: Optional[CallContext]
326-
lend_count: int
327-
328-
def __init__(self, rep, own, scope = None):
329-
self.rep = rep
330-
self.own = own
331-
self.scope = scope
332-
self.lend_count = 0
333-
334-
class HandleTable:
335-
array: list[Optional[HandleElem]]
338+
ElemT = TypeVar('ElemT')
339+
class Table(Generic[ElemT]):
340+
array: list[Optional[ElemT]]
336341
free: list[int]
337342

338343
def __init__(self):
@@ -344,40 +349,34 @@ def get(self, i):
344349
trap_if(self.array[i] is None)
345350
return self.array[i]
346351

347-
def add(self, h):
352+
def add(self, e):
348353
if self.free:
349354
i = self.free.pop()
350355
assert(self.array[i] is None)
351-
self.array[i] = h
356+
self.array[i] = e
352357
else:
353358
i = len(self.array)
354359
trap_if(i >= 2**30)
355-
self.array.append(h)
360+
self.array.append(e)
356361
return i
357362

358363
def remove(self, i):
359-
h = self.get(i)
364+
e = self.get(i)
360365
self.array[i] = None
361366
self.free.append(i)
362-
return h
363-
364-
class HandleTables:
365-
rt_to_table: MutableMapping[ResourceType, HandleTable]
366-
367-
def __init__(self):
368-
self.rt_to_table = dict()
367+
return e
369368

370-
def table(self, rt):
371-
if rt not in self.rt_to_table:
372-
self.rt_to_table[rt] = HandleTable()
373-
return self.rt_to_table[rt]
369+
class HandleElem:
370+
rep: int
371+
own: bool
372+
scope: Optional[CallContext]
373+
lend_count: int
374374

375-
def get(self, rt, i):
376-
return self.table(rt).get(i)
377-
def add(self, rt, h):
378-
return self.table(rt).add(h)
379-
def remove(self, rt, i):
380-
return self.table(rt).remove(i)
375+
def __init__(self, rep, own, scope = None):
376+
self.rep = rep
377+
self.own = own
378+
self.scope = scope
379+
self.lend_count = 0
381380

382381
### Loading
383382

0 commit comments

Comments
 (0)