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