• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

henrythasler / rust-tiny-wasm / 26400542397

25 May 2026 12:29PM UTC coverage: 95.172% (-0.3%) from 95.514%
26400542397

push

github

henrythasler
add trap handler jit code

Co-authored-by: Copilot <copilot@github.com>

67 of 81 new or added lines in 7 files covered. (82.72%)

1 existing line in 1 file now uncovered.

1715 of 1802 relevant lines covered (95.17%)

70.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.38
/src/runtime.rs
1
use memmap2::{Mmap, MmapMut};
2
use std::mem;
3

4
use super::assembler::*;
5
use super::*;
6
// use debugger::*;
7

8
// mod debugger;
9

10
#[repr(u64)]
11
#[non_exhaustive]
12
#[derive(Debug, PartialEq, Eq)]
13
pub enum WasmReturnCode {
14
    Ok = 0,
15
    Trap = 1,
16
}
17

18
#[non_exhaustive]
19
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20
pub enum TrapCode {
21
    None = 0,
22
    StackOverflow = 1,
23
    MemoryOutOfBounds = 2,
24
    HeapMisaligned = 3,
25
    TableOutOfBounds = 4,
26
    IndirectCallToNull = 5,
27
    BadSignature = 6,
28
    IntegerOverflow = 7,
29
    IntegerDivisionByZero = 8,
30
    BadConversionToInteger = 9,
31
    UnreachableCodeReached = 10,
32
    Interrupt = 11,
33
}
34

35
impl TrapCode {
36
    pub fn from_code(code: i64) -> Self {
2✔
37
        match code {
2✔
38
            0 => TrapCode::None,
1✔
NEW
39
            1 => TrapCode::StackOverflow,
×
NEW
40
            2 => TrapCode::MemoryOutOfBounds,
×
NEW
41
            3 => TrapCode::HeapMisaligned,
×
NEW
42
            4 => TrapCode::TableOutOfBounds,
×
NEW
43
            5 => TrapCode::IndirectCallToNull,
×
NEW
44
            6 => TrapCode::BadSignature,
×
NEW
45
            7 => TrapCode::IntegerOverflow,
×
NEW
46
            8 => TrapCode::IntegerDivisionByZero,
×
NEW
47
            9 => TrapCode::BadConversionToInteger,
×
NEW
48
            10 => TrapCode::UnreachableCodeReached,
×
NEW
49
            11 => TrapCode::Interrupt,
×
50
            _ => panic!("Unknown trap code: {}", code),
1✔
51
        }
52
    }
1✔
53

54
    pub const ALL: &'static [TrapCode] = &[
55
        TrapCode::None,
56
        TrapCode::StackOverflow,
57
        TrapCode::MemoryOutOfBounds,
58
        TrapCode::HeapMisaligned,
59
        TrapCode::TableOutOfBounds,
60
        TrapCode::IndirectCallToNull,
61
        TrapCode::BadSignature,
62
        TrapCode::IntegerOverflow,
63
        TrapCode::IntegerDivisionByZero,
64
        TrapCode::BadConversionToInteger,
65
        TrapCode::UnreachableCodeReached,
66
        TrapCode::Interrupt,
67
    ];
68
}
69

70
#[derive(Debug, PartialEq, Eq)]
71
pub enum TinyWasmError {
72
    Io(std::io::ErrorKind),
73
    Parser(String),
74
    Compiler(String),
75
    Runtime(String),
76
    Trap(TrapCode),
77
}
78

79
impl From<std::io::Error> for TinyWasmError {
80
    fn from(err: std::io::Error) -> Self {
1✔
81
        TinyWasmError::Io(err.kind())
1✔
82
    }
1✔
83
}
84

85
impl From<wasmparser::BinaryReaderError> for TinyWasmError {
86
    fn from(err: wasmparser::BinaryReaderError) -> Self {
2✔
87
        TinyWasmError::Parser(err.message().to_string())
2✔
88
    }
2✔
89
}
90

91
#[cfg(target_arch = "aarch64")]
92
unsafe fn clear_cache(addr: *mut u8, len: usize) {
20✔
93
    // Use libc function
94
    unsafe extern "C" {
95
        fn __clear_cache(start: *mut u8, end: *mut u8);
96
    }
97
    unsafe {
20✔
98
        __clear_cache(addr, addr.add(len));
20✔
99
    }
20✔
100
}
20✔
101

102
#[derive(Debug)]
103
pub struct Callable<P, R> {
104
    ptr: *const u8,
105
    _marker: std::marker::PhantomData<fn(P) -> R>,
106
}
107

108
impl<P, R> Callable<P, R> {
109
    /// This function casts the memory address to a function.
110
    ///
111
    /// # Safety
112
    /// The user **MUST** ensure that the signature used for the
113
    /// generic matches the actual function that is called.
114
    pub unsafe fn new(ptr: *const u8) -> Self
67✔
115
    where
67✔
116
        R: Into64,
67✔
117
    {
118
        Self {
67✔
119
            ptr,
67✔
120
            _marker: std::marker::PhantomData,
67✔
121
        }
67✔
122
    }
67✔
123
}
124

125
macro_rules! impl_call {
126
    () => {
127
        impl<R> Callable<(), R>
128
        where
129
        R: Into64,
130
        {
131
            pub fn call(&self) -> Result<R> {
31✔
132
                let res = unsafe {
31✔
133
                let wasm_func: extern "C" fn() -> (u64, R) =
31✔
134
                    std::mem::transmute(self.ptr);
31✔
135
                // set_breakpoint();
136
                wasm_func()
31✔
137
                };
138
                let result: Result<R> = match res.0 {
31✔
139
                    0 => Ok(res.1),
29✔
140
                    1 => Err(TinyWasmError::Trap(TrapCode::from_code(res.1.into()))),
1✔
141
                    _ => Err(TinyWasmError::Runtime(format!(
1✔
142
                        "Invalid result tag: {:?}",
1✔
143
                        res.0
1✔
144
                    ))),
1✔
145
                };
146
                result
31✔
147
            }
31✔
148
        }
149
    };
150

151
    ($($arg:ident),+) => {
152
        impl<$($arg,)+ R> Callable<($($arg,)+), R>
153
        where
154
        R: Into64,
155
         {
156
            #[allow(non_snake_case)]
157
            #[allow(clippy::too_many_arguments)]
158
            /// This function calls the JIT-compiled WebAssembly function with the provided arguments and returns the result.
159
            ///
160
            /// The function is expected to return a tuple of (value, tag), where 'value' is the actual return value and 'tag'
161
            /// indicates whether the call was successful (0) or resulted in a trap (1).
162
            /// The result is returned as a `Result<R>`, where `R` is the expected return type of the WebAssembly function.
163
            pub fn call(
172✔
164
                &self,
172✔
165
                $($arg: $arg),+
172✔
166
            ) -> Result<R> {
172✔
167
                let res = unsafe {
172✔
168
                let wasm_func: extern "C" fn($($arg),+) -> (u64, R) =
172✔
169
                    std::mem::transmute(self.ptr);
172✔
170
                // set_breakpoint();
171
                wasm_func($($arg),+)
172✔
172
                };
173
                let result: Result<R> = match res.0 {
172✔
174
                    0 => Ok(res.1),
172✔
NEW
175
                    1 => Err(TinyWasmError::Trap(TrapCode::from_code(res.1.into()))),
×
176
                    _ => Err(TinyWasmError::Runtime(format!(
×
NEW
177
                        "Invalid result tag: {:?}",
×
NEW
178
                        res.0
×
UNCOV
179
                    ))),
×
180
                };
181
                result
172✔
182
            }
172✔
183
        }
184
    };
185
}
186

187
impl_call!();
188
impl_call!(A);
189
impl_call!(A, B);
190
impl_call!(A, B, C);
191
impl_call!(A, B, C, D);
192
impl_call!(A, B, C, D, E);
193
impl_call!(A, B, C, D, E, F);
194
impl_call!(A, B, C, D, E, F, G);
195
impl_call!(A, B, C, D, E, F, G, H);
196

197
pub trait Into64 {
198
    fn into(self) -> i64;
199
}
200

201
impl Into64 for i32 {
202
    fn into(self) -> i64 {
2✔
203
        self as i64
2✔
204
    }
2✔
205
}
206

207
impl Into64 for i64 {
208
    fn into(self) -> i64 {
1✔
209
        self
1✔
210
    }
1✔
211
}
212

213
// For void functions:
214
impl Into64 for () {
215
    fn into(self) -> i64 {
1✔
216
        0
1✔
217
    } // or panic if you never use it
1✔
218
}
219

220
#[derive(Debug)]
221
pub struct Runtime {
222
    machinecode: Mmap,
223
    functions: Vec<WasmFunction>,
224
}
225

226
impl Runtime {
227
    pub fn get_function<P, R>(&self, name: &str) -> Result<Callable<P, R>>
69✔
228
    where
69✔
229
        R: Into64,
69✔
230
    {
231
        let function =
67✔
232
            self.functions
69✔
233
                .iter()
69✔
234
                .find(|&x| x.name == name)
281✔
235
                .ok_or(TinyWasmError::Runtime(format!(
69✔
236
                    "Function '{}' not found in module exports",
69✔
237
                    name
69✔
238
                )))?;
69✔
239
        let ptr = self
67✔
240
            .machinecode
67✔
241
            .as_ptr()
67✔
242
            .wrapping_add(function.offset * aarch64::INSTRUCTION_SIZE);
67✔
243

244
        assert!(!ptr.is_null());
67✔
245
        assert_eq!((ptr as usize) % mem::align_of::<extern "C" fn(P) -> R>(), 0);
67✔
246
        assert_eq!(
67✔
247
            mem::size_of::<extern "C" fn(P) -> R>(),
248
            mem::size_of::<*const u8>()
249
        );
250

251
        let callable = unsafe { Callable::<P, R>::new(ptr) };
67✔
252
        Ok(callable)
67✔
253
    }
69✔
254
}
255

256
pub fn instantiate_module(module: &LinkedModule) -> Result<Runtime> {
21✔
257
    // Allocate executable memory and copy JIT code into that region
258
    let bytes = bytemuck::cast_slice(&module.machinecode);
21✔
259
    if bytes.is_empty() {
21✔
260
        return Err(TinyWasmError::Runtime(String::from("JIT code is empty")));
1✔
261
    }
20✔
262
    let mut mmap = MmapMut::map_anon(bytes.len()).expect("map_anon() failed");
20✔
263
    mmap.copy_from_slice(bytes);
20✔
264

265
    // clear instruction cache on aarch64 target
266
    #[cfg(target_arch = "aarch64")]
267
    unsafe {
20✔
268
        clear_cache(mmap.as_mut_ptr(), mmap.len());
20✔
269
    }
20✔
270

271
    // set execution permissions
272
    let machinecode = mmap.make_exec().expect("make_exec() failed");
20✔
273
    Ok(Runtime {
20✔
274
        machinecode,
20✔
275
        functions: module.functions.to_vec(),
20✔
276
    })
20✔
277
}
21✔
278

279
#[cfg(test)]
280
mod tests {
281
    use super::*;
282

283
    #[test]
284
    fn into64_test() {
1✔
285
        assert_eq!(Into64::into(1i64), 1);
1✔
286
        assert_eq!(Into64::into(1i32), 1);
1✔
287
        assert_eq!(Into64::into(()), 0);
1✔
288
    }
1✔
289

290
    #[test]
291
    #[should_panic]
292
    fn unknown_trapcode() {
1✔
293
        let _ = TrapCode::from_code(0xdead);
1✔
294
    }
1✔
295

296
    #[test]
297
    fn simple_jit_code() -> Result<()> {
1✔
298
        let module = LinkedModule::new(
1✔
299
            vec![0x0b000020, 0xd65f03c0],
1✔
300
            vec![WasmFunction {
1✔
301
                name: String::from("test"),
1✔
302
                offset: 0,
1✔
303
                length: 2,
1✔
304
            }],
1✔
305
            None,
1✔
306
        );
307
        let instance = instantiate_module(&module)?;
1✔
308
        let _ = instance.get_function::<(), i32>("test")?;
1✔
309
        Ok(())
1✔
310
    }
1✔
311

312
    #[test]
313
    fn void_fn() -> Result<()> {
1✔
314
        let module = LinkedModule::new(
1✔
315
            // does not return anything
316
            vec![0xAA1F03E0, 0xAA1F03E1, 0xd65f03c0], // [MOV X0, XZR; MOV X1, XZR; RET]
1✔
317
            vec![WasmFunction {
1✔
318
                name: String::from("void"),
1✔
319
                offset: 0,
1✔
320
                length: 3,
1✔
321
            }],
1✔
322
            None,
1✔
323
        );
324
        let instance = instantiate_module(&module)?;
1✔
325
        let func = instance.get_function::<(), ()>("void")?;
1✔
326
        let res = func.call()?;
1✔
327
        assert_eq!(res, ());
1✔
328
        Ok(())
1✔
329
    }
1✔
330

331
    #[test]
332
    fn invalid_result_tag() -> Result<()> {
1✔
333
        let module = LinkedModule::new(
1✔
334
            // result tag in X0 is 255, which is invalid
335
            vec![0xD2801FE0, 0xD2801FE1, 0xd65f03c0], // [MOV X0, 0xFF; MOV X1, 0xFF; RET]
1✔
336
            vec![WasmFunction {
1✔
337
                name: String::from("invalid_result"),
1✔
338
                offset: 0,
1✔
339
                length: 3,
1✔
340
            }],
1✔
341
            None,
1✔
342
        );
343
        let instance = instantiate_module(&module)?;
1✔
344
        let func = instance.get_function::<(), i64>("invalid_result")?;
1✔
345
        let res = func.call();
1✔
346
        assert!(
1✔
347
            matches!(res.unwrap_err(), TinyWasmError::Runtime(msg) if msg.contains("result tag"))
1✔
348
        );
349
        Ok(())
1✔
350
    }
1✔
351

352
    #[test]
353
    fn trap_code() -> Result<()> {
1✔
354
        let module = LinkedModule::new(
1✔
355
            // Tag in X0 is 1, which indicates a trap
356
            // Value in X1 is 0, which is TrapCode::None
357
            vec![0xAA1F03E1, 0xD2800020, 0xd65f03c0], // [MOV X1, XZR; MOV X0, 1; RET]
1✔
358
            vec![WasmFunction {
1✔
359
                name: String::from("trap_code"),
1✔
360
                offset: 0,
1✔
361
                length: 3,
1✔
362
            }],
1✔
363
            None,
1✔
364
        );
365
        let instance = instantiate_module(&module)?;
1✔
366
        let func = instance.get_function::<(), i32>("trap_code")?;
1✔
367
        let res = func.call();
1✔
368
        assert!(matches!(res.unwrap_err(), TinyWasmError::Trap(value) if value==TrapCode::None));
1✔
369
        Ok(())
1✔
370
    }
1✔
371

372
    #[test]
373
    fn invalid_jit_code() -> Result<()> {
1✔
374
        let module = LinkedModule::new(
1✔
375
            vec![],
1✔
376
            vec![WasmFunction {
1✔
377
                name: String::from("empty"),
1✔
378
                offset: 0,
1✔
379
                length: 0,
1✔
380
            }],
1✔
381
            None,
1✔
382
        );
383
        assert_eq!(
1✔
384
            instantiate_module(&module).unwrap_err(),
1✔
385
            TinyWasmError::Runtime(String::from("JIT code is empty"))
1✔
386
        );
387
        Ok(())
1✔
388
    }
1✔
389

390
    #[test]
391
    fn unknown_function() -> Result<()> {
1✔
392
        let module = LinkedModule::new(
1✔
393
            vec![0x0b000020, 0xd65f03c0],
1✔
394
            vec![WasmFunction {
1✔
395
                name: String::from("test"),
1✔
396
                offset: 0,
1✔
397
                length: 2,
1✔
398
            }],
1✔
399
            None,
1✔
400
        );
401
        let instance = instantiate_module(&module)?;
1✔
402
        assert_eq!(
1✔
403
            instance.get_function::<(), i32>("unknown").unwrap_err(),
1✔
404
            TinyWasmError::Runtime(String::from(
1✔
405
                "Function 'unknown' not found in module exports"
1✔
406
            ))
1✔
407
        );
408
        Ok(())
1✔
409
    }
1✔
410
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc