1- use std:: sync:: { Arc , Mutex , Once } ;
1+ use std:: collections:: HashMap ;
2+ use std:: env;
3+ use std:: ops:: Deref ;
4+ use std:: path:: Path ;
5+ use std:: sync:: { Arc , LazyLock , Once } ;
26use std:: time:: Duration ;
37
4- use anyhow:: Result ;
8+ use anyhow:: { Result , anyhow , bail } ;
59use futures:: stream:: { FuturesUnordered , TryStreamExt } ;
610use tokio:: fs;
11+ use tokio:: sync:: Mutex ;
712use wasm_compose:: composer:: ComponentComposer ;
813use wasmtime:: component:: { Component , Linker , ResourceTable } ;
914use wasmtime:: { Config , Engine , Store } ;
@@ -20,7 +25,16 @@ pub fn config() -> Config {
2025 init_logger ( ) ;
2126
2227 let mut config = Config :: new ( ) ;
23- config. cranelift_debug_verifier ( true ) ;
28+ if env:: var_os ( "MIRI_TEST_CWASM_DIR" ) . is_some ( ) {
29+ config. target ( "pulley64" ) . unwrap ( ) ;
30+ config. memory_reservation ( 1 << 20 ) ;
31+ config. memory_guard_size ( 0 ) ;
32+ config. signals_based_traps ( false ) ;
33+ config. debug_info ( false ) ;
34+ } else {
35+ config. cranelift_debug_verifier ( true ) ;
36+ config. debug_info ( true ) ;
37+ }
2438 config. wasm_component_model ( true ) ;
2539 config. wasm_component_model_async ( true ) ;
2640 config. wasm_component_model_async_builtins ( true ) ;
@@ -34,7 +48,7 @@ pub fn config() -> Config {
3448///
3549/// a is the "root" component, and b is composed into it
3650#[ allow( unused) ]
37- pub async fn compose ( a : & [ u8 ] , b : & [ u8 ] ) -> Result < Vec < u8 > > {
51+ async fn compose ( a : & [ u8 ] , b : & [ u8 ] ) -> Result < Vec < u8 > > {
3852 let dir = tempfile:: tempdir ( ) ?;
3953
4054 let a_file = dir. path ( ) . join ( "a.wasm" ) ;
@@ -54,18 +68,111 @@ pub async fn compose(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
5468 . compose ( )
5569}
5670
71+ pub async fn make_component ( engine : & Engine , components : & [ & str ] ) -> Result < Component > {
72+ fn cwasm_name ( components : & [ & str ] ) -> Result < String > {
73+ if components. is_empty ( ) {
74+ Err ( anyhow ! ( "expected at least one path" ) )
75+ } else {
76+ let names = components
77+ . iter ( )
78+ . map ( |& path| {
79+ let path = Path :: new ( path) ;
80+ if let Some ( name) = path. file_name ( ) {
81+ Ok ( name)
82+ } else {
83+ Err ( anyhow ! (
84+ "expected path with at least two components; got: {}" ,
85+ path. display( )
86+ ) )
87+ }
88+ } )
89+ . collect :: < Result < Vec < _ > > > ( ) ?;
90+
91+ Ok ( format ! (
92+ "{}.cwasm" ,
93+ names
94+ . iter( )
95+ . map( |name| { name. to_str( ) . unwrap( ) } )
96+ . collect:: <Vec <_>>( )
97+ . join( "+" )
98+ ) )
99+ }
100+ }
101+
102+ async fn compile ( engine : & Engine , components : & [ & str ] ) -> Result < Vec < u8 > > {
103+ match components {
104+ [ component] => engine. precompile_component ( & fs:: read ( component) . await ?) ,
105+ [ a, b] => engine
106+ . precompile_component ( & compose ( & fs:: read ( a) . await ?, & fs:: read ( b) . await ?) . await ?) ,
107+ _ => Err ( anyhow ! ( "expected one or two paths" ) ) ,
108+ }
109+ }
110+
111+ async fn load ( engine : & Engine , components : & [ & str ] ) -> Result < Vec < u8 > > {
112+ let cwasm_path = if let Some ( cwasm_dir) = & env:: var_os ( "MIRI_TEST_CWASM_DIR" ) {
113+ Some ( Path :: new ( cwasm_dir) . join ( cwasm_name ( components) ?) )
114+ } else {
115+ None
116+ } ;
117+
118+ if let Some ( cwasm_path) = & cwasm_path {
119+ if let Ok ( compiled) = fs:: read ( cwasm_path) . await {
120+ return Ok ( compiled) ;
121+ }
122+ }
123+
124+ if cfg ! ( miri) {
125+ bail ! (
126+ "Running these tests with miri requires precompiled .cwasm files.\n \
127+ Please set the `MIRI_TEST_CWASM_DIR` environment variable to the\n \
128+ absolute path of a valid directory, then run the test(s)\n \
129+ _without_ miri, and finally run them again _with_ miri."
130+ )
131+ }
132+
133+ let compiled = compile ( engine, components) . await ?;
134+ if let Some ( cwasm_path) = & cwasm_path {
135+ fs:: write ( cwasm_path, & compiled) . await ?;
136+ }
137+ Ok ( compiled)
138+ }
139+
140+ static CACHE : LazyLock < Mutex < HashMap < Vec < String > , Arc < Mutex < Option < Arc < Vec < u8 > > > > > > > > =
141+ LazyLock :: new ( || Mutex :: new ( HashMap :: new ( ) ) ) ;
142+
143+ let compiled = {
144+ let entry = CACHE
145+ . lock ( )
146+ . await
147+ . entry ( components. iter ( ) . map ( |& s| s. to_owned ( ) ) . collect ( ) )
148+ . or_insert_with ( || Arc :: new ( Mutex :: new ( None ) ) )
149+ . clone ( ) ;
150+
151+ let mut entry = entry. lock ( ) . await ;
152+ if let Some ( component) = entry. deref ( ) {
153+ component. clone ( )
154+ } else {
155+ let component = Arc :: new ( load ( engine, components) . await ?) ;
156+ * entry = Some ( component. clone ( ) ) ;
157+ component
158+ }
159+ } ;
160+
161+ Ok ( unsafe { Component :: deserialize ( & engine, & * compiled) ? } )
162+ }
163+
57164#[ allow( unused) ]
58- pub async fn test_run ( component : & [ u8 ] ) -> Result < ( ) > {
59- test_run_with_count ( component , 3 ) . await
165+ pub async fn test_run ( components : & [ & str ] ) -> Result < ( ) > {
166+ test_run_with_count ( components , 3 ) . await
60167}
61168
62- pub async fn test_run_with_count ( component : & [ u8 ] , count : usize ) -> Result < ( ) > {
169+ pub async fn test_run_with_count ( components : & [ & str ] , count : usize ) -> Result < ( ) > {
63170 let mut config = config ( ) ;
64171 config. epoch_interruption ( true ) ;
65172
66173 let engine = Engine :: new ( & config) ?;
67174
68- let component = Component :: new ( & engine, component ) ?;
175+ let component = make_component ( & engine, components ) . await ?;
69176
70177 let mut linker = Linker :: new ( & engine) ;
71178
@@ -90,7 +197,7 @@ pub async fn test_run_with_count(component: &[u8], count: usize) -> Result<()> {
90197 wasi : WasiCtxBuilder :: new ( ) . inherit_stdio ( ) . build ( ) ,
91198 table : ResourceTable :: default ( ) ,
92199 continue_ : false ,
93- wakers : Arc :: new ( Mutex :: new ( None ) ) ,
200+ wakers : Arc :: new ( std :: sync :: Mutex :: new ( None ) ) ,
94201 } ,
95202 ) ;
96203 store. set_epoch_deadline ( 1 ) ;
0 commit comments