Skip to content

Commit

Permalink
Reorder Context definitions to present top-down
Browse files Browse the repository at this point in the history
  • Loading branch information
lukewagner committed Dec 4, 2022
1 parent 8d89078 commit f5d9ee2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 73 deletions.
90 changes: 48 additions & 42 deletions design/mvp/CanonicalABI.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,31 +210,39 @@ The subsequent definitions of loading and storing a value from linear memory
require additional context, which is threaded through most subsequent
definitions via the `cx` parameter:
```python
class Context:
opts: CanonicalOptions
inst: ComponentInstance
called_as_export: bool
```

The `opts` field represents the [`canonopt`] values supplied to
currently-executing `canon lift` or `canon lower`:
```python
class CanonicalOptions:
memory: bytearray
string_encoding: str
realloc: Callable[[int,int,int,int],int]
post_return: Callable[[],None]
```

The `inst` field represents the component instance that the currently-executing
canonical definition is defined to execute inside. The `may_enter` and
`may_leave` fields are used to enforce the [component invariants]: `may_leave`
indicates whether the instance may call out to an import and the `may_enter`
state indicates whether the instance may be called from the outside world
through an export.
```python
class ComponentInstance:
may_leave = True
may_enter = True
# ...

class Context:
opts: CanonicalOptions
inst: ComponentInstance
```
Going through the fields of `Context`:

The `opts` field represents the [`canonopt`] values supplied to
currently-executing `canon lift` or `canon lower`.

The `inst` field represents the component instance that the currently-executing
canonical definition is closed over. The `may_enter` and `may_leave` fields are
used to enforce the [component invariants]: `may_leave` indicates whether the
instance may call out to an import and the `may_enter` state indicates whether
the instance may be called from the outside world through an export.
Lastly, the `called_as_export` field indicates whether the lifted function is
being called through a component export or whether this is an internal call,
(for example, when a child component calls an import that is defined by its
parent component).


### Loading
Expand Down Expand Up @@ -1204,29 +1212,29 @@ component*.

Given the above closure arguments, `canon_lift` is defined:
```python
def canon_lift(callee_cx, callee, ft, args, called_as_export):
if called_as_export:
trap_if(not callee_cx.inst.may_enter)
callee_cx.inst.may_enter = False
def canon_lift(cx, callee, ft, args):
if cx.called_as_export:
trap_if(not cx.inst.may_enter)
cx.inst.may_enter = False
else:
assert(not callee_cx.inst.may_enter)
assert(not cx.inst.may_enter)

assert(callee_cx.inst.may_leave)
callee_cx.inst.may_leave = False
flat_args = lower_values(callee_cx, MAX_FLAT_PARAMS, args, ft.param_types())
callee_cx.inst.may_leave = True
assert(cx.inst.may_leave)
cx.inst.may_leave = False
flat_args = lower_values(cx, MAX_FLAT_PARAMS, args, ft.param_types())
cx.inst.may_leave = True

try:
flat_results = callee(flat_args)
except CoreWebAssemblyException:
trap()

results = lift_values(callee_cx, MAX_FLAT_RESULTS, ValueIter(flat_results), ft.result_types())
results = lift_values(cx, MAX_FLAT_RESULTS, ValueIter(flat_results), ft.result_types())
def post_return():
if callee_cx.opts.post_return is not None:
callee_cx.opts.post_return(flat_results)
if called_as_export:
callee_cx.inst.may_enter = True
if cx.opts.post_return is not None:
cx.opts.post_return(flat_results)
if cx.called_as_export:
cx.inst.may_enter = True

return (results, post_return)
```
Expand All @@ -1237,15 +1245,13 @@ boundaries. Thus, if a component wishes to signal an error, it must use some
sort of explicit type such as `result` (whose `error` case particular language
bindings may choose to map to and from exceptions).

The `called_as_export` parameter indicates whether `canon_lift` is being called
as part of a component export or whether this `canon_lift` is being called
internally (for example, by a child component instance). By clearing
`may_enter` for the duration of `canon_lift` when called as an export, the
dynamic traps ensure that components cannot be reentered, which is a [component
invariant]. Furthermore, because `may_enter` is not cleared on the exceptional
exit path taken by `trap()`, if there is a trap during Core WebAssembly
execution or lifting/lowering, the component is left permanently un-enterable,
ensuring the lockdown-after-trap [component invariant].
By clearing `may_enter` for the duration of `canon_lift` when the function is
called as an export, the dynamic traps ensure that components cannot be
reentered, ensuring the non-reentrance [component invariant]. Furthermore,
because `may_enter` is not cleared on the exceptional exit path taken by
`trap()`, if there is a trap during Core WebAssembly execution of lifting or
lowering, the component is left permanently un-enterable, ensuring the
lockdown-after-trap [component invariant].

The contract assumed by `canon_lift` (and ensured by `canon_lower` below) is
that the caller of `canon_lift` *must* call `post_return` right after lowering
Expand All @@ -1272,17 +1278,17 @@ Thus, from the perspective of Core WebAssembly, `$f` is a [function instance]
containing a `hostfunc` that closes over `$opts`, `$inst`, `$callee` and `$ft`
and, when called from Core WebAssembly code, calls `canon_lower`, which is defined as:
```python
def canon_lower(caller_cx, callee, ft, flat_args):
trap_if(not caller_cx.inst.may_leave)
def canon_lower(cx, callee, ft, flat_args):
trap_if(not cx.inst.may_leave)

flat_args = ValueIter(flat_args)
args = lift_values(caller_cx, MAX_FLAT_PARAMS, flat_args, ft.param_types())
args = lift_values(cx, MAX_FLAT_PARAMS, flat_args, ft.param_types())

results, post_return = callee(args)

caller_cx.inst.may_leave = False
flat_results = lower_values(caller_cx, MAX_FLAT_RESULTS, results, ft.result_types(), flat_args)
caller_cx.inst.may_leave = True
cx.inst.may_leave = False
flat_results = lower_values(cx, MAX_FLAT_RESULTS, results, ft.result_types(), flat_args)
cx.inst.may_leave = True

post_return()

Expand Down
54 changes: 30 additions & 24 deletions design/mvp/canonical-abi/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Boilerplate

from __future__ import annotations
import math
import struct
from dataclasses import dataclass
Expand Down Expand Up @@ -271,21 +272,26 @@ def num_i32_flags(labels):

### Context

class Context:
opts: CanonicalOptions
inst: ComponentInstance
called_as_export: bool

#

class CanonicalOptions:
memory: bytearray
string_encoding: str
realloc: Callable[[int,int,int,int],int]
post_return: Callable[[],None]

#

class ComponentInstance:
may_leave = True
may_enter = True
# ...

class Context:
opts: CanonicalOptions
inst: ComponentInstance

### Loading

def load(cx, ptr, t):
Expand Down Expand Up @@ -1002,45 +1008,45 @@ def lower_values(cx, max_flat, vs, ts, out_param = None):

### `lift`

def canon_lift(callee_cx, callee, ft, args, called_as_export):
if called_as_export:
trap_if(not callee_cx.inst.may_enter)
callee_cx.inst.may_enter = False
def canon_lift(cx, callee, ft, args):
if cx.called_as_export:
trap_if(not cx.inst.may_enter)
cx.inst.may_enter = False
else:
assert(not callee_cx.inst.may_enter)
assert(not cx.inst.may_enter)

assert(callee_cx.inst.may_leave)
callee_cx.inst.may_leave = False
flat_args = lower_values(callee_cx, MAX_FLAT_PARAMS, args, ft.param_types())
callee_cx.inst.may_leave = True
assert(cx.inst.may_leave)
cx.inst.may_leave = False
flat_args = lower_values(cx, MAX_FLAT_PARAMS, args, ft.param_types())
cx.inst.may_leave = True

try:
flat_results = callee(flat_args)
except CoreWebAssemblyException:
trap()

results = lift_values(callee_cx, MAX_FLAT_RESULTS, ValueIter(flat_results), ft.result_types())
results = lift_values(cx, MAX_FLAT_RESULTS, ValueIter(flat_results), ft.result_types())
def post_return():
if callee_cx.opts.post_return is not None:
callee_cx.opts.post_return(flat_results)
if called_as_export:
callee_cx.inst.may_enter = True
if cx.opts.post_return is not None:
cx.opts.post_return(flat_results)
if cx.called_as_export:
cx.inst.may_enter = True

return (results, post_return)

### `lower`

def canon_lower(caller_cx, callee, ft, flat_args):
trap_if(not caller_cx.inst.may_leave)
def canon_lower(cx, callee, ft, flat_args):
trap_if(not cx.inst.may_leave)

flat_args = ValueIter(flat_args)
args = lift_values(caller_cx, MAX_FLAT_PARAMS, flat_args, ft.param_types())
args = lift_values(cx, MAX_FLAT_PARAMS, flat_args, ft.param_types())

results, post_return = callee(args)

caller_cx.inst.may_leave = False
flat_results = lower_values(caller_cx, MAX_FLAT_RESULTS, results, ft.result_types(), flat_args)
caller_cx.inst.may_leave = True
cx.inst.may_leave = False
flat_results = lower_values(cx, MAX_FLAT_RESULTS, results, ft.result_types(), flat_args)
cx.inst.may_leave = True

post_return()

Expand Down
15 changes: 8 additions & 7 deletions design/mvp/canonical-abi/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ def realloc(self, original_ptr, original_size, alignment, new_size):
self.memory[ret : ret + original_size] = self.memory[original_ptr : original_ptr + original_size]
return ret

def mk_cx(memory, encoding = None, realloc = None, post_return = None):
def mk_cx(memory = bytearray(), encoding = 'utf8', realloc = None, post_return = None):
cx = Context()
cx.opts = CanonicalOptions()
cx.opts.memory = memory
cx.opts.string_encoding = encoding
cx.opts.realloc = realloc
cx.opts.post_return = post_return
cx.inst = ComponentInstance()
cx.called_as_export = True
return cx

def mk_str(s):
Expand All @@ -56,7 +57,7 @@ def fail(msg):
raise BaseException(msg)

def test(t, vals_to_lift, v,
cx = mk_cx(bytearray(), 'utf8', None, None),
cx = mk_cx(),
dst_encoding = None,
lower_t = None,
lower_v = None):
Expand Down Expand Up @@ -85,7 +86,7 @@ def test_name():
heap = Heap(5*len(cx.opts.memory))
if dst_encoding is None:
dst_encoding = cx.opts.string_encoding
cx = mk_cx(heap.memory, dst_encoding, heap.realloc, None)
cx = mk_cx(heap.memory, dst_encoding, heap.realloc)
lowered_vals = lower_flat(cx, v, lower_t)
assert(flatten_type(lower_t) == list(map(lambda v: v.t, lowered_vals)))

Expand Down Expand Up @@ -200,7 +201,7 @@ def test_nan64(inbits, outbits):
def test_string_internal(src_encoding, dst_encoding, s, encoded, tagged_code_units):
heap = Heap(len(encoded))
heap.memory[:] = encoded[:]
cx = mk_cx(heap.memory, src_encoding, None, None)
cx = mk_cx(heap.memory, src_encoding)
v = (s, src_encoding, tagged_code_units)
test(String(), [0, tagged_code_units], v, cx, dst_encoding)

Expand Down Expand Up @@ -237,7 +238,7 @@ def test_string(src_encoding, dst_encoding, s):

def test_heap(t, expect, args, byte_array):
heap = Heap(byte_array)
cx = mk_cx(heap.memory, 'utf8', None, None)
cx = mk_cx(heap.memory)
test(t, args, expect, cx)

test_heap(List(Record([])), [{},{},{}], [0,3], [])
Expand Down Expand Up @@ -348,10 +349,10 @@ def test_roundtrip(t, v):

callee_heap = Heap(1000)
callee_cx = mk_cx(callee_heap.memory, 'utf8', callee_heap.realloc, lambda x: () )
lifted_callee = lambda args: canon_lift(callee_cx, callee, ft, args, True)
lifted_callee = lambda args: canon_lift(callee_cx, callee, ft, args)

caller_heap = Heap(1000)
caller_cx = mk_cx(caller_heap.memory, 'utf8', caller_heap.realloc, None)
caller_cx = mk_cx(caller_heap.memory, 'utf8', caller_heap.realloc)

flat_args = lower_flat(caller_cx, v, t)
flat_results = canon_lower(caller_cx, lifted_callee, ft, flat_args)
Expand Down

0 comments on commit f5d9ee2

Please sign in to comment.