@@ -1222,6 +1222,49 @@ class LiftLowerContext:
12221222The ` borrow_scope ` field may be ` None ` if the types being lifted/lowered are
12231223known 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).
28032846def 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
28272870def 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:
28522895def 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
28772920def 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.)
29222965def 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):
29643007def 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
34903533out-param:
34913534``` python
34923535def 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
48584896def 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```
0 commit comments