Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/pulley.rs
3088 views
1
use std::ptr::NonNull;
2
use std::thread;
3
use wasmtime::Result;
4
use wasmtime::component::{self, Component};
5
use wasmtime::{
6
Caller, Config, Engine, Func, FuncType, Instance, Module, Store, Trap, Val, ValType,
7
};
8
use wasmtime_environ::TripleExt;
9
10
fn pulley_target() -> String {
11
target_lexicon::Triple::pulley_host().to_string()
12
}
13
14
fn pulley_config() -> Config {
15
let mut config = Config::new();
16
config.target(&pulley_target()).unwrap();
17
config
18
}
19
20
#[test]
21
fn can_compile_pulley_module() -> Result<()> {
22
let engine = Engine::new(&pulley_config())?;
23
Module::new(&engine, "(module)")?;
24
25
Ok(())
26
}
27
28
#[test]
29
fn can_deserialize_pulley_module() -> Result<()> {
30
let engine = Engine::new(&pulley_config())?;
31
let bytes = engine.precompile_module(b"(module)")?;
32
unsafe {
33
Module::deserialize(&engine, &bytes)?;
34
}
35
Ok(())
36
}
37
38
#[test]
39
fn pulley_wrong_architecture_is_rejected() -> Result<()> {
40
let mut config = Config::new();
41
// Intentionally swap pointer widths here to ensure pulley is the wrong one.
42
if cfg!(target_pointer_width = "64") {
43
config.target("pulley32").unwrap();
44
} else {
45
config.target("pulley64").unwrap();
46
}
47
48
// Creating `Module` should fail as we can't run the wrong architecture.
49
let engine = Engine::new(&config)?;
50
assert!(Module::new(&engine, "(module)").is_err());
51
52
// Precompiling should succeed but deserialization should fail because it's
53
// the wrong pointer width.
54
let engine = Engine::new(&config)?;
55
let bytes = engine.precompile_module(b"(module)")?;
56
unsafe {
57
assert!(Module::deserialize(&engine, &bytes).is_err());
58
}
59
Ok(())
60
}
61
62
// CLI subcommands should support `--target`
63
#[test]
64
#[cfg(not(miri))]
65
fn can_run_on_cli() -> Result<()> {
66
use crate::cli_tests::run_wasmtime;
67
run_wasmtime(&[
68
"--target",
69
&pulley_target(),
70
"tests/all/cli_tests/empty-module.wat",
71
])?;
72
run_wasmtime(&[
73
"run",
74
"--target",
75
&pulley_target(),
76
"tests/all/cli_tests/empty-module.wat",
77
])?;
78
Ok(())
79
}
80
81
fn provenance_test_config() -> Config {
82
let mut config = pulley_config();
83
config.wasm_function_references(true);
84
config.memory_reservation(1 << 20);
85
config.memory_guard_size(0);
86
config.signals_based_traps(false);
87
config.wasm_component_model_async(true);
88
config.wasm_component_model_async_builtins(true);
89
config.wasm_component_model_async_stackful(true);
90
config.wasm_component_model_threading(true);
91
config.wasm_component_model_error_context(true);
92
config
93
}
94
95
/// This is a one-size-fits-all test to test out pointer provenance in Pulley.
96
///
97
/// The goal of this test is to exercise an actual wasm module being run in
98
/// Pulley in MIRI to ensure that it's not undefined behavior. The main reason
99
/// we don't do this for the entire test suite is that Cranelift compilation in
100
/// MIRI is excessively slow to the point that it's not tenable to run. Thus
101
/// the way this test is run is a little nonstandard.
102
///
103
/// * The test here is ignored on MIRI by default. That means this runs on
104
/// native platforms otherwise though.
105
///
106
/// * A script `./ci/miri-provenance-test.sh` is provided to execute just this
107
/// one test. That will precompile the wasm module in question here using
108
/// native code, leaving a `*.cwasm` in place.
109
///
110
/// Thus in native code this compiles the module here just-in-time. On MIRI
111
/// this test must be run through the above script and this will deserialize
112
/// the file from disk. In the end we skip Cranelift on MIRI while still getting
113
/// to execute some wasm.
114
///
115
/// This test then has a wasm module with a number of "interesting" constructs
116
/// and instructions. The goal is to kind of do a dry run of interesting
117
/// shapes/sizes of what you can do with core wasm and ensure MIRI gives us a
118
/// clean bill of health.
119
#[test]
120
#[cfg_attr(miri, ignore)]
121
fn pulley_provenance_test() -> Result<()> {
122
let config = provenance_test_config();
123
let engine = Engine::new(&config)?;
124
let module = if cfg!(miri) {
125
unsafe { Module::deserialize_file(&engine, "./tests/all/pulley_provenance_test.cwasm")? }
126
} else {
127
Module::from_file(&engine, "./tests/all/pulley_provenance_test.wat")?
128
};
129
let mut store = Store::new(&engine, ());
130
let host_wrap = Func::wrap(&mut store, || (1_i32, 2_i32, 3_i32));
131
let host_new_ty = FuncType::new(
132
store.engine(),
133
vec![],
134
vec![ValType::I32, ValType::I32, ValType::I32],
135
);
136
let host_new = Func::new(&mut store, host_new_ty, |_, _params, results| {
137
results[0] = Val::I32(1);
138
results[1] = Val::I32(2);
139
results[2] = Val::I32(3);
140
Ok(())
141
});
142
let instance = Instance::new(&mut store, &module, &[host_wrap.into(), host_new.into()])?;
143
144
for func in [
145
"call-wasm",
146
"call-native-wrap",
147
"call-native-new",
148
"return-call-wasm",
149
"call_indirect-wasm",
150
] {
151
println!("testing func {func:?}");
152
let func = instance
153
.get_typed_func::<(), (i32, i32, i32)>(&mut store, func)
154
.unwrap();
155
let results = func.call(&mut store, ())?;
156
assert_eq!(results, (1, 2, 3));
157
}
158
159
let funcref = instance.get_func(&mut store, "call-wasm").unwrap();
160
for func in ["call_ref-wasm", "return_call_ref-wasm"] {
161
println!("testing func {func:?}");
162
let func = instance.get_typed_func::<Func, (i32, i32, i32)>(&mut store, func)?;
163
let results = func.call(&mut store, funcref)?;
164
assert_eq!(results, (1, 2, 3));
165
}
166
167
let trap = instance
168
.get_typed_func::<(), ()>(&mut store, "unreachable")?
169
.call(&mut store, ())
170
.unwrap_err()
171
.downcast::<Trap>()?;
172
assert_eq!(trap, Trap::UnreachableCodeReached);
173
174
let trap = instance
175
.get_typed_func::<(), i32>(&mut store, "divide-by-zero")?
176
.call(&mut store, ())
177
.unwrap_err()
178
.downcast::<Trap>()?;
179
assert_eq!(trap, Trap::IntegerDivisionByZero);
180
181
instance
182
.get_typed_func::<(), ()>(&mut store, "memory-intrinsics")?
183
.call(&mut store, ())?;
184
instance
185
.get_typed_func::<(), ()>(&mut store, "table-intrinsics")?
186
.call(&mut store, ())?;
187
instance
188
.get_typed_func::<(), ()>(&mut store, "table-intrinsics2")?
189
.call(&mut store, ())?;
190
191
let funcref = Func::wrap(&mut store, move |mut caller: Caller<'_, ()>| {
192
let func = instance.get_typed_func::<(), (i32, i32, i32)>(&mut caller, "call-wasm")?;
193
func.call(&mut caller, ())
194
});
195
let func = instance.get_typed_func::<Func, (i32, i32, i32)>(&mut store, "call_ref-wasm")?;
196
let results = func.call(&mut store, funcref)?;
197
assert_eq!(results, (1, 2, 3));
198
199
instance
200
.get_typed_func::<(), ()>(&mut store, "ref-func-myself")?
201
.call(&mut store, ())?;
202
203
Ok(())
204
}
205
206
#[test]
207
#[cfg_attr(miri, ignore)]
208
fn pulley_provenance_test_components() -> Result<()> {
209
let config = provenance_test_config();
210
let engine = Engine::new(&config)?;
211
let component = if cfg!(miri) {
212
unsafe {
213
Component::deserialize_file(
214
&engine,
215
"./tests/all/pulley_provenance_test_component.cwasm",
216
)?
217
}
218
} else {
219
Component::from_file(&engine, "./tests/all/pulley_provenance_test_component.wat")?
220
};
221
{
222
use wasmtime::component::{ComponentType, Lift, Lower};
223
224
#[derive(ComponentType, Lift, Lower, Clone, Copy, PartialEq, Debug)]
225
#[component(enum)]
226
#[repr(u8)]
227
enum E {
228
#[expect(dead_code, reason = "only testing other variants")]
229
A,
230
B,
231
#[expect(dead_code, reason = "only testing other variants")]
232
C,
233
}
234
235
let mut store = Store::new(&engine, ());
236
let mut linker = component::Linker::new(&engine);
237
linker.root().func_wrap("host-empty", |_, (): ()| Ok(()))?;
238
linker
239
.root()
240
.func_wrap("host-u32", |_, (value,): (u32,)| Ok((value,)))?;
241
linker
242
.root()
243
.func_wrap("host-enum", |_, (value,): (E,)| Ok((value,)))?;
244
linker
245
.root()
246
.func_wrap("host-option", |_, (value,): (Option<u8>,)| Ok((value,)))?;
247
linker
248
.root()
249
.func_wrap("host-result", |_, (value,): (Result<u16, i64>,)| {
250
Ok((value,))
251
})?;
252
linker
253
.root()
254
.func_wrap("host-string", |_, (value,): (String,)| Ok((value,)))?;
255
linker
256
.root()
257
.func_wrap("host-list", |_, (value,): (Vec<String>,)| Ok((value,)))?;
258
let instance = linker.instantiate(&mut store, &component)?;
259
260
let guest_empty = instance.get_typed_func::<(), ()>(&mut store, "guest-empty")?;
261
let guest_u32 = instance.get_typed_func::<(u32,), (u32,)>(&mut store, "guest-u32")?;
262
let guest_enum = instance.get_typed_func::<(E,), (E,)>(&mut store, "guest-enum")?;
263
let guest_option =
264
instance.get_typed_func::<(Option<u8>,), (Option<u8>,)>(&mut store, "guest-option")?;
265
let guest_result = instance.get_typed_func::<(Result<u16, i64>,), (Result<u16, i64>,)>(
266
&mut store,
267
"guest-result",
268
)?;
269
let guest_string =
270
instance.get_typed_func::<(&str,), (String,)>(&mut store, "guest-string")?;
271
let guest_list =
272
instance.get_typed_func::<(&[&str],), (Vec<String>,)>(&mut store, "guest-list")?;
273
274
guest_empty.call(&mut store, ())?;
275
276
let (result,) = guest_u32.call(&mut store, (42,))?;
277
assert_eq!(result, 42);
278
279
let (result,) = guest_enum.call(&mut store, (E::B,))?;
280
assert_eq!(result, E::B);
281
282
let (result,) = guest_option.call(&mut store, (None,))?;
283
assert_eq!(result, None);
284
let (result,) = guest_option.call(&mut store, (Some(200),))?;
285
assert_eq!(result, Some(200));
286
287
let (result,) = guest_result.call(&mut store, (Ok(10),))?;
288
assert_eq!(result, Ok(10));
289
let (result,) = guest_result.call(&mut store, (Err(i64::MIN),))?;
290
assert_eq!(result, Err(i64::MIN));
291
292
let (result,) = guest_string.call(&mut store, ("",))?;
293
assert_eq!(result, "");
294
let (result,) = guest_string.call(&mut store, ("hello",))?;
295
assert_eq!(result, "hello");
296
297
let (result,) = guest_list.call(&mut store, (&[],))?;
298
assert!(result.is_empty());
299
let (result,) = guest_list.call(&mut store, (&["a", "", "b", "c"],))?;
300
assert_eq!(result, ["a", "", "b", "c"]);
301
302
instance
303
.get_typed_func::<(), ()>(&mut store, "resource-intrinsics")?
304
.call(&mut store, ())?;
305
}
306
{
307
use wasmtime::component::Val;
308
let mut store = Store::new(&engine, ());
309
let mut linker = component::Linker::new(&engine);
310
linker
311
.root()
312
.func_new("host-empty", |_, _, _args, _results| Ok(()))?;
313
linker.root().func_new("host-u32", |_, _, args, results| {
314
results[0] = args[0].clone();
315
Ok(())
316
})?;
317
linker.root().func_new("host-enum", |_, _, args, results| {
318
results[0] = args[0].clone();
319
Ok(())
320
})?;
321
linker
322
.root()
323
.func_new("host-option", |_, _, args, results| {
324
results[0] = args[0].clone();
325
Ok(())
326
})?;
327
linker
328
.root()
329
.func_new("host-result", |_, _, args, results| {
330
results[0] = args[0].clone();
331
Ok(())
332
})?;
333
linker
334
.root()
335
.func_new("host-string", |_, _, args, results| {
336
results[0] = args[0].clone();
337
Ok(())
338
})?;
339
linker.root().func_new("host-list", |_, _, args, results| {
340
results[0] = args[0].clone();
341
Ok(())
342
})?;
343
let instance = linker.instantiate(&mut store, &component)?;
344
345
let guest_empty = instance.get_func(&mut store, "guest-empty").unwrap();
346
let guest_u32 = instance.get_func(&mut store, "guest-u32").unwrap();
347
let guest_enum = instance.get_func(&mut store, "guest-enum").unwrap();
348
let guest_option = instance.get_func(&mut store, "guest-option").unwrap();
349
let guest_result = instance.get_func(&mut store, "guest-result").unwrap();
350
let guest_string = instance.get_func(&mut store, "guest-string").unwrap();
351
let guest_list = instance.get_func(&mut store, "guest-list").unwrap();
352
353
let mut results = [];
354
guest_empty.call(&mut store, &[], &mut results)?;
355
356
let mut results = [Val::U32(0)];
357
guest_u32.call(&mut store, &[Val::U32(42)], &mut results)?;
358
assert_eq!(results[0], Val::U32(42));
359
360
guest_enum.call(&mut store, &[Val::Enum("B".into())], &mut results)?;
361
assert_eq!(results[0], Val::Enum("B".into()));
362
363
guest_option.call(&mut store, &[Val::Option(None)], &mut results)?;
364
assert_eq!(results[0], Val::Option(None));
365
guest_option.call(
366
&mut store,
367
&[Val::Option(Some(Box::new(Val::U8(201))))],
368
&mut results,
369
)?;
370
assert_eq!(results[0], Val::Option(Some(Box::new(Val::U8(201)))));
371
372
guest_result.call(
373
&mut store,
374
&[Val::Result(Ok(Some(Box::new(Val::U16(20)))))],
375
&mut results,
376
)?;
377
assert_eq!(results[0], Val::Result(Ok(Some(Box::new(Val::U16(20))))));
378
guest_result.call(
379
&mut store,
380
&[Val::Result(Err(Some(Box::new(Val::S64(i64::MAX)))))],
381
&mut results,
382
)?;
383
assert_eq!(
384
results[0],
385
Val::Result(Err(Some(Box::new(Val::S64(i64::MAX)))))
386
);
387
388
guest_string.call(&mut store, &[Val::String("B".into())], &mut results)?;
389
assert_eq!(results[0], Val::String("B".into()));
390
guest_string.call(&mut store, &[Val::String("".into())], &mut results)?;
391
assert_eq!(results[0], Val::String("".into()));
392
393
guest_list.call(&mut store, &[Val::List(Vec::new())], &mut results)?;
394
assert_eq!(results[0], Val::List(Vec::new()));
395
}
396
397
Ok(())
398
}
399
400
async fn sleep(duration: std::time::Duration) {
401
let (tx, rx) = tokio::sync::oneshot::channel();
402
thread::spawn(move || {
403
thread::sleep(duration);
404
tx.send(()).unwrap();
405
});
406
rx.await.unwrap()
407
}
408
409
#[tokio::test]
410
#[cfg_attr(miri, ignore)]
411
async fn pulley_provenance_test_async_components() -> Result<()> {
412
let config = provenance_test_config();
413
let engine = Engine::new(&config)?;
414
let component = if cfg!(miri) {
415
unsafe {
416
Component::deserialize_file(
417
&engine,
418
"./tests/all/pulley_provenance_test_async_component.cwasm",
419
)?
420
}
421
} else {
422
Component::from_file(
423
&engine,
424
"./tests/all/pulley_provenance_test_async_component.wat",
425
)?
426
};
427
{
428
let mut store = Store::new(&engine, ());
429
let mut linker = component::Linker::new(&engine);
430
linker.root().func_wrap_concurrent("sleep", |_, ()| {
431
Box::pin(async {
432
sleep(std::time::Duration::from_millis(10)).await;
433
Ok(())
434
})
435
})?;
436
437
let instance = linker.instantiate_async(&mut store, &component).await?;
438
439
let run = instance.get_typed_func::<(), ()>(&mut store, "run-stackless")?;
440
store
441
.run_concurrent(async move |accessor| {
442
wasmtime::error::Ok(run.call_concurrent(accessor, ()).await?.0)
443
})
444
.await??;
445
446
let run = instance.get_typed_func::<(), ()>(&mut store, "run-stackful")?;
447
store
448
.run_concurrent(async move |accessor| {
449
wasmtime::error::Ok(run.call_concurrent(accessor, ()).await?.0)
450
})
451
.await??;
452
453
let run = instance.get_typed_func::<(), ()>(&mut store, "run-stackless-stackless")?;
454
store
455
.run_concurrent(async move |accessor| {
456
wasmtime::error::Ok(run.call_concurrent(accessor, ()).await?.0)
457
})
458
.await??;
459
460
let run = instance.get_typed_func::<(), ()>(&mut store, "run-stackful-stackful")?;
461
store
462
.run_concurrent(async move |accessor| {
463
wasmtime::error::Ok(run.call_concurrent(accessor, ()).await?.0)
464
})
465
.await??;
466
}
467
468
Ok(())
469
}
470
471
#[test]
472
#[cfg(not(miri))]
473
fn enabling_debug_info_doesnt_break_anything() -> Result<()> {
474
let mut config = pulley_config();
475
config.debug_info(true);
476
let engine = Engine::new(&config)?;
477
assert!(Module::from_file(&engine, "./tests/all/cli_tests/greeter_command.wat").is_err());
478
Ok(())
479
}
480
481
// Ensure that Pulley doesn't require that the input image is aligned at any
482
// particular boundary, namely for now this double-checks that the `unaligned`
483
// feature of the `object` crate is enabled.
484
#[test]
485
fn decode_unaligned() -> Result<()> {
486
let engine = Engine::new(&pulley_config())?;
487
let mut bytes = Module::new(&engine, "(module)")?.serialize()?;
488
489
for i in 0..10 {
490
let serialized = &bytes[i..];
491
unsafe {
492
Module::deserialize_raw(&engine, NonNull::from(serialized))?;
493
}
494
bytes.insert(0, 0);
495
}
496
497
Ok(())
498
}
499
500