Skip to content

Commit 4c0ce2b

Browse files
authored
Merge pull request #147 from WebAssembly/fix-dynamic-cast
Move resource type tag to handle
2 parents f65f4ac + a78eaba commit 4c0ce2b

File tree

4 files changed

+50
-41
lines changed

4 files changed

+50
-41
lines changed

design/mvp/CanonicalABI.md

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -258,40 +258,38 @@ defined by its parent component).
258258
The `HandleTable` class is defined in terms of a collection of supporting
259259
runtime bookkeeping classes that we'll go through first.
260260

261-
The `Resource` class represents a runtime instance of a resource type:
261+
The `Resource` class represents a runtime instance of a resource type, storing
262+
the core representation value (which is currently fixed to `i32`):
262263
```python
264+
@dataclass
263265
class Resource:
264-
t: ResourceType
265266
rep: int
266-
267-
def __init__(self, t, rep):
268-
self.t = t
269-
self.rep = rep
270267
```
271-
The `t` field points to the [`resourcetype`](Explainer.md#type-definitions)
272-
used to create this `Resource` and is used to perform dynamic type checking
273-
below. The `rep` field stores the representation value of this `Resource` whose
274-
type is currently fixed to `i32` as described in the Explainer.
275268

276269
The `OwnHandle` and `BorrowHandle` classes represent runtime handle values of
277270
`own` and `borrow` type, resp:
278271
```python
279272
class Handle:
280273
resource: Resource
274+
rt: ResourceType
281275
lend_count: int
282276

283-
def __init__(self, resource):
277+
def __init__(self, resource, rt):
284278
self.resource = resource
279+
self.rt = rt
285280
self.lend_count = 0
286281

287282
class OwnHandle(Handle): pass
288283
class BorrowHandle(Handle): pass
289284
```
290285
The `resource` field points to the resource instance this handle refers to. The
291-
`lend_count` field maintains a count of the outstanding handles that were lent
292-
from this handle (by calls to `borrow`-taking functions). This count is used
293-
below to dynamically enforce the invariant that a handle cannot be dropped
294-
while it has currently lent out a `borrow`.
286+
`rt` field points to a runtime value representing the static
287+
[`resourcetype`](Explainer.md#type-definitions) of this handle and is used by
288+
dynamic type checking below. Lastly, the `lend_count` field maintains a count
289+
of the outstanding handles that were lent from this handle (by calls to
290+
`borrow`-taking functions). This count is used below to dynamically enforce the
291+
invariant that a handle cannot be dropped while it has currently lent out a
292+
`borrow`.
295293

296294
The `Call` class represents a single runtime call (activation) of a
297295
component-level function. A `Call` is finished in two steps by `finish_lift`
@@ -368,13 +366,16 @@ use-after-free:
368366
def get(self, i, rt):
369367
trap_if(i >= len(self.array))
370368
trap_if(self.array[i] is None)
371-
trap_if(self.array[i].resource.t is not rt)
369+
trap_if(self.array[i].rt is not rt)
372370
return self.array[i]
373371
```
374372
Additionally, the `get` method takes the runtime resource type tag and checks
375-
a tag match before returning the handle with this new-valid resource type. Note
376-
that this is a non-structural, pointer-equality-based check to implement the
377-
[type-checking rules](Explainer.md) of resource types.
373+
a tag match before returning the handle with this new-valid resource type. This
374+
check is a non-structural, pointer-equality-based test used to enforce the
375+
[type-checking rules](Explainer.md) of resource types at runtime. Importantly,
376+
this check keeps type imports abstract, considering each type import to have a
377+
unique `rt` value distinct from every other type import even if the two imports
378+
happen to be instantiated with the same resource type at runtime.
378379

379380
The `lend` method is called when borrowing a handle. `lend` uses `Call.lenders`
380381
to decrement the handle's `lend_count` at the end of the call.
@@ -978,16 +979,18 @@ Finally, `own` and `borrow` handles are lowered by inserting them into the
978979
current component instance's `HandleTable`:
979980
```python
980981
def lower_own(cx, resource, rt):
981-
assert(resource.t is rt)
982-
h = OwnHandle(resource)
982+
h = OwnHandle(resource, rt)
983983
return cx.inst.handles.insert(cx, h)
984984

985985
def lower_borrow(cx, resource, rt):
986-
assert(resource.t is rt)
987-
h = BorrowHandle(resource)
986+
h = BorrowHandle(resource, rt)
988987
return cx.inst.handles.insert(cx, h)
989988
```
990-
The assertions are ensured by validation.
989+
Note that the `rt` value that is stored in the runtime `Handle` captures what
990+
is statically known about the handle right before losing this information in
991+
the homogeneous `HandleTable`. Moreoever, as described above, distinct type
992+
imports are given distinct `rt` values so that handles produced by lowering
993+
different type imports are never interchangeable.
991994

992995
### Flattening
993996

@@ -1564,7 +1567,7 @@ Calling `$f` invokes the following function, which creates a resource object
15641567
and inserts it into the current instance's handle table:
15651568
```python
15661569
def canon_resource_new(cx, rt, rep):
1567-
h = OwnHandle(Resource(rt, rep))
1570+
h = OwnHandle(Resource(rep), rt)
15681571
return cx.inst.handles.insert(cx, h)
15691572
```
15701573

design/mvp/Explainer.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,14 @@ replaced by `$R` when validating the instantiations of `$c1` and `$c2`. These
995995
type-checking rules for instantiating type imports mirror the *elimination*
996996
rule of [universal types] (∀T).
997997

998+
Importantly, this type substitution performed by the parent is not visible to
999+
the child at validation- or run-time. In particular, the type checks performed
1000+
by the [Canonical ABI](CanonicalABI.md#context) use distinct type tags for
1001+
distinct type imports and associate type tags with *handles*, not the underlying
1002+
*resource*, leveraging the shared-nothing nature of components to type-tag handles
1003+
at component boundaries and avoid the usual [type-exposure problems with
1004+
dynamic casts][non-parametric parametricity].
1005+
9981006
In summary: all type constructors are *structural* with the exception of
9991007
`resource`, which is *abstract* and *generative*. Type imports and exports that
10001008
have a subtype bound also introduce abstract types and follow the standard
@@ -1816,8 +1824,10 @@ and will be added over the coming months to complete the MVP proposal:
18161824
[Subtyping]: https://en.wikipedia.org/wiki/Subtyping
18171825
[Universal Types]: https://en.wikipedia.org/wiki/System_F
18181826
[Existential Types]: https://en.wikipedia.org/wiki/System_F
1827+
18191828
[Generative]: https://www.researchgate.net/publication/2426300_A_Syntactic_Theory_of_Type_Generativity_and_Sharing
18201829
[Avoidance Problem]: https://counterexamples.org/avoidance.html
1830+
[Non-Parametric Parametricity]: https://people.mpi-sws.org/~dreyer/papers/npp/main.pdf
18211831

18221832
[URL Standard]: https://url.spec.whatwg.org
18231833
[URI]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier

design/mvp/canonical-abi/definitions.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -315,22 +315,20 @@ def __init__(self):
315315

316316
#
317317

318+
@dataclass
318319
class Resource:
319-
t: ResourceType
320320
rep: int
321321

322-
def __init__(self, t, rep):
323-
self.t = t
324-
self.rep = rep
325-
326322
#
327323

328324
class Handle:
329325
resource: Resource
326+
rt: ResourceType
330327
lend_count: int
331328

332-
def __init__(self, resource):
329+
def __init__(self, resource, rt):
333330
self.resource = resource
331+
self.rt = rt
334332
self.lend_count = 0
335333

336334
class OwnHandle(Handle): pass
@@ -381,7 +379,7 @@ def insert(self, cx, h):
381379
def get(self, i, rt):
382380
trap_if(i >= len(self.array))
383381
trap_if(self.array[i] is None)
384-
trap_if(self.array[i].resource.t is not rt)
382+
trap_if(self.array[i].rt is not rt)
385383
return self.array[i]
386384

387385
#
@@ -845,13 +843,11 @@ def pack_flags_into_int(v, labels):
845843
#
846844

847845
def lower_own(cx, resource, rt):
848-
assert(resource.t is rt)
849-
h = OwnHandle(resource)
846+
h = OwnHandle(resource, rt)
850847
return cx.inst.handles.insert(cx, h)
851848

852849
def lower_borrow(cx, resource, rt):
853-
assert(resource.t is rt)
854-
h = BorrowHandle(resource)
850+
h = BorrowHandle(resource, rt)
855851
return cx.inst.handles.insert(cx, h)
856852

857853
### Flattening
@@ -1215,7 +1211,7 @@ def canon_lower(cx, callee, ft, flat_args):
12151211
### `resource.new`
12161212

12171213
def canon_resource_new(cx, rt, rep):
1218-
h = OwnHandle(Resource(rt, rep))
1214+
h = OwnHandle(Resource(rep), rt)
12191215
return cx.inst.handles.insert(cx, h)
12201216

12211217
### `resource.drop`

design/mvp/canonical-abi/run_tests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,15 +381,15 @@ def dtor(x):
381381
nonlocal dtor_value
382382
dtor_value = x
383383
rt = ResourceType(dtor)
384-
r1 = Resource(rt, 42)
385-
r2 = Resource(rt, 43)
386-
r3 = Resource(rt, 44)
384+
r1 = Resource(42)
385+
r2 = Resource(43)
386+
r3 = Resource(44)
387387

388388
def host_import(act, args):
389389
assert(len(args) == 2)
390390
assert(args[0] is r1)
391391
assert(args[1] is r3)
392-
return ([Resource(rt, 45)], lambda:())
392+
return ([Resource(45)], lambda:())
393393

394394
cx = mk_cx()
395395
def core_wasm(args):

0 commit comments

Comments
 (0)