Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reserve handle index 0 and add handle index max in Canonical ABI #284

Merged
merged 1 commit into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions design/mvp/CanonicalABI.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ class HandleTable:
free: [int]

def __init__(self):
self.array = []
self.array = [None]
self.free = []

def get(self, i):
Expand All @@ -361,7 +361,9 @@ class HandleTable:
The `HandleTable` class maintains a dense array of handles that can contain
holes created by the `remove` method (defined below). When handles are accessed
(by lifting and `resource.rep`), there are thus both a bounds check and hole
check necessary.
check necessary. Upon initialization, table element `0` is allocated and set to
`None`, effectively reserving index `0` which is both useful for catching
null/uninitialized accesses and allowing `0` to serve as a sentinel value.

The `add` and `remove` methods work together to maintain a free list of holes
that are used in preference to growing the table. The free list is represented
Expand All @@ -375,6 +377,7 @@ free list in the free elements of `array`.
self.array[i] = h
else:
i = len(self.array)
trap_if(i >= 2**30)
self.array.append(h)
return i

Expand All @@ -384,6 +387,9 @@ free list in the free elements of `array`.
self.free.append(i)
return h
```
The handle index limit of `2**20` ensures that the high 2 bits of handle
indices are unset and available for other use in guest code (e.g., for tagging,
packed words or sentinel values).

Finally, we can define `HandleTables` (plural) as simply a wrapper around
a mutable mapping from `ResourceType` to `HandleTable`:
Expand Down
3 changes: 2 additions & 1 deletion design/mvp/canonical-abi/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ class HandleTable:
free: [int]

def __init__(self):
self.array = []
self.array = [None]
self.free = []

def get(self, i):
Expand All @@ -351,6 +351,7 @@ def add(self, h):
self.array[i] = h
else:
i = len(self.array)
trap_if(i >= 2**30)
self.array.append(h)
return i

Expand Down
46 changes: 24 additions & 22 deletions design/mvp/canonical-abi/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,15 @@ def core_wasm(args):
nonlocal dtor_value

assert(len(args) == 4)
assert(args[0].t == 'i32' and args[0].v == 0)
assert(args[1].t == 'i32' and args[1].v == 1)
assert(args[2].t == 'i32' and args[2].v == 2)
assert(len(inst.handles.table(rt).array) == 4)
assert(inst.handles.table(rt).array[0] is None)
assert(args[0].t == 'i32' and args[0].v == 1)
assert(args[1].t == 'i32' and args[1].v == 2)
assert(args[2].t == 'i32' and args[2].v == 3)
assert(args[3].t == 'i32' and args[3].v == 13)
assert(canon_resource_rep(inst, rt, 0) == 42)
assert(canon_resource_rep(inst, rt, 1) == 43)
assert(canon_resource_rep(inst, rt, 2) == 44)
assert(canon_resource_rep(inst, rt, 1) == 42)
assert(canon_resource_rep(inst, rt, 2) == 43)
assert(canon_resource_rep(inst, rt, 3) == 44)

host_ft = FuncType([
Borrow(rt),
Expand All @@ -405,35 +407,35 @@ def core_wasm(args):
Own(rt)
])
args = [
Value('i32',0),
Value('i32',2)
Value('i32',1),
Value('i32',3)
]
results = canon_lower(opts, inst, host_import, True, host_ft, args)
assert(len(results) == 1)
assert(results[0].t == 'i32' and results[0].v == 3)
assert(canon_resource_rep(inst, rt, 3) == 45)
assert(results[0].t == 'i32' and results[0].v == 4)
assert(canon_resource_rep(inst, rt, 4) == 45)

dtor_value = None
canon_resource_drop(inst, rt, 0)
canon_resource_drop(inst, rt, 1)
assert(dtor_value == 42)
assert(len(inst.handles.table(rt).array) == 4)
assert(inst.handles.table(rt).array[0] is None)
assert(len(inst.handles.table(rt).array) == 5)
assert(inst.handles.table(rt).array[1] is None)
assert(len(inst.handles.table(rt).free) == 1)

h = canon_resource_new(inst, rt, 46)
assert(h == 0)
assert(len(inst.handles.table(rt).array) == 4)
assert(inst.handles.table(rt).array[0] is not None)
assert(h == 1)
assert(len(inst.handles.table(rt).array) == 5)
assert(inst.handles.table(rt).array[1] is not None)
assert(len(inst.handles.table(rt).free) == 0)

dtor_value = None
canon_resource_drop(inst, rt, 2)
canon_resource_drop(inst, rt, 3)
assert(dtor_value is None)
assert(len(inst.handles.table(rt).array) == 4)
assert(inst.handles.table(rt).array[2] is None)
assert(len(inst.handles.table(rt).array) == 5)
assert(inst.handles.table(rt).array[3] is None)
assert(len(inst.handles.table(rt).free) == 1)

return [Value('i32', 0), Value('i32', 1), Value('i32', 3)]
return [Value('i32', 1), Value('i32', 2), Value('i32', 4)]

ft = FuncType([
Own(rt),
Expand All @@ -457,8 +459,8 @@ def core_wasm(args):
assert(got[0] == 46)
assert(got[1] == 43)
assert(got[2] == 45)
assert(len(inst.handles.table(rt).array) == 4)
assert(all(inst.handles.table(rt).array[i] is None for i in range(3)))
assert(len(inst.handles.table(rt).array) == 5)
assert(all(inst.handles.table(rt).array[i] is None for i in range(4)))
assert(len(inst.handles.table(rt).free) == 4)
definitions.MAX_FLAT_RESULTS = before

Expand Down