@@ -18,6 +18,7 @@ mod js;
18
18
mod multivalue;
19
19
pub mod wasm2es6js;
20
20
mod wit;
21
+ mod throw2unreachable;
21
22
22
23
pub struct Bindgen {
23
24
input : Input ,
@@ -45,14 +46,22 @@ pub struct Bindgen {
45
46
pub struct Output {
46
47
module : walrus:: Module ,
47
48
stem : String ,
49
+ generated : Generated ,
50
+ }
51
+
52
+ enum Generated {
53
+ InterfaceTypes ,
54
+ Js ( JsGenerated ) ,
55
+ }
56
+
57
+ struct JsGenerated {
58
+ mode : OutputMode ,
48
59
js : String ,
49
60
ts : String ,
50
- mode : OutputMode ,
51
- typescript : bool ,
52
61
snippets : HashMap < String , Vec < String > > ,
53
62
local_modules : HashMap < String , String > ,
54
63
npm_dependencies : HashMap < String , ( PathBuf , String ) > ,
55
- wasm_interface_types : bool ,
64
+ typescript : bool ,
56
65
}
57
66
58
67
#[ derive( Clone ) ]
@@ -354,54 +363,69 @@ impl Bindgen {
354
363
}
355
364
}
356
365
366
+ // If wasm interface types are enabled then the `__wbindgen_throw`
367
+ // intrinsic isn't available but it may be used by our runtime, so
368
+ // change all calls to this function to calls to `unreachable` instead.
369
+ // See more documentation in the pass documentation itself.
370
+ if self . wasm_interface_types {
371
+ throw2unreachable:: run ( & mut module) ;
372
+ }
373
+
374
+ // Using all of our metadata convert our module to a multi-value using
375
+ // module if applicable.
376
+ if self . multi_value {
377
+ if !self . wasm_interface_types {
378
+ anyhow:: bail!(
379
+ "Wasm multi-value is currently only available when \
380
+ Wasm interface types is also enabled"
381
+ ) ;
382
+ }
383
+ multivalue:: run ( & mut module)
384
+ . context ( "failed to transform return pointers into multi-value Wasm" ) ?;
385
+ }
386
+
357
387
// We've done a whole bunch of transformations to the wasm module, many
358
388
// of which leave "garbage" lying around, so let's prune out all our
359
389
// unnecessary things here.
360
390
gc_module_and_adapters ( & mut module) ;
361
391
362
- let aux = module
363
- . customs
364
- . delete_typed :: < wit:: WasmBindgenAux > ( )
365
- . expect ( "aux section should be present" ) ;
366
- let mut adapters = module
367
- . customs
368
- . delete_typed :: < wit:: NonstandardWitSection > ( )
369
- . unwrap ( ) ;
370
-
371
- // Now that our module is massaged and good to go, feed it into the JS
372
- // shim generation which will actually generate JS for all this.
373
- let ( npm_dependencies, ( js, ts) ) = {
392
+ // We're ready for the final emission passes now. If we're in wasm
393
+ // interface types mode then we execute the various passes there and
394
+ // generate a valid interface typess section into the wasm module.
395
+ //
396
+ // Otherwise we execute the JS generation passes to actually emit
397
+ // JS/TypeScript/etc. The output here is unused in wasm interfac
398
+ let generated = if self . wasm_interface_types {
399
+ wit:: section:: add ( & mut module)
400
+ . context ( "failed to generate a standard interface types section" ) ?;
401
+ Generated :: InterfaceTypes
402
+ } else {
403
+ let aux = module
404
+ . customs
405
+ . delete_typed :: < wit:: WasmBindgenAux > ( )
406
+ . expect ( "aux section should be present" ) ;
407
+ let adapters = module
408
+ . customs
409
+ . delete_typed :: < wit:: NonstandardWitSection > ( )
410
+ . unwrap ( ) ;
374
411
let mut cx = js:: Context :: new ( & mut module, self , & adapters, & aux) ?;
375
412
cx. generate ( ) ?;
376
- let npm_dependencies = cx. npm_dependencies . clone ( ) ;
377
- ( npm_dependencies, cx. finalize ( stem) ?)
413
+ let ( js, ts) = cx. finalize ( stem) ?;
414
+ Generated :: Js ( JsGenerated {
415
+ snippets : aux. snippets . clone ( ) ,
416
+ local_modules : aux. local_modules . clone ( ) ,
417
+ mode : self . mode . clone ( ) ,
418
+ typescript : self . typescript ,
419
+ npm_dependencies : cx. npm_dependencies . clone ( ) ,
420
+ js,
421
+ ts,
422
+ } )
378
423
} ;
379
424
380
- if self . wasm_interface_types {
381
- multivalue:: run ( & mut module, & mut adapters)
382
- . context ( "failed to transform return pointers into multi-value Wasm" ) ?;
383
- wit:: section:: add ( & mut module, & aux, & adapters)
384
- . context ( "failed to generate a standard wasm bindings custom section" ) ?;
385
- } else {
386
- if self . multi_value {
387
- anyhow:: bail!(
388
- "Wasm multi-value is currently only available when \
389
- Wasm interface types is also enabled"
390
- ) ;
391
- }
392
- }
393
-
394
425
Ok ( Output {
395
426
module,
396
427
stem : stem. to_string ( ) ,
397
- snippets : aux. snippets . clone ( ) ,
398
- local_modules : aux. local_modules . clone ( ) ,
399
- npm_dependencies,
400
- js,
401
- ts,
402
- mode : self . mode . clone ( ) ,
403
- typescript : self . typescript ,
404
- wasm_interface_types : self . wasm_interface_types ,
428
+ generated,
405
429
} )
406
430
}
407
431
@@ -554,8 +578,10 @@ fn unexported_unused_lld_things(module: &mut Module) {
554
578
555
579
impl Output {
556
580
pub fn js ( & self ) -> & str {
557
- assert ! ( !self . wasm_interface_types) ;
558
- & self . js
581
+ match & self . generated {
582
+ Generated :: InterfaceTypes => panic ! ( "no js with interface types output" ) ,
583
+ Generated :: Js ( gen) => & gen. js ,
584
+ }
559
585
}
560
586
561
587
pub fn wasm ( & self ) -> & walrus:: Module {
@@ -571,24 +597,24 @@ impl Output {
571
597
}
572
598
573
599
fn _emit ( & mut self , out_dir : & Path ) -> Result < ( ) , Error > {
574
- let wasm_name = if self . wasm_interface_types {
575
- self . stem . clone ( )
576
- } else {
577
- format ! ( "{}_bg" , self . stem)
600
+ let wasm_name = match & self . generated {
601
+ Generated :: InterfaceTypes => self . stem . clone ( ) ,
602
+ Generated :: Js ( _) => format ! ( "{}_bg" , self . stem) ,
578
603
} ;
579
604
let wasm_path = out_dir. join ( wasm_name) . with_extension ( "wasm" ) ;
580
605
fs:: create_dir_all ( out_dir) ?;
581
606
let wasm_bytes = self . module . emit_wasm ( ) ;
582
607
fs:: write ( & wasm_path, wasm_bytes)
583
608
. with_context ( || format ! ( "failed to write `{}`" , wasm_path. display( ) ) ) ?;
584
609
585
- if self . wasm_interface_types {
586
- return Ok ( ( ) ) ;
587
- }
610
+ let gen = match & self . generated {
611
+ Generated :: InterfaceTypes => return Ok ( ( ) ) ,
612
+ Generated :: Js ( gen) => gen,
613
+ } ;
588
614
589
615
// Write out all local JS snippets to the final destination now that
590
616
// we've collected them from all the programs.
591
- for ( identifier, list) in self . snippets . iter ( ) {
617
+ for ( identifier, list) in gen . snippets . iter ( ) {
592
618
for ( i, js) in list. iter ( ) . enumerate ( ) {
593
619
let name = format ! ( "inline{}.js" , i) ;
594
620
let path = out_dir. join ( "snippets" ) . join ( identifier) . join ( name) ;
@@ -598,15 +624,15 @@ impl Output {
598
624
}
599
625
}
600
626
601
- for ( path, contents) in self . local_modules . iter ( ) {
627
+ for ( path, contents) in gen . local_modules . iter ( ) {
602
628
let path = out_dir. join ( "snippets" ) . join ( path) ;
603
629
fs:: create_dir_all ( path. parent ( ) . unwrap ( ) ) ?;
604
630
fs:: write ( & path, contents)
605
631
. with_context ( || format ! ( "failed to write `{}`" , path. display( ) ) ) ?;
606
632
}
607
633
608
- if self . npm_dependencies . len ( ) > 0 {
609
- let map = self
634
+ if gen . npm_dependencies . len ( ) > 0 {
635
+ let map = gen
610
636
. npm_dependencies
611
637
. iter ( )
612
638
. map ( |( k, v) | ( k, & v. 1 ) )
@@ -617,29 +643,29 @@ impl Output {
617
643
618
644
// And now that we've got all our JS and TypeScript, actually write it
619
645
// out to the filesystem.
620
- let extension = if self . mode . nodejs_experimental_modules ( ) {
646
+ let extension = if gen . mode . nodejs_experimental_modules ( ) {
621
647
"mjs"
622
648
} else {
623
649
"js"
624
650
} ;
625
651
let js_path = out_dir. join ( & self . stem ) . with_extension ( extension) ;
626
- fs:: write ( & js_path, reset_indentation ( & self . js ) )
652
+ fs:: write ( & js_path, reset_indentation ( & gen . js ) )
627
653
. with_context ( || format ! ( "failed to write `{}`" , js_path. display( ) ) ) ?;
628
654
629
- if self . typescript {
655
+ if gen . typescript {
630
656
let ts_path = js_path. with_extension ( "d.ts" ) ;
631
- fs:: write ( & ts_path, & self . ts )
657
+ fs:: write ( & ts_path, & gen . ts )
632
658
. with_context ( || format ! ( "failed to write `{}`" , ts_path. display( ) ) ) ?;
633
659
}
634
660
635
- if self . mode . nodejs ( ) {
661
+ if gen . mode . nodejs ( ) {
636
662
let js_path = wasm_path. with_extension ( extension) ;
637
- let shim = self . generate_node_wasm_import ( & self . module , & wasm_path) ;
663
+ let shim = gen . generate_node_wasm_import ( & self . module , & wasm_path) ;
638
664
fs:: write ( & js_path, shim)
639
665
. with_context ( || format ! ( "failed to write `{}`" , js_path. display( ) ) ) ?;
640
666
}
641
667
642
- if self . typescript {
668
+ if gen . typescript {
643
669
let ts_path = wasm_path. with_extension ( "d.ts" ) ;
644
670
let ts = wasm2es6js:: typescript ( & self . module ) ?;
645
671
fs:: write ( & ts_path, ts)
@@ -648,7 +674,9 @@ impl Output {
648
674
649
675
Ok ( ( ) )
650
676
}
677
+ }
651
678
679
+ impl JsGenerated {
652
680
fn generate_node_wasm_import ( & self , m : & Module , path : & Path ) -> String {
653
681
let mut imports = BTreeSet :: new ( ) ;
654
682
for import in m. imports . iter ( ) {
@@ -720,17 +748,14 @@ impl Output {
720
748
}
721
749
722
750
fn gc_module_and_adapters ( module : & mut Module ) {
723
- // First up we execute walrus's own gc passes, and this may enable us to
724
- // delete entries in the `implements` section of the nonstandard wasm
725
- // interface types section. (if the import is GC'd, then the implements
726
- // annotation is no longer needed).
727
- //
728
- // By deleting adapter functions that may enable us to further delete more
729
- // functions, so we run this in a loop until we don't actually delete any
730
- // adapter functions.
731
751
loop {
752
+ // Fist up, cleanup the native wasm module. Note that roots can come
753
+ // from custom sections, namely our wasm interface types custom section
754
+ // as well as the aux section.
732
755
walrus:: passes:: gc:: run ( module) ;
733
756
757
+ // ... and afterwards we can delete any `implements` directives for any
758
+ // imports that have been deleted.
734
759
let imports_remaining = module
735
760
. imports
736
761
. iter ( )
@@ -740,20 +765,30 @@ fn gc_module_and_adapters(module: &mut Module) {
740
765
. customs
741
766
. get_typed_mut :: < wit:: NonstandardWitSection > ( )
742
767
. unwrap ( ) ;
743
- let mut deleted_implements = Vec :: new ( ) ;
744
- section. implements . retain ( |pair| {
745
- if imports_remaining. contains ( & pair. 0 ) {
746
- true
747
- } else {
748
- deleted_implements. push ( pair. 2 ) ;
749
- false
750
- }
751
- } ) ;
752
- if deleted_implements. len ( ) == 0 {
768
+ section. implements . retain ( |pair| imports_remaining. contains ( & pair. 0 ) ) ;
769
+
770
+ // ... and after we delete the `implements` directive we try to
771
+ // delete some adapters themselves. If nothing is deleted, then we're
772
+ // good to go. If something is deleted though then we may have free'd up
773
+ // some functions in the main module to get deleted, so go again to gc
774
+ // things.
775
+ if !section. gc ( ) {
753
776
break ;
754
777
}
755
- for id in deleted_implements {
756
- section. adapters . remove ( & id) ;
757
- }
758
778
}
759
779
}
780
+
781
+ /// Returns a sorted iterator over a hash map, sorted based on key.
782
+ ///
783
+ /// The intention of this API is to be used whenever the iteration order of a
784
+ /// `HashMap` might affect the generated JS bindings. We want to ensure that the
785
+ /// generated output is deterministic and we do so by ensuring that iteration of
786
+ /// hash maps is consistently sorted.
787
+ fn sorted_iter < K , V > ( map : & HashMap < K , V > ) -> impl Iterator < Item = ( & K , & V ) >
788
+ where
789
+ K : Ord ,
790
+ {
791
+ let mut pairs = map. iter ( ) . collect :: < Vec < _ > > ( ) ;
792
+ pairs. sort_by_key ( |( k, _) | * k) ;
793
+ pairs. into_iter ( )
794
+ }
0 commit comments