Skip to content

Commit 585716a

Browse files
committed
Define 'realloc' calls to execute on fresh thread
Resolves #678
1 parent 706074c commit 585716a

3 files changed

Lines changed: 175 additions & 43 deletions

File tree

design/mvp/CanonicalABI.md

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,49 @@ class LiftLowerContext:
12221222
The `borrow_scope` field may be `None` if the types being lifted/lowered are
12231223
known to not contain `borrow`.
12241224

1225+
The `LiftLowerContext.reallocate` and `LiftLowerContext.allocate` methods define
1226+
how to call a Core WebAssembly `LiftLowerOptions.realloc` function when lowering
1227+
a value that requires dynamic allocation. When one component imports and calls
1228+
another component which synchronously returns a value that requires dynamic
1229+
allocation, the callstack at the point where the `realloc` `canonopt` needs to
1230+
be called has the form:
1231+
```
1232+
... -> |component A| --calls-import--> |component B| --returns-value--> |component A realloc|
1233+
```
1234+
Thus, in general, `realloc` must be called reentrantly and so the call to
1235+
`realloc` is specified as if `realloc` were an exported function called without
1236+
the usual `ComponentInstance.may_enter_from` reentrance checks enforced by
1237+
`Store.lift`:
1238+
```python
1239+
def reallocate(self, old, old_byte_length, alignment, new_byte_length):
1240+
assert(self.inst.may_leave)
1241+
self.inst.may_leave = False
1242+
ptrt = U32Type() if self.opts.memory.ptr_type() == 'i32' else U64Type()
1243+
ft = FuncType([ptrt, ptrt, ptrt, ptrt], [ptrt], async_ = False)
1244+
opts = CanonicalOptions(async_ = False)
1245+
def on_start():
1246+
return [old, old_byte_length, alignment, new_byte_length]
1247+
ptr = None
1248+
def on_resolve(result):
1249+
nonlocal ptr
1250+
[ptr] = result
1251+
canon_lift(self.opts.realloc, ft, opts, self.inst, on_start, on_resolve)
1252+
assert(ptr is not None)
1253+
self.inst.may_leave = True
1254+
return ptr
1255+
1256+
def allocate(self, alignment, byte_length):
1257+
return self.reallocate(0, 0, alignment, byte_length)
1258+
```
1259+
As a consequence of the above definitions, calls to `realloc` semantically
1260+
execute in a new `Thread` with zero-initialized `Thread.storage` (so that
1261+
`context.get` returns `0` on entry to `realloc`). However, because the call is
1262+
fully synchronous and because all "interesting" threading built-ins that might
1263+
observably require a proper thread are guarded by `may_leave` traps, an
1264+
optimizing implementation may compile `realloc` calls to synchronous calls on
1265+
whatever internal thread structure is handy (being careful to set and restore
1266+
thread-local storage and the current component instance if necessary).
1267+
12251268

12261269
## Runtime State
12271270

@@ -2803,7 +2846,7 @@ byte after every Latin-1 byte).
28032846
def store_string_copy(cx, src, src_code_units, dst_code_unit_size, dst_alignment, dst_encoding):
28042847
dst_byte_length = dst_code_unit_size * src_code_units
28052848
assert(dst_byte_length <= REALLOC_I32_MAX)
2806-
ptr = cx.opts.realloc(0, 0, dst_alignment, dst_byte_length)
2849+
ptr = cx.allocate(dst_alignment, dst_byte_length)
28072850
trap_if(ptr != align_to(ptr, dst_alignment))
28082851
trap_if(ptr + dst_byte_length > len(cx.opts.memory))
28092852
encoded = src.encode(dst_encoding)
@@ -2826,19 +2869,19 @@ def store_latin1_to_utf8(cx, src, src_code_units):
28262869

28272870
def store_string_to_utf8(cx, src, src_code_units, worst_case_size):
28282871
assert(src_code_units <= REALLOC_I32_MAX)
2829-
ptr = cx.opts.realloc(0, 0, 1, src_code_units)
2872+
ptr = cx.allocate(1, src_code_units)
28302873
trap_if(ptr + src_code_units > len(cx.opts.memory))
28312874
for i,code_point in enumerate(src):
28322875
if ord(code_point) < 2**7:
28332876
cx.opts.memory[ptr + i] = ord(code_point)
28342877
else:
28352878
assert(worst_case_size <= REALLOC_I32_MAX)
2836-
ptr = cx.opts.realloc(ptr, src_code_units, 1, worst_case_size)
2879+
ptr = cx.reallocate(ptr, src_code_units, 1, worst_case_size)
28372880
trap_if(ptr + worst_case_size > len(cx.opts.memory))
28382881
encoded = src.encode('utf-8')
28392882
cx.opts.memory[ptr+i : ptr+len(encoded)] = encoded[i : ]
28402883
if worst_case_size > len(encoded):
2841-
ptr = cx.opts.realloc(ptr, worst_case_size, 1, len(encoded))
2884+
ptr = cx.reallocate(ptr, worst_case_size, 1, len(encoded))
28422885
trap_if(ptr + len(encoded) > len(cx.opts.memory))
28432886
return (ptr, len(encoded))
28442887
return (ptr, src_code_units)
@@ -2852,13 +2895,13 @@ if multiple UTF-8 bytes were collapsed into a single 2-byte UTF-16 code unit:
28522895
def store_utf8_to_utf16(cx, src, src_code_units):
28532896
worst_case_size = 2 * src_code_units
28542897
assert(worst_case_size <= REALLOC_I32_MAX)
2855-
ptr = cx.opts.realloc(0, 0, 2, worst_case_size)
2898+
ptr = cx.allocate(2, worst_case_size)
28562899
trap_if(ptr != align_to(ptr, 2))
28572900
trap_if(ptr + worst_case_size > len(cx.opts.memory))
28582901
encoded = src.encode('utf-16-le')
28592902
cx.opts.memory[ptr : ptr+len(encoded)] = encoded
28602903
if len(encoded) < worst_case_size:
2861-
ptr = cx.opts.realloc(ptr, worst_case_size, 2, len(encoded))
2904+
ptr = cx.reallocate(ptr, worst_case_size, 2, len(encoded))
28622905
trap_if(ptr != align_to(ptr, 2))
28632906
trap_if(ptr + len(encoded) > len(cx.opts.memory))
28642907
code_units = int(len(encoded) / 2)
@@ -2876,7 +2919,7 @@ bytes):
28762919
```python
28772920
def store_string_to_latin1_or_utf16(cx, src, src_code_units):
28782921
assert(src_code_units <= REALLOC_I32_MAX)
2879-
ptr = cx.opts.realloc(0, 0, 2, src_code_units)
2922+
ptr = cx.allocate(2, src_code_units)
28802923
trap_if(ptr != align_to(ptr, 2))
28812924
trap_if(ptr + src_code_units > len(cx.opts.memory))
28822925
dst_byte_length = 0
@@ -2887,7 +2930,7 @@ def store_string_to_latin1_or_utf16(cx, src, src_code_units):
28872930
else:
28882931
worst_case_size = 2 * src_code_units
28892932
assert(worst_case_size <= REALLOC_I32_MAX)
2890-
ptr = cx.opts.realloc(ptr, src_code_units, 2, worst_case_size)
2933+
ptr = cx.reallocate(ptr, src_code_units, 2, worst_case_size)
28912934
trap_if(ptr != align_to(ptr, 2))
28922935
trap_if(ptr + worst_case_size > len(cx.opts.memory))
28932936
for j in range(dst_byte_length-1, -1, -1):
@@ -2896,13 +2939,13 @@ def store_string_to_latin1_or_utf16(cx, src, src_code_units):
28962939
encoded = src.encode('utf-16-le')
28972940
cx.opts.memory[ptr+2*dst_byte_length : ptr+len(encoded)] = encoded[2*dst_byte_length : ]
28982941
if worst_case_size > len(encoded):
2899-
ptr = cx.opts.realloc(ptr, worst_case_size, 2, len(encoded))
2942+
ptr = cx.reallocate(ptr, worst_case_size, 2, len(encoded))
29002943
trap_if(ptr != align_to(ptr, 2))
29012944
trap_if(ptr + len(encoded) > len(cx.opts.memory))
29022945
tagged_code_units = int(len(encoded) / 2) | utf16_tag(cx.opts.memory.ptr_type())
29032946
return (ptr, tagged_code_units)
29042947
if dst_byte_length < src_code_units:
2905-
ptr = cx.opts.realloc(ptr, src_code_units, 2, dst_byte_length)
2948+
ptr = cx.reallocate(ptr, src_code_units, 2, dst_byte_length)
29062949
trap_if(ptr != align_to(ptr, 2))
29072950
trap_if(ptr + dst_byte_length > len(cx.opts.memory))
29082951
return (ptr, dst_byte_length)
@@ -2922,7 +2965,7 @@ inexpensively fused with the UTF-16 validate+copy loop.)
29222965
def store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units):
29232966
src_byte_length = 2 * src_code_units
29242967
assert(src_byte_length <= REALLOC_I32_MAX)
2925-
ptr = cx.opts.realloc(0, 0, 2, src_byte_length)
2968+
ptr = cx.allocate(2, src_byte_length)
29262969
trap_if(ptr != align_to(ptr, 2))
29272970
trap_if(ptr + src_byte_length > len(cx.opts.memory))
29282971
encoded = src.encode('utf-16-le')
@@ -2933,7 +2976,7 @@ def store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units):
29332976
latin1_size = int(len(encoded) / 2)
29342977
for i in range(latin1_size):
29352978
cx.opts.memory[ptr + i] = cx.opts.memory[ptr + 2*i]
2936-
ptr = cx.opts.realloc(ptr, src_byte_length, 1, latin1_size)
2979+
ptr = cx.reallocate(ptr, src_byte_length, 1, latin1_size)
29372980
trap_if(ptr + latin1_size > len(cx.opts.memory))
29382981
return (ptr, latin1_size)
29392982
```
@@ -2964,7 +3007,7 @@ def store_list(cx, v, ptr, elem_type, maybe_length):
29643007
def store_list_into_range(cx, v, elem_type):
29653008
byte_length = len(v) * elem_size(elem_type, cx.opts.memory.ptr_type())
29663009
assert(byte_length <= REALLOC_I32_MAX)
2967-
ptr = cx.opts.realloc(0, 0, alignment(elem_type, cx.opts.memory.ptr_type()), byte_length)
3010+
ptr = cx.allocate(alignment(elem_type, cx.opts.memory.ptr_type()), byte_length)
29683011
trap_if(ptr != align_to(ptr, alignment(elem_type, cx.opts.memory.ptr_type())))
29693012
trap_if(ptr + byte_length > len(cx.opts.memory))
29703013
store_list_into_valid_range(cx, v, ptr, elem_type)
@@ -3490,13 +3533,13 @@ storage with `realloc` or accepting a caller-allocated buffer as an
34903533
out-param:
34913534
```python
34923535
def lower_flat_values(cx, max_flat, vs, ts, out_param = None):
3493-
cx.inst.may_leave = False
34943536
flat_types = flatten_types(ts, cx.opts)
34953537
if len(flat_types) > max_flat:
34963538
tuple_type = TupleType(ts)
34973539
tuple_value = {str(i): v for i,v in enumerate(vs)}
34983540
if out_param is None:
3499-
ptr = cx.opts.realloc(0, 0, alignment(tuple_type, cx.opts.memory.ptr_type()), elem_size(tuple_type, cx.opts.memory.ptr_type()))
3541+
ptr_type = cx.opts.memory.ptr_type()
3542+
ptr = cx.allocate(alignment(tuple_type, ptr_type), elem_size(tuple_type, ptr_type))
35003543
flat_vals = [ptr]
35013544
else:
35023545
ptr = out_param.next(cx.opts.memory.ptr_type())
@@ -3508,14 +3551,8 @@ def lower_flat_values(cx, max_flat, vs, ts, out_param = None):
35083551
flat_vals = []
35093552
for i in range(len(vs)):
35103553
flat_vals += lower_flat(cx, vs[i], ts[i])
3511-
cx.inst.may_leave = True
35123554
return flat_vals
35133555
```
3514-
The `may_leave` flag is guarded by `canon_lower` below to prevent a component
3515-
from calling out of the component while in the middle of lowering, ensuring
3516-
that the relative ordering of the side effects of lifting followed by lowering
3517-
cannot be observed and thus an implementation may reliably fuse lifting with
3518-
lowering when making a cross-component call to avoid the intermediate copy.
35193556

35203557

35213558
## Canonical Definitions
@@ -3629,6 +3666,7 @@ function can free any associated allocations.
36293666
result = lift_flat_values(cx, MAX_FLAT_RESULTS, CoreValueIter(flat_results), ft.result_type())
36303667
task.return_(result)
36313668
if opts.post_return is not None:
3669+
assert(cx.inst.may_leave)
36323670
inst.may_leave = False
36333671
[] = call_and_trap_on_throw(opts.post_return, flat_results)
36343672
inst.may_leave = True
@@ -4857,6 +4895,7 @@ of the [current thread]:
48574895
```python
48584896
def canon_thread_index():
48594897
thread = current_thread()
4898+
trap_if(not thread.task.inst.may_leave)
48604899
assert(thread.index is not None)
48614900
return [thread.index]
48624901
```

design/mvp/canonical-abi/definitions.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,26 @@ def __init__(self, opts, inst, borrow_scope = None):
666666
self.inst = inst
667667
self.borrow_scope = borrow_scope
668668

669+
def reallocate(self, old, old_byte_length, alignment, new_byte_length):
670+
assert(self.inst.may_leave)
671+
self.inst.may_leave = False
672+
ptrt = U32Type() if self.opts.memory.ptr_type() == 'i32' else U64Type()
673+
ft = FuncType([ptrt, ptrt, ptrt, ptrt], [ptrt], async_ = False)
674+
opts = CanonicalOptions(async_ = False)
675+
def on_start():
676+
return [old, old_byte_length, alignment, new_byte_length]
677+
ptr = None
678+
def on_resolve(result):
679+
nonlocal ptr
680+
[ptr] = result
681+
canon_lift(self.opts.realloc, ft, opts, self.inst, on_start, on_resolve)
682+
assert(ptr is not None)
683+
self.inst.may_leave = True
684+
return ptr
685+
686+
def allocate(self, alignment, byte_length):
687+
return self.reallocate(0, 0, alignment, byte_length)
688+
669689
## Runtime State
670690

671691
### Table State
@@ -1633,7 +1653,7 @@ def store_string_into_range(cx, v: String):
16331653
def store_string_copy(cx, src, src_code_units, dst_code_unit_size, dst_alignment, dst_encoding):
16341654
dst_byte_length = dst_code_unit_size * src_code_units
16351655
assert(dst_byte_length <= REALLOC_I32_MAX)
1636-
ptr = cx.opts.realloc(0, 0, dst_alignment, dst_byte_length)
1656+
ptr = cx.allocate(dst_alignment, dst_byte_length)
16371657
trap_if(ptr != align_to(ptr, dst_alignment))
16381658
trap_if(ptr + dst_byte_length > len(cx.opts.memory))
16391659
encoded = src.encode(dst_encoding)
@@ -1651,41 +1671,41 @@ def store_latin1_to_utf8(cx, src, src_code_units):
16511671

16521672
def store_string_to_utf8(cx, src, src_code_units, worst_case_size):
16531673
assert(src_code_units <= REALLOC_I32_MAX)
1654-
ptr = cx.opts.realloc(0, 0, 1, src_code_units)
1674+
ptr = cx.allocate(1, src_code_units)
16551675
trap_if(ptr + src_code_units > len(cx.opts.memory))
16561676
for i,code_point in enumerate(src):
16571677
if ord(code_point) < 2**7:
16581678
cx.opts.memory[ptr + i] = ord(code_point)
16591679
else:
16601680
assert(worst_case_size <= REALLOC_I32_MAX)
1661-
ptr = cx.opts.realloc(ptr, src_code_units, 1, worst_case_size)
1681+
ptr = cx.reallocate(ptr, src_code_units, 1, worst_case_size)
16621682
trap_if(ptr + worst_case_size > len(cx.opts.memory))
16631683
encoded = src.encode('utf-8')
16641684
cx.opts.memory[ptr+i : ptr+len(encoded)] = encoded[i : ]
16651685
if worst_case_size > len(encoded):
1666-
ptr = cx.opts.realloc(ptr, worst_case_size, 1, len(encoded))
1686+
ptr = cx.reallocate(ptr, worst_case_size, 1, len(encoded))
16671687
trap_if(ptr + len(encoded) > len(cx.opts.memory))
16681688
return (ptr, len(encoded))
16691689
return (ptr, src_code_units)
16701690

16711691
def store_utf8_to_utf16(cx, src, src_code_units):
16721692
worst_case_size = 2 * src_code_units
16731693
assert(worst_case_size <= REALLOC_I32_MAX)
1674-
ptr = cx.opts.realloc(0, 0, 2, worst_case_size)
1694+
ptr = cx.allocate(2, worst_case_size)
16751695
trap_if(ptr != align_to(ptr, 2))
16761696
trap_if(ptr + worst_case_size > len(cx.opts.memory))
16771697
encoded = src.encode('utf-16-le')
16781698
cx.opts.memory[ptr : ptr+len(encoded)] = encoded
16791699
if len(encoded) < worst_case_size:
1680-
ptr = cx.opts.realloc(ptr, worst_case_size, 2, len(encoded))
1700+
ptr = cx.reallocate(ptr, worst_case_size, 2, len(encoded))
16811701
trap_if(ptr != align_to(ptr, 2))
16821702
trap_if(ptr + len(encoded) > len(cx.opts.memory))
16831703
code_units = int(len(encoded) / 2)
16841704
return (ptr, code_units)
16851705

16861706
def store_string_to_latin1_or_utf16(cx, src, src_code_units):
16871707
assert(src_code_units <= REALLOC_I32_MAX)
1688-
ptr = cx.opts.realloc(0, 0, 2, src_code_units)
1708+
ptr = cx.allocate(2, src_code_units)
16891709
trap_if(ptr != align_to(ptr, 2))
16901710
trap_if(ptr + src_code_units > len(cx.opts.memory))
16911711
dst_byte_length = 0
@@ -1696,7 +1716,7 @@ def store_string_to_latin1_or_utf16(cx, src, src_code_units):
16961716
else:
16971717
worst_case_size = 2 * src_code_units
16981718
assert(worst_case_size <= REALLOC_I32_MAX)
1699-
ptr = cx.opts.realloc(ptr, src_code_units, 2, worst_case_size)
1719+
ptr = cx.reallocate(ptr, src_code_units, 2, worst_case_size)
17001720
trap_if(ptr != align_to(ptr, 2))
17011721
trap_if(ptr + worst_case_size > len(cx.opts.memory))
17021722
for j in range(dst_byte_length-1, -1, -1):
@@ -1705,21 +1725,21 @@ def store_string_to_latin1_or_utf16(cx, src, src_code_units):
17051725
encoded = src.encode('utf-16-le')
17061726
cx.opts.memory[ptr+2*dst_byte_length : ptr+len(encoded)] = encoded[2*dst_byte_length : ]
17071727
if worst_case_size > len(encoded):
1708-
ptr = cx.opts.realloc(ptr, worst_case_size, 2, len(encoded))
1728+
ptr = cx.reallocate(ptr, worst_case_size, 2, len(encoded))
17091729
trap_if(ptr != align_to(ptr, 2))
17101730
trap_if(ptr + len(encoded) > len(cx.opts.memory))
17111731
tagged_code_units = int(len(encoded) / 2) | utf16_tag(cx.opts.memory.ptr_type())
17121732
return (ptr, tagged_code_units)
17131733
if dst_byte_length < src_code_units:
1714-
ptr = cx.opts.realloc(ptr, src_code_units, 2, dst_byte_length)
1734+
ptr = cx.reallocate(ptr, src_code_units, 2, dst_byte_length)
17151735
trap_if(ptr != align_to(ptr, 2))
17161736
trap_if(ptr + dst_byte_length > len(cx.opts.memory))
17171737
return (ptr, dst_byte_length)
17181738

17191739
def store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units):
17201740
src_byte_length = 2 * src_code_units
17211741
assert(src_byte_length <= REALLOC_I32_MAX)
1722-
ptr = cx.opts.realloc(0, 0, 2, src_byte_length)
1742+
ptr = cx.allocate(2, src_byte_length)
17231743
trap_if(ptr != align_to(ptr, 2))
17241744
trap_if(ptr + src_byte_length > len(cx.opts.memory))
17251745
encoded = src.encode('utf-16-le')
@@ -1730,7 +1750,7 @@ def store_probably_utf16_to_latin1_or_utf16(cx, src, src_code_units):
17301750
latin1_size = int(len(encoded) / 2)
17311751
for i in range(latin1_size):
17321752
cx.opts.memory[ptr + i] = cx.opts.memory[ptr + 2*i]
1733-
ptr = cx.opts.realloc(ptr, src_byte_length, 1, latin1_size)
1753+
ptr = cx.reallocate(ptr, src_byte_length, 1, latin1_size)
17341754
trap_if(ptr + latin1_size > len(cx.opts.memory))
17351755
return (ptr, latin1_size)
17361756

@@ -1749,7 +1769,7 @@ def store_list(cx, v, ptr, elem_type, maybe_length):
17491769
def store_list_into_range(cx, v, elem_type):
17501770
byte_length = len(v) * elem_size(elem_type, cx.opts.memory.ptr_type())
17511771
assert(byte_length <= REALLOC_I32_MAX)
1752-
ptr = cx.opts.realloc(0, 0, alignment(elem_type, cx.opts.memory.ptr_type()), byte_length)
1772+
ptr = cx.allocate(alignment(elem_type, cx.opts.memory.ptr_type()), byte_length)
17531773
trap_if(ptr != align_to(ptr, alignment(elem_type, cx.opts.memory.ptr_type())))
17541774
trap_if(ptr + byte_length > len(cx.opts.memory))
17551775
store_list_into_valid_range(cx, v, ptr, elem_type)
@@ -2107,13 +2127,13 @@ def lift_flat_values(cx, max_flat, vi, ts):
21072127
return [ lift_flat(cx, vi, t) for t in ts ]
21082128

21092129
def lower_flat_values(cx, max_flat, vs, ts, out_param = None):
2110-
cx.inst.may_leave = False
21112130
flat_types = flatten_types(ts, cx.opts)
21122131
if len(flat_types) > max_flat:
21132132
tuple_type = TupleType(ts)
21142133
tuple_value = {str(i): v for i,v in enumerate(vs)}
21152134
if out_param is None:
2116-
ptr = cx.opts.realloc(0, 0, alignment(tuple_type, cx.opts.memory.ptr_type()), elem_size(tuple_type, cx.opts.memory.ptr_type()))
2135+
ptr_type = cx.opts.memory.ptr_type()
2136+
ptr = cx.allocate(alignment(tuple_type, ptr_type), elem_size(tuple_type, ptr_type))
21172137
flat_vals = [ptr]
21182138
else:
21192139
ptr = out_param.next(cx.opts.memory.ptr_type())
@@ -2125,7 +2145,6 @@ def lower_flat_values(cx, max_flat, vs, ts, out_param = None):
21252145
flat_vals = []
21262146
for i in range(len(vs)):
21272147
flat_vals += lower_flat(cx, vs[i], ts[i])
2128-
cx.inst.may_leave = True
21292148
return flat_vals
21302149

21312150
## Canonical Definitions
@@ -2149,6 +2168,7 @@ def thread_func():
21492168
result = lift_flat_values(cx, MAX_FLAT_RESULTS, CoreValueIter(flat_results), ft.result_type())
21502169
task.return_(result)
21512170
if opts.post_return is not None:
2171+
assert(cx.inst.may_leave)
21522172
inst.may_leave = False
21532173
[] = call_and_trap_on_throw(opts.post_return, flat_results)
21542174
inst.may_leave = True
@@ -2667,6 +2687,7 @@ def drop(EndT, stream_or_future_t, hi):
26672687

26682688
def canon_thread_index():
26692689
thread = current_thread()
2690+
trap_if(not thread.task.inst.may_leave)
26702691
assert(thread.index is not None)
26712692
return [thread.index]
26722693

0 commit comments

Comments
 (0)