@@ -49,64 +49,6 @@ pub(crate) fn is_list_canonical(resolve: &Resolve, element: &Type) -> bool {
4949 }
5050}
5151
52- fn collect_direct_canonical_lists (
53- resolve : & Resolve ,
54- ty : & Type ,
55- expression : String ,
56- wasm_param : usize ,
57- expressions : & mut HashSet < String > ,
58- wasm_params : & mut BTreeMap < usize , Type > ,
59- ) {
60- let Type :: Id ( id) = ty else {
61- return ;
62- } ;
63- match & resolve. types [ * id] . kind {
64- TypeDefKind :: Type ( ty) => collect_direct_canonical_lists (
65- resolve,
66- ty,
67- expression,
68- wasm_param,
69- expressions,
70- wasm_params,
71- ) ,
72- TypeDefKind :: List ( element) => {
73- if is_list_canonical ( resolve, element) {
74- expressions. insert ( expression) ;
75- wasm_params. insert ( wasm_param, * element) ;
76- }
77- }
78- TypeDefKind :: Record ( record) => {
79- let mut field_wasm_param = wasm_param;
80- for field in & record. fields {
81- collect_direct_canonical_lists (
82- resolve,
83- & field. ty ,
84- format ! ( "({expression}).{}" , field. name. to_moonbit_ident( ) ) ,
85- field_wasm_param,
86- expressions,
87- wasm_params,
88- ) ;
89- field_wasm_param += abi:: flat_types ( resolve, & field. ty , None ) . unwrap ( ) . len ( ) ;
90- }
91- }
92- TypeDefKind :: Tuple ( tuple) => {
93- let mut field_wasm_param = wasm_param;
94- for ( index, field) in tuple. types . iter ( ) . enumerate ( ) {
95- collect_direct_canonical_lists (
96- resolve,
97- field,
98- format ! ( "({expression}).{index}" ) ,
99- field_wasm_param,
100- expressions,
101- wasm_params,
102- ) ;
103- field_wasm_param += abi:: flat_types ( resolve, field, None ) . unwrap ( ) . len ( ) ;
104- }
105- }
106- _ => { }
107- }
108- }
109-
11052// Assumptions:
11153// - Data: u8 -> Byte, s8 | s32 -> Int, s16 -> Int16, u16 -> UInt16, u32 -> UInt, s64 -> Int64, u64 -> UInt64, f32 -> Float, f64 -> Double, address -> Int
11254// - Encoding: UTF16
@@ -685,25 +627,16 @@ impl InterfaceGenerator<'_> {
685627 let endpoint_plan = self . import_async_function_plan ( self . interface , func) ;
686628 let wasm_sig = self . resolve . wasm_signature ( variant, func) ;
687629 let mbt_sig = self . world_gen . pkg_resolver . mbt_sig ( self . name , func, false ) ;
688- let mut direct_canonical_lists = HashSet :: new ( ) ;
689- let mut direct_canonical_wasm_params = BTreeMap :: new ( ) ;
690- if !async_plan. is_async ( ) && !wasm_sig. indirect_params {
691- let mut wasm_param = 0 ;
692- for Param { name, ty, .. } in & func. params {
693- collect_direct_canonical_lists (
694- self . resolve ,
695- ty,
696- name. to_moonbit_ident ( ) ,
697- wasm_param,
698- & mut direct_canonical_lists,
699- & mut direct_canonical_wasm_params,
700- ) ;
701- wasm_param += abi:: flat_types ( self . resolve , ty, None ) . unwrap ( ) . len ( ) ;
702- }
703- }
704- let ( src, needs_cleanup_list, endpoint_state) = if async_plan. is_async ( ) {
630+ let ( src, needs_cleanup_list, endpoint_state, direct_canonical_wasm_params) = if async_plan
631+ . is_async ( )
632+ {
705633 let body = self . generate_async_import_body ( & endpoint_plan, func, & mbt_sig, & wasm_sig) ;
706- ( body. src , body. needs_cleanup_list , body. state )
634+ (
635+ body. src ,
636+ body. needs_cleanup_list ,
637+ body. state ,
638+ BTreeMap :: new ( ) ,
639+ )
707640 } else {
708641 let mut bindgen = FunctionBindgen :: new (
709642 self ,
@@ -712,13 +645,7 @@ impl InterfaceGenerator<'_> {
712645 . map ( |Param { name, .. } | name. to_moonbit_ident ( ) )
713646 . collect ( ) ,
714647 )
715- . with_direct_canonical_list_params (
716- direct_canonical_lists,
717- direct_canonical_wasm_params
718- . keys ( )
719- . map ( |param| param + 1 )
720- . collect ( ) ,
721- )
648+ . with_direct_list_borrows ( !wasm_sig. indirect_params )
722649 . with_async_state ( endpoint_plan. state ( ) ) ;
723650 if endpoint_plan. has_endpoints ( ) {
724651 bindgen = bindgen. with_sync_import_commit (
@@ -734,7 +661,12 @@ impl InterfaceGenerator<'_> {
734661 & mut bindgen,
735662 false ,
736663 ) ;
737- ( bindgen. src , bindgen. needs_cleanup_list , bindgen. async_state )
664+ (
665+ bindgen. src ,
666+ bindgen. needs_cleanup_list ,
667+ bindgen. async_state ,
668+ bindgen. direct_canonical_wasm_params ,
669+ )
738670 } ;
739671
740672 let cleanup_list = if needs_cleanup_list {
@@ -763,8 +695,6 @@ impl InterfaceGenerator<'_> {
763695 if let Some ( element) = direct_canonical_wasm_params. get ( & i) {
764696 let element = self . world_gen . pkg_resolver . type_name ( self . name , element) ;
765697 format ! ( "p{i} : FixedArray[{element}]" )
766- } else if i > 0 && direct_canonical_wasm_params. contains_key ( & ( i - 1 ) ) {
767- format ! ( "p{i}? : Int = p{}.length()" , i - 1 )
768698 } else {
769699 format ! ( "p{i} : {}" , wasm_type( * param) )
770700 }
@@ -1537,8 +1467,9 @@ struct FunctionBindgen<'a, 'b> {
15371467 sync_endpoint_drop : bool ,
15381468 commit_endpoints : bool ,
15391469 sync_import_argument_types : Option < Vec < Type > > ,
1540- direct_canonical_list_params : HashSet < String > ,
1541- direct_canonical_list_length_params : HashSet < usize > ,
1470+ direct_list_borrows : bool ,
1471+ direct_list_borrow_candidates : HashMap < String , ( String , Type ) > ,
1472+ direct_canonical_wasm_params : BTreeMap < usize , Type > ,
15421473 async_state : AsyncFunctionState ,
15431474}
15441475
@@ -1569,19 +1500,15 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
15691500 sync_endpoint_drop : false ,
15701501 commit_endpoints : false ,
15711502 sync_import_argument_types : None ,
1572- direct_canonical_list_params : HashSet :: new ( ) ,
1573- direct_canonical_list_length_params : HashSet :: new ( ) ,
1503+ direct_list_borrows : false ,
1504+ direct_list_borrow_candidates : HashMap :: new ( ) ,
1505+ direct_canonical_wasm_params : BTreeMap :: new ( ) ,
15741506 async_state : AsyncFunctionState :: default ( ) ,
15751507 }
15761508 }
15771509
1578- fn with_direct_canonical_list_params (
1579- mut self ,
1580- params : HashSet < String > ,
1581- length_params : HashSet < usize > ,
1582- ) -> Self {
1583- self . direct_canonical_list_params = params;
1584- self . direct_canonical_list_length_params = length_params;
1510+ fn with_direct_list_borrows ( mut self , enabled : bool ) -> Self {
1511+ self . direct_list_borrows = enabled;
15851512 self
15861513 }
15871514
@@ -2135,22 +2062,29 @@ impl Bindgen for FunctionBindgen<'_, '_> {
21352062 ) ) ,
21362063
21372064 Instruction :: ListCanonLower { element, realloc } => {
2138- let element : & Type = element;
2139- let element = match element {
2065+ let original_element : & Type = element;
2066+ let element = match original_element {
21402067 Type :: Id ( id) => match & resolve. types [ dealias ( resolve, * id) ] . kind {
21412068 TypeDefKind :: Type ( element) => element,
21422069 _ => unreachable ! ( "unsupported list element type" ) ,
21432070 } ,
2144- _ => element ,
2071+ _ => original_element ,
21452072 } ;
2073+ let op = & operands[ 0 ] ;
2074+ // A canonical list can be passed as a typed borrow only when its
2075+ // pointer and length flow directly to the Wasm call. Lists lowered
2076+ // inside blocks are merged into variants or written into enclosing
2077+ // list storage, so those still need their integer pointer.
2078+ if self . direct_list_borrows && realloc. is_none ( ) && self . block_storage . is_empty ( ) {
2079+ let length = format ! ( "{op}.length()" ) ;
2080+ self . direct_list_borrow_candidates
2081+ . insert ( op. clone ( ) , ( length. clone ( ) , * original_element) ) ;
2082+ results. push ( op. clone ( ) ) ;
2083+ results. push ( length) ;
2084+ return ;
2085+ }
21462086 match element {
21472087 Type :: U8 => {
2148- let op = & operands[ 0 ] ;
2149- if realloc. is_none ( ) && self . direct_canonical_list_params . contains ( op) {
2150- results. push ( op. clone ( ) ) ;
2151- results. push ( format ! ( "{op}.length()" ) ) ;
2152- return ;
2153- }
21542088 let ptr = self . locals . tmp ( "ptr" ) ;
21552089 self . use_ffi ( ffi:: BYTES2PTR ) ;
21562090 uwriteln ! (
@@ -2174,12 +2108,6 @@ impl Bindgen for FunctionBindgen<'_, '_> {
21742108 | Type :: S64
21752109 | Type :: F32
21762110 | Type :: F64 => {
2177- let op = & operands[ 0 ] ;
2178- if realloc. is_none ( ) && self . direct_canonical_list_params . contains ( op) {
2179- results. push ( op. clone ( ) ) ;
2180- results. push ( format ! ( "{op}.length()" ) ) ;
2181- return ;
2182- }
21832111 let ptr = self . locals . tmp ( "ptr" ) ;
21842112 let ( owned_ffi, ty) = match element {
21852113 Type :: Bool => ( ffi:: BOOL_ARRAY2PTR , "bool" ) ,
@@ -2441,6 +2369,13 @@ impl Bindgen for FunctionBindgen<'_, '_> {
24412369 } ;
24422370
24432371 let func_name = name. to_upper_camel_case ( ) ;
2372+ for ( index, operand) in operands. iter ( ) . enumerate ( ) {
2373+ if let Some ( ( length, element) ) = self . direct_list_borrow_candidates . get ( operand)
2374+ {
2375+ assert_eq ! ( operands. get( index + 1 ) , Some ( length) ) ;
2376+ self . direct_canonical_wasm_params . insert ( index, * element) ;
2377+ }
2378+ }
24442379 let call_operands = if self . sync_import_argument_types . is_some ( ) {
24452380 operands
24462381 . iter ( )
@@ -2453,15 +2388,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
24532388 } else {
24542389 operands. clone ( )
24552390 } ;
2456- let arguments = call_operands
2457- . iter ( )
2458- . enumerate ( )
2459- . filter_map ( |( i, operand) | {
2460- ( !self . direct_canonical_list_length_params . contains ( & i) )
2461- . then_some ( operand. as_str ( ) )
2462- } )
2463- . collect :: < Vec < _ > > ( )
2464- . join ( ", " ) ;
2391+ let arguments = call_operands. join ( ", " ) ;
24652392 // TODO: handle this to support async functions
24662393 uwriteln ! ( self . src, "{assignment} wasmImport{func_name}({arguments});" ) ;
24672394 self . commit_sync_import_arguments ( sig, & call_operands) ;
@@ -3745,9 +3672,11 @@ mod tests {
37453672
37463673 assert ! (
37473674 top. contains(
3748- "wasmImportSend(bytes, unsigned_shorts, signed_shorts, words, (envelope).words, booleans"
3675+ "wasmImportSend(bytes, bytes.length(), unsigned_shorts, unsigned_shorts.length(), \
3676+ signed_shorts, signed_shorts.length(), words, words.length(), \
3677+ (envelope).words, (envelope).words.length(), booleans, booleans.length())"
37493678 ) ,
3750- "direct canonical lists must let the FFI supply their lengths : {top}"
3679+ "direct canonical lists must retain their canonical pointer/length operands : {top}"
37513680 ) ;
37523681 assert ! (
37533682 !top. contains( "mbt_ffi_borrowed_array2ptr(bytes)" )
@@ -3756,27 +3685,18 @@ mod tests {
37563685 && !top. contains( "mbt_ffi_borrowed_array2ptr(words)" ) ,
37573686 "direct list parameters must not round-trip through Int: {top}"
37583687 ) ;
3759- assert ! (
3760- !top. contains( "bytes.length()" )
3761- && !top. contains( "unsigned_shorts.length()" )
3762- && !top. contains( "signed_shorts.length()" )
3763- && !top. contains( "words.length()" )
3764- && !top. contains( "(envelope).words.length()" )
3765- && !top. contains( "booleans.length()" ) ,
3766- "direct canonical list lengths must be supplied by FFI defaults: {top}"
3767- ) ;
37683688 assert ! ( !top. contains( "mbt_ffi_borrowed_array2ptr" ) , "{top}" ) ;
37693689 assert ! (
37703690 ffi. contains(
37713691 "#unsafe_skip_stub_check\n #borrow(p0, p2, p4, p6, p8, p10)\n fn wasmImportSend(\
3772- p0 : FixedArray[Byte], p1? : Int = p0.length() , \
3773- p2 : FixedArray[UInt16], p3? : Int = p2.length() , \
3774- p4 : FixedArray[Int16], p5? : Int = p4.length() , \
3775- p6 : FixedArray[UInt], p7? : Int = p6.length() , \
3776- p8 : FixedArray[UInt], p9? : Int = p8.length() , \
3777- p10 : FixedArray[Bool], p11? : Int = p10.length() )"
3692+ p0 : FixedArray[Byte], p1 : Int, \
3693+ p2 : FixedArray[UInt16], p3 : Int, \
3694+ p4 : FixedArray[Int16], p5 : Int, \
3695+ p6 : FixedArray[UInt], p7 : Int, \
3696+ p8 : FixedArray[UInt], p9 : Int, \
3697+ p10 : FixedArray[Bool], p11 : Int)"
37783698 ) ,
3779- "the imported FFI must derive borrowed array lengths by default : {ffi}"
3699+ "the imported FFI must borrow arrays while retaining explicit lengths : {ffi}"
37803700 ) ;
37813701 assert ! (
37823702 !ffi. contains( "mbt_ffi_borrowed_array2ptr" ) ,
@@ -3824,6 +3744,74 @@ mod tests {
38243744 ) ;
38253745 }
38263746
3747+ #[ test]
3748+ fn imported_conditional_canonical_lists_retain_pointer_lowering ( ) {
3749+ let files = generate (
3750+ r#"
3751+ package a:b;
3752+
3753+ interface api {
3754+ variant optional-bytes {
3755+ none,
3756+ some(list<u8>),
3757+ }
3758+ send: func(bytes: optional-bytes);
3759+ }
3760+
3761+ world client { import api; }
3762+ "# ,
3763+ "client" ,
3764+ ) ;
3765+ let top = file ( & files, "interface/a/b/api/top.mbt" ) ;
3766+ let ffi = file ( & files, "interface/a/b/api/ffi.mbt" ) ;
3767+
3768+ assert ! (
3769+ top. contains( "mbt_ffi_bytes2ptr" ) ,
3770+ "conditional list pointers must be lowered before merging variant cases: {top}"
3771+ ) ;
3772+ assert ! (
3773+ !ffi. contains( "#borrow" ) ,
3774+ "a conditionally occupied pointer parameter cannot be a typed borrow: {ffi}"
3775+ ) ;
3776+ }
3777+
3778+ #[ test]
3779+ fn imported_indirect_canonical_lists_retain_pointer_lowering ( ) {
3780+ let files = generate (
3781+ r#"
3782+ package a:b;
3783+
3784+ interface api {
3785+ send: func(
3786+ a: list<u8>,
3787+ b: list<u8>,
3788+ c: list<u8>,
3789+ d: list<u8>,
3790+ e: list<u8>,
3791+ f: list<u8>,
3792+ g: list<u8>,
3793+ h: list<u8>,
3794+ i: list<u8>,
3795+ );
3796+ }
3797+
3798+ world client { import api; }
3799+ "# ,
3800+ "client" ,
3801+ ) ;
3802+ let top = file ( & files, "interface/a/b/api/top.mbt" ) ;
3803+ let ffi = file ( & files, "interface/a/b/api/ffi.mbt" ) ;
3804+
3805+ assert ! (
3806+ top. contains( "mbt_ffi_bytes2ptr(a)" ) ,
3807+ "lists stored in an indirect argument area still need integer pointers: {top}"
3808+ ) ;
3809+ assert ! (
3810+ ffi. contains( "fn wasmImportSend(p0 : Int)" ) && !ffi. contains( "#borrow" ) ,
3811+ "an indirect import receives only its argument-area pointer: {ffi}"
3812+ ) ;
3813+ }
3814+
38273815 #[ test]
38283816 fn async_export_background_group_name_is_deconflicted ( ) {
38293817 let files = generate (
0 commit comments