@@ -13,15 +13,30 @@ use wasmparser::*;
1313
1414const PAGE_SIZE : i32 = 64 * 1024 ;
1515
16- /// This function will reduce the input core `wasm` module to only the set of
16+ /// The primitive allocation method we use to implement a realloc function,
17+ /// based on what the main module provides
18+ pub enum ReallocScheme < ' a > {
19+ /// The main module exports a ready-made realloc function with the given
20+ /// name.
21+ Realloc ( & ' a str ) ,
22+ /// The main module exports a `malloc(size: u32) -> u32` function with the
23+ /// given name. We can wrap this in a thin shell to make realloc.
24+ Malloc ( & ' a str ) ,
25+ /// The main module exports no realloc function or anything to make one out
26+ /// of, so we should implement realloc in terms of `memory.grow`.
27+ MemoryGrow ,
28+ }
29+ use ReallocScheme :: * ;
30+
31+ /// This function will reduce the input core `wasm` module (the adapter, I believe) to only the set of
1732/// exports `required`.
1833///
1934/// This internally performs a "gc" pass after removing exports to ensure that
2035/// the resulting module imports the minimal set of functions necessary.
2136pub fn run (
2237 wasm : & [ u8 ] ,
2338 required : & IndexSet < String > ,
24- main_module_realloc : Option < & str > ,
39+ realloc_scheme : ReallocScheme ,
2540) -> Result < Vec < u8 > > {
2641 assert ! ( !required. is_empty( ) ) ;
2742
@@ -46,7 +61,7 @@ pub fn run(
4661 }
4762 assert ! ( !module. exports. is_empty( ) ) ;
4863 module. liveness ( ) ?;
49- module. encode ( main_module_realloc )
64+ module. encode ( realloc_scheme )
5065}
5166
5267/// This function generates a Wasm function body which implements `cabi_realloc` in terms of `memory.grow`. It
@@ -500,9 +515,21 @@ impl<'a> Module<'a> {
500515 live_iter ( & self . live_tables , self . tables . iter ( ) )
501516 }
502517
518+ /// Returns a new Wasm function body which implements `cabi_realloc` to call
519+ /// a `malloc` that's exported from the main module. Pass in the index of
520+ /// the `malloc` function within the adapter.
521+ fn realloc_via_malloc ( & self , malloc_index : u32 ) -> Result < wasm_encoder:: Function > {
522+ let mut func = wasm_encoder:: Function :: new ( [ ] ) ;
523+ func. instructions ( )
524+ . local_get ( 3 ) // desired new size
525+ . call ( malloc_index)
526+ . end ( ) ;
527+ Ok ( func)
528+ }
529+
503530 /// Encodes this `Module` to a new wasm module which is gc'd and only
504531 /// contains the items that are live as calculated by the `liveness` pass.
505- fn encode ( & mut self , main_module_realloc : Option < & str > ) -> Result < Vec < u8 > > {
532+ fn encode ( & mut self , realloc_scheme : ReallocScheme ) -> Result < Vec < u8 > > {
506533 // Data structure used to track the mapping of old index to new index
507534 // for all live items.
508535 let mut map = Encoder :: default ( ) ;
@@ -584,11 +611,38 @@ impl<'a> Module<'a> {
584611 let is_realloc =
585612 |m, n| m == "__main_module__" && matches ! ( n, "canonical_abi_realloc" | "cabi_realloc" ) ;
586613
614+ let add_malloc_type = |types : & mut wasm_encoder:: TypeSection | {
615+ let type_index = types. len ( ) ;
616+ types
617+ . ty ( )
618+ . function ( [ wasm_encoder:: ValType :: I32 ] , [ wasm_encoder:: ValType :: I32 ] ) ;
619+ type_index
620+ } ;
621+
622+ let mut malloc_index = None ;
623+ let mut func_names = Vec :: new ( ) ;
624+
625+ // Import malloc before we start keeping track of the realloc_index so
626+ // we don't have to add num_func_imports every time we read it.
627+ if let Malloc ( malloc_name) = realloc_scheme {
628+ imports. import (
629+ "__main_module__" ,
630+ malloc_name,
631+ EntityType :: Function ( add_malloc_type ( & mut types) ) ,
632+ ) ;
633+ malloc_index = Some ( num_func_imports) ;
634+ map. funcs . reserve ( ) ;
635+ func_names. push ( ( num_func_imports, malloc_name) ) ;
636+ num_func_imports += 1 ;
637+ }
638+
587639 let ( imported, local) =
588640 self . live_funcs ( )
589641 . partition :: < Vec < _ > , _ > ( |( _, func) | match & func. def {
590642 Definition :: Import ( m, n) => {
591- !is_realloc ( * m, * n) || main_module_realloc. is_some ( )
643+ // Keep realloc function around iff we're going to use
644+ // it. Always keep other functions around.
645+ !is_realloc ( * m, * n) || matches ! ( realloc_scheme, Realloc ( _) )
592646 }
593647 Definition :: Local ( _) => false ,
594648 } ) ;
@@ -603,7 +657,10 @@ impl<'a> Module<'a> {
603657 // exports that function, but possibly using a different name
604658 // (e.g. `canonical_abi_realloc`). Update the name to match if necessary.
605659 realloc_index = Some ( num_func_imports) ;
606- main_module_realloc. unwrap_or ( n)
660+ match realloc_scheme {
661+ Realloc ( name) => name,
662+ _ => n,
663+ }
607664 } else {
608665 n
609666 } ;
@@ -637,22 +694,17 @@ impl<'a> Module<'a> {
637694 let sp = self . find_mut_i32_global ( "__stack_pointer" ) ?;
638695 let allocation_state = self . find_mut_i32_global ( "allocation_state" ) ?;
639696
640- let mut func_names = Vec :: new ( ) ;
641-
642- if let ( Some ( realloc) , Some ( _) , None ) = ( main_module_realloc, sp, realloc_index) {
697+ if let ( Realloc ( realloc_name) , Some ( _) , None ) = ( & realloc_scheme, sp, realloc_index) {
643698 // The main module exports a realloc function, and although the adapter doesn't import it, we're going
644699 // to add a function which calls it to allocate some stack space, so let's add an import now.
645-
646- // Tell the function remapper we're reserving a slot for our extra import:
647- map. funcs . next += 1 ;
648-
700+ map. funcs . reserve ( ) ;
649701 realloc_index = Some ( num_func_imports) ;
650702 imports. import (
651703 "__main_module__" ,
652- realloc ,
704+ realloc_name ,
653705 EntityType :: Function ( add_realloc_type ( & mut types) ) ,
654706 ) ;
655- func_names. push ( ( num_func_imports, realloc ) ) ;
707+ func_names. push ( ( num_func_imports, realloc_name ) ) ;
656708 num_func_imports += 1 ;
657709 }
658710
@@ -665,30 +717,35 @@ impl<'a> Module<'a> {
665717 // exporting it. In this case, we need to define a local function it can call instead.
666718 realloc_index = Some ( num_func_imports + funcs. len ( ) ) ;
667719 funcs. function ( ty) ;
668- code. function ( & realloc_via_memory_grow ( ) ) ;
720+ let realloc_func = match realloc_scheme {
721+ Malloc ( _) => self . realloc_via_malloc (
722+ malloc_index. expect ( "this was set above when the enum was Malloc" ) ,
723+ ) ?,
724+ MemoryGrow => realloc_via_memory_grow ( ) ,
725+ Realloc ( _) => bail ! (
726+ "shouldn't get here, as we already know the main module doesn't export a realloc function"
727+ ) ,
728+ } ;
729+ code. function ( & realloc_func) ;
669730 }
670731 Definition :: Local ( _) => {
671732 funcs. function ( ty) ;
672733 }
673734 }
674735 }
675736
676- let lazy_stack_init_index =
677- if sp . is_some ( ) && allocation_state . is_some ( ) && main_module_realloc . is_some ( ) {
678- // We have a stack pointer, a `cabi_realloc` function from the main module, and a global variable for
737+ let lazy_stack_init_index = match ( & realloc_scheme , sp , allocation_state ) {
738+ ( Realloc ( _ ) , Some ( _ ) , Some ( _ ) ) => {
739+ // We have a `cabi_realloc` function from the main module, a stack pointer , and a global variable for
679740 // keeping track of (and short-circuiting) reentrance. That means we can (and should) do lazy stack
680741 // allocation.
681742 let index = num_func_imports + funcs. len ( ) ;
682-
683- // Tell the function remapper we're reserving a slot for our extra function:
684- map. funcs . next += 1 ;
685-
743+ map. funcs . reserve ( ) ;
686744 funcs. function ( add_empty_type ( & mut types) ) ;
687-
688745 Some ( index)
689- } else {
690- None
691- } ;
746+ }
747+ _ => None ,
748+ } ;
692749
693750 let exported_funcs = self
694751 . exports
@@ -733,10 +790,16 @@ impl<'a> Module<'a> {
733790
734791 if sp. is_some ( ) && ( realloc_index. is_none ( ) || allocation_state. is_none ( ) ) {
735792 // Either the main module does _not_ export a realloc function, or it is not safe to use for stack
736- // allocation because we have no way to short-circuit reentrance, so we'll use `memory.grow` instead.
793+ // allocation because we have no way to short-circuit reentrance, so we'll provide our own realloc
794+ // function instead.
737795 realloc_index = Some ( num_func_imports + funcs. len ( ) ) ;
738796 funcs. function ( add_realloc_type ( & mut types) ) ;
739- code. function ( & realloc_via_memory_grow ( ) ) ;
797+ let realloc_func = if let Some ( index) = malloc_index {
798+ self . realloc_via_malloc ( index) ?
799+ } else {
800+ realloc_via_memory_grow ( )
801+ } ;
802+ code. function ( & realloc_func) ;
740803 }
741804
742805 // Inject a start function to initialize the stack pointer which will be local to this module. This only
@@ -879,11 +942,12 @@ impl<'a> Module<'a> {
879942 section. push ( code) ;
880943 subsection. encode ( & mut section) ;
881944 } ;
882- if let ( Some ( realloc_index) , true ) = (
883- realloc_index,
884- main_module_realloc. is_none ( ) || allocation_state. is_none ( ) ,
885- ) {
886- func_names. push ( ( realloc_index, "realloc_via_memory_grow" ) ) ;
945+ if let Some ( realloc_index) = realloc_index {
946+ match realloc_scheme {
947+ Malloc ( _) => func_names. push ( ( realloc_index, "realloc_via_malloc" ) ) ,
948+ MemoryGrow => func_names. push ( ( realloc_index, "realloc_via_memory_grow" ) ) ,
949+ Realloc ( _) => ( ) , // The realloc routine is in another module.
950+ }
887951 }
888952 if let Some ( lazy_stack_init_index) = lazy_stack_init_index {
889953 func_names. push ( ( lazy_stack_init_index, "allocate_stack" ) ) ;
@@ -1118,6 +1182,14 @@ impl Remap {
11181182 self . next += 1 ;
11191183 }
11201184
1185+ /// Reserves the next "new index" for an item you are adding.
1186+ ///
1187+ /// For example, call this when you add a new function to a module and need
1188+ /// to avoid other functions getting remapped to its index.
1189+ fn reserve ( & mut self ) {
1190+ self . next += 1 ;
1191+ }
1192+
11211193 /// Returns the new index corresponding to an old index.
11221194 ///
11231195 /// Panics if the `old` index was not added via `push` above.
0 commit comments