Skip to main content

hara_native/jit/
native.rs

1//! Optional machine-code backend. Trace IR is lowered to a tiny wasm module
2//! and Wasmtime/Cranelift compiles that module to host code. This reuses the
3//! runtime's existing native Cranelift dependency and adds nothing to wasm or
4//! default builds.
5
6use super::{ExitReason, ExitSnapshot, Trace, TraceBackend, TraceOp, TraceOutcome, TraceValue};
7use crate::core::IntrinsicOp;
8use std::cell::RefCell;
9use std::collections::{HashMap, HashSet};
10use wasmtime::{Engine, Instance, Memory, Module, Store, TypedFunc};
11
12const MAX_CACHED_MODULES: usize = 128;
13const WASM_PAGE_BYTES: usize = 65_536;
14const MAX_TRACE_MEMORY_BYTES: usize = 16 * 1024 * 1024;
15
16thread_local! {
17    static NATIVE_ENGINE: Engine = Engine::default();
18    static MODULE_CACHE: RefCell<HashMap<Vec<u8>, Module>> = RefCell::new(HashMap::new());
19}
20
21pub struct NativeTrace {
22    store: Store<()>,
23    memory: Memory,
24    run: TypedFunc<i32, i32>,
25    trace: Trace,
26    local_count: usize,
27    checkpoint_start: usize,
28    heap_start: usize,
29}
30
31pub struct NativeBackend {
32    engine: Engine,
33}
34
35impl Default for NativeBackend {
36    fn default() -> Self {
37        Self {
38            engine: NATIVE_ENGINE.with(Clone::clone),
39        }
40    }
41}
42
43impl TraceBackend for NativeBackend {
44    type Compiled = NativeTrace;
45
46    fn compile(&mut self, trace: &Trace) -> Result<NativeTrace, String> {
47        let local_count = trace
48            .operations
49            .iter()
50            .filter_map(|operation| match operation {
51                TraceOp::GuardLocalI64 { local }
52                | TraceOp::GuardLocalBool { local }
53                | TraceOp::GuardLocalNil { local }
54                | TraceOp::GuardLocalVectorI64 { local }
55                | TraceOp::LoadLocal { local }
56                | TraceOp::StoreLocal { local } => Some(*local as usize + 1),
57                _ => None,
58            })
59            .max()
60            .unwrap_or(0);
61        let (constant_offsets, checkpoint_start, heap_start) = constant_layout(trace, local_count)?;
62        let wasm = lower(trace, local_count, checkpoint_start, &constant_offsets)?;
63        let module = MODULE_CACHE.with(|cache| -> Result<Module, String> {
64            if let Some(module) = cache.borrow().get(&wasm) {
65                return Ok(module.clone());
66            }
67            let module = Module::new(&self.engine, &wasm).map_err(|error| format!("{error:?}"))?;
68            let mut cache = cache.borrow_mut();
69            if cache.len() >= MAX_CACHED_MODULES {
70                cache.clear();
71            }
72            cache.insert(wasm, module.clone());
73            Ok(module)
74        })?;
75        let mut store = Store::new(&self.engine, ());
76        let instance =
77            Instance::new(&mut store, &module, &[]).map_err(|error| error.to_string())?;
78        let memory = instance
79            .get_memory(&mut store, "locals")
80            .ok_or("native trace has no locals memory")?;
81        let run = instance
82            .get_typed_func::<i32, i32>(&mut store, "run")
83            .map_err(|error| error.to_string())?;
84        ensure_memory(&memory, &mut store, heap_start)?;
85        {
86            let data = memory.data_mut(&mut store);
87            for (values, offset) in trace.vectors.iter().zip(&constant_offsets) {
88                write_vector(data, *offset, values)?;
89            }
90        }
91        Ok(NativeTrace {
92            store,
93            memory,
94            run,
95            trace: trace.clone(),
96            local_count,
97            checkpoint_start,
98            heap_start,
99        })
100    }
101
102    fn enter(
103        &mut self,
104        compiled: &mut NativeTrace,
105        locals: &mut [TraceValue],
106        max_iterations: u32,
107    ) -> TraceOutcome {
108        let mut vector_locals = HashMap::new();
109        let mut heap_cursor = compiled.heap_start;
110        for operation in &compiled.trace.operations {
111            match operation {
112                TraceOp::GuardLocalI64 { local }
113                    if !matches!(locals.get(usize::from(*local)), Some(TraceValue::I64(_))) =>
114                {
115                    return side_exit(&compiled.trace, ExitReason::WrongTag, 0, locals)
116                }
117                TraceOp::GuardLocalBool { local }
118                    if !matches!(locals.get(usize::from(*local)), Some(TraceValue::Bool(_))) =>
119                {
120                    return side_exit(&compiled.trace, ExitReason::WrongTag, 0, locals)
121                }
122                TraceOp::GuardLocalNil { local }
123                    if !matches!(locals.get(usize::from(*local)), Some(TraceValue::Nil)) =>
124                {
125                    return side_exit(&compiled.trace, ExitReason::WrongTag, 0, locals)
126                }
127                TraceOp::GuardLocalVectorI64 { local } if !vector_locals.contains_key(local) => {
128                    let Some(values) = locals
129                        .get(usize::from(*local))
130                        .and_then(numeric_vector_values)
131                    else {
132                        return side_exit(&compiled.trace, ExitReason::WrongTag, 0, locals);
133                    };
134                    let bytes = match vector_bytes(values.len()) {
135                        Ok(bytes) => bytes,
136                        Err(_) => {
137                            return side_exit(&compiled.trace, ExitReason::Unsupported, 0, locals)
138                        }
139                    };
140                    vector_locals.insert(*local, (heap_cursor, values));
141                    heap_cursor = match heap_cursor.checked_add(bytes) {
142                        Some(cursor) if cursor <= MAX_TRACE_MEMORY_BYTES => cursor,
143                        _ => return side_exit(&compiled.trace, ExitReason::Unsupported, 0, locals),
144                    };
145                }
146                _ => {}
147            }
148        }
149        if locals.len() < compiled.local_count {
150            return side_exit(&compiled.trace, ExitReason::WrongTag, 0, locals);
151        }
152        if ensure_memory(&compiled.memory, &mut compiled.store, heap_cursor).is_err() {
153            return side_exit(&compiled.trace, ExitReason::Unsupported, 0, locals);
154        }
155        {
156            let data = compiled.memory.data_mut(&mut compiled.store);
157            for (index, value) in locals.iter().take(compiled.local_count).enumerate() {
158                let bits = match value {
159                    TraceValue::I64(value) => *value,
160                    TraceValue::Bool(value) => i64::from(*value),
161                    TraceValue::Nil => 0,
162                    TraceValue::Indexed(_) => vector_locals
163                        .get(&(index as u16))
164                        .map_or(0, |(offset, _)| *offset as i64),
165                    TraceValue::VectorSlice(_) => vector_locals
166                        .get(&(index as u16))
167                        .map_or(0, |(offset, _)| *offset as i64),
168                    TraceValue::Unsupported => 0,
169                };
170                data[index * 8..index * 8 + 8].copy_from_slice(&bits.to_le_bytes());
171                let checkpoint = compiled.checkpoint_start + index * 8;
172                data[checkpoint..checkpoint + 8].copy_from_slice(&bits.to_le_bytes());
173            }
174            for (offset, values) in vector_locals.values() {
175                if write_vector(data, *offset, values).is_err() {
176                    return side_exit(&compiled.trace, ExitReason::Unsupported, 0, locals);
177                }
178            }
179        }
180        let result = match compiled
181            .run
182            .call(&mut compiled.store, max_iterations as i32)
183        {
184            Ok(result) => result,
185            Err(_) => return side_exit(&compiled.trace, ExitReason::Unsupported, 0, locals),
186        };
187        {
188            let data = compiled.memory.data(&compiled.store);
189            for (index, value) in locals.iter_mut().take(compiled.local_count).enumerate() {
190                // The generated function selects current state on completion
191                // and the iteration checkpoint on every side exit, then
192                // writes that selection into the primary ABI bank.
193                let offset = index * 8;
194                let bits = i64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
195                match value {
196                    TraceValue::I64(_) => *value = TraceValue::I64(bits),
197                    TraceValue::Bool(_) => *value = TraceValue::Bool(bits != 0),
198                    _ => {}
199                }
200            }
201        }
202        match result {
203            -1 => side_exit(&compiled.trace, ExitReason::Overflow, 0, locals),
204            -2 => side_exit(&compiled.trace, ExitReason::DivisionByZero, 0, locals),
205            -3 => side_exit(&compiled.trace, ExitReason::IndexOutOfBounds, 0, locals),
206            value if value >= 0 && value < max_iterations as i32 => side_exit(
207                &compiled.trace,
208                ExitReason::BranchChanged,
209                value as u32,
210                locals,
211            ),
212            _ => TraceOutcome::Completed {
213                iterations: max_iterations,
214            },
215        }
216    }
217}
218
219fn side_exit(
220    trace: &Trace,
221    reason: ExitReason,
222    iterations: u32,
223    locals: &[TraceValue],
224) -> TraceOutcome {
225    TraceOutcome::SideExit {
226        reason,
227        iterations,
228        snapshot: ExitSnapshot {
229            function: trace.function,
230            instruction: trace.resume_ip,
231            locals: locals.to_vec(),
232            stack: Vec::new(),
233        },
234    }
235}
236
237fn numeric_vector_values(value: &TraceValue) -> Option<Vec<i64>> {
238    match value {
239        TraceValue::Indexed(value) => {
240            let values: Box<dyn Iterator<Item = &crate::core::Value> + '_> = match value.as_ref() {
241                crate::core::Value::Tuple(values) => Box::new(values.iter()),
242                crate::core::Value::Vector(values) => Box::new(values.iter()),
243                _ => return None,
244            };
245            values
246                .map(|value| match value {
247                    crate::core::Value::Number(value) => Some(*value),
248                    _ => None,
249                })
250                .collect()
251        }
252        TraceValue::VectorSlice(slice) => Some(slice.values[slice.start..].to_vec()),
253        _ => None,
254    }
255}
256
257fn vector_bytes(length: usize) -> Result<usize, String> {
258    length
259        .checked_mul(2)
260        .and_then(|words| words.checked_add(1))
261        .and_then(|words| words.checked_mul(8))
262        .ok_or_else(|| "trace vector is too large".into())
263}
264
265fn constant_layout(
266    trace: &Trace,
267    local_count: usize,
268) -> Result<(Vec<usize>, usize, usize), String> {
269    let checkpoint_start = local_count
270        .checked_mul(8)
271        .ok_or_else(|| "trace locals exceed the memory limit".to_string())?;
272    let mut cursor = checkpoint_start
273        .checked_add(checkpoint_start)
274        .ok_or_else(|| "trace checkpoints exceed the memory limit".to_string())?;
275    let mut offsets = Vec::with_capacity(trace.vectors.len());
276    for vector in &trace.vectors {
277        offsets.push(cursor);
278        cursor = cursor
279            .checked_add(vector_bytes(vector.len())?)
280            .ok_or_else(|| "trace constants exceed the memory limit".to_string())?;
281    }
282    if cursor > MAX_TRACE_MEMORY_BYTES {
283        return Err("trace constants exceed the memory limit".into());
284    }
285    Ok((offsets, checkpoint_start, cursor))
286}
287
288fn ensure_memory(memory: &Memory, store: &mut Store<()>, required: usize) -> Result<(), String> {
289    if required > MAX_TRACE_MEMORY_BYTES {
290        return Err("trace memory exceeds the limit".into());
291    }
292    let current = memory.data_size(&mut *store);
293    if required <= current {
294        return Ok(());
295    }
296    let pages = (required - current).div_ceil(WASM_PAGE_BYTES);
297    memory
298        .grow(
299            &mut *store,
300            u64::try_from(pages).map_err(|_| "trace memory growth exceeds u64")?,
301        )
302        .map_err(|error| error.to_string())?;
303    Ok(())
304}
305
306fn write_vector(data: &mut [u8], offset: usize, values: &[i64]) -> Result<(), String> {
307    let end = offset
308        .checked_add(vector_bytes(values.len())?)
309        .ok_or_else(|| "trace vector address overflow".to_string())?;
310    let target = data
311        .get_mut(offset..end)
312        .ok_or_else(|| "trace vector exceeds native memory".to_string())?;
313    for index in 0..=values.len() {
314        let header = index * 16;
315        target[header..header + 8].copy_from_slice(&((values.len() - index) as i64).to_le_bytes());
316        if let Some(value) = values.get(index) {
317            target[header + 8..header + 16].copy_from_slice(&value.to_le_bytes());
318        }
319    }
320    Ok(())
321}
322
323fn lower(
324    trace: &Trace,
325    local_count: usize,
326    _checkpoint_start: usize,
327    constant_offsets: &[usize],
328) -> Result<Vec<u8>, String> {
329    const COUNTER_LOCAL: u8 = 1;
330    const TRACE_LOCAL_BASE: usize = 5;
331    let vector_locals = trace
332        .operations
333        .iter()
334        .filter_map(|operation| match operation {
335            TraceOp::GuardLocalVectorI64 { local } => Some(*local),
336            _ => None,
337        })
338        .collect::<HashSet<_>>();
339    let mut i32_locals = vector_locals.clone();
340    i32_locals.extend(
341        trace
342            .operations
343            .iter()
344            .filter_map(|operation| match operation {
345                TraceOp::GuardLocalBool { local } | TraceOp::GuardLocalNil { local } => {
346                    Some(*local)
347                }
348                _ => None,
349            }),
350    );
351    // Keep traced VM locals in Wasm locals for the whole native entry. Linear
352    // memory is only the host ABI at entry/exit. The second bank is the
353    // current-iteration checkpoint used by precise side exits.
354    let trace_local_count = local_count
355        .checked_mul(2)
356        .and_then(|count| count.checked_add(3))
357        .ok_or_else(|| "native trace local count overflow".to_string())?;
358    let mut body = vec![0x02, 0x01, 0x7f]; // counter i32
359    uleb(
360        &mut body,
361        u32::try_from(trace_local_count).map_err(|_| "native trace local count exceeds u32")?,
362    );
363    body.push(0x7e); // a,b,result plus current/checkpoint trace locals: i64
364    for local in 0..local_count {
365        i32_const(&mut body, (local * 8) as i32);
366        body.extend([0x29, 0x03, 0x00]);
367        local_set(&mut body, TRACE_LOCAL_BASE + local)?;
368        local_get(&mut body, TRACE_LOCAL_BASE + local)?;
369        local_set(&mut body, TRACE_LOCAL_BASE + local_count + local)?;
370    }
371    body.extend([0x41, 0x00, 0x21, 0x01, 0x02, 0x40, 0x03, 0x40]);
372    for operation in &trace.operations {
373        match *operation {
374            TraceOp::GuardLocalI64 { .. }
375            | TraceOp::GuardLocalBool { .. }
376            | TraceOp::GuardLocalNil { .. }
377            | TraceOp::GuardLocalVectorI64 { .. } => {}
378            TraceOp::LoadLocal { local } => {
379                local_get(&mut body, TRACE_LOCAL_BASE + usize::from(local))?;
380                if i32_locals.contains(&local) {
381                    body.push(0xa7); // i32.wrap_i64
382                }
383            }
384            TraceOp::ConstantI64(value) => i64_const(&mut body, value),
385            TraceOp::ConstantBool(value) => i32_const(&mut body, i32::from(value)),
386            TraceOp::ConstantNil => i32_const(&mut body, 0),
387            TraceOp::ConstantVectorI64 { vector } => {
388                let offset = constant_offsets
389                    .get(usize::from(vector))
390                    .ok_or_else(|| format!("trace vector {vector} is out of range"))?;
391                i32_const(
392                    &mut body,
393                    i32::try_from(*offset).map_err(|_| "trace vector offset exceeds i32")?,
394                );
395            }
396            TraceOp::StoreLocal { local } => {
397                if i32_locals.contains(&local) {
398                    body.push(0xad); // i64.extend_i32_u
399                }
400                local_set(&mut body, TRACE_LOCAL_BASE + usize::from(local))?;
401            }
402            TraceOp::Pop => {
403                body.push(0x1a);
404            }
405            TraceOp::GuardTruthy { expected: true } => body.extend([0x45, 0x0d, 0x01]),
406            TraceOp::GuardTruthy { expected: false } => body.extend([0x0d, 0x01]),
407            TraceOp::BinaryI64(op) => binary(&mut body, op)?,
408            TraceOp::VectorCountI64 => vector_count(&mut body),
409            TraceOp::VectorFirstI64 => vector_element(&mut body, 0),
410            TraceOp::VectorRestI64 => {
411                i32_const(&mut body, 16);
412                body.push(0x6a);
413            }
414            TraceOp::VectorSecondI64 => vector_element(&mut body, 1),
415            TraceOp::VectorNthI64 => vector_nth(&mut body),
416            TraceOp::LoopBackedge => {
417                for local in 0..local_count {
418                    local_get(&mut body, TRACE_LOCAL_BASE + local)?;
419                    local_set(&mut body, TRACE_LOCAL_BASE + local_count + local)?;
420                }
421                body.extend([
422                    0x20, 0x01, 0x41, 0x01, 0x6a, 0x21, 0x01, 0x20, 0x01, 0x20, 0x00, 0x48, 0x0d,
423                    0x00,
424                ]);
425            }
426        }
427    }
428    body.extend([0x0b, 0x0b]);
429    for local in 0..local_count {
430        i32_const(&mut body, (local * 8) as i32);
431        body.extend([0x20, COUNTER_LOCAL, 0x20, 0x00, 0x46]); // counter == max
432        body.extend([0x04, 0x7e]); // if (result i64)
433        local_get(&mut body, TRACE_LOCAL_BASE + local)?;
434        body.push(0x05); // else: restore iteration checkpoint
435        local_get(&mut body, TRACE_LOCAL_BASE + local_count + local)?;
436        body.extend([0x0b, 0x37, 0x03, 0x00]);
437    }
438    body.extend([0x20, COUNTER_LOCAL, 0x0b]);
439    let mut module = b"\0asm\x01\0\0\0".to_vec();
440    section(&mut module, 1, vec![0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f]);
441    section(&mut module, 3, vec![0x01, 0x00]);
442    section(&mut module, 5, vec![0x01, 0x00, 0x01]);
443    let mut exports = vec![0x02, 0x06];
444    exports.extend(b"locals");
445    exports.extend([0x02, 0x00, 0x03]);
446    exports.extend(b"run");
447    exports.extend([0x00, 0x00]);
448    section(&mut module, 7, exports);
449    let mut code = vec![0x01];
450    uleb(&mut code, body.len() as u32);
451    code.extend(body);
452    section(&mut module, 10, code);
453    if local_count
454        .checked_mul(16)
455        .map_or(true, |bytes| bytes > MAX_TRACE_MEMORY_BYTES)
456    {
457        return Err("native trace locals exceed the memory limit".into());
458    }
459    Ok(module)
460}
461
462fn local_get(body: &mut Vec<u8>, local: usize) -> Result<(), String> {
463    body.push(0x20);
464    uleb(
465        body,
466        u32::try_from(local).map_err(|_| "native trace local index exceeds u32")?,
467    );
468    Ok(())
469}
470
471fn local_set(body: &mut Vec<u8>, local: usize) -> Result<(), String> {
472    body.push(0x21);
473    uleb(
474        body,
475        u32::try_from(local).map_err(|_| "native trace local index exceeds u32")?,
476    );
477    Ok(())
478}
479
480fn binary(body: &mut Vec<u8>, op: IntrinsicOp) -> Result<(), String> {
481    body.extend([0x21, 0x03, 0x21, 0x02]);
482    match op {
483        IntrinsicOp::Add | IntrinsicOp::Subtract => {
484            body.extend([
485                0x20,
486                0x02,
487                0x20,
488                0x03,
489                if op == IntrinsicOp::Add { 0x7c } else { 0x7d },
490                0x21,
491                0x04,
492            ]);
493            if op == IntrinsicOp::Add {
494                body.extend([0x20, 0x02, 0x20, 0x04, 0x85, 0x20, 0x03, 0x20, 0x04, 0x85]);
495            } else {
496                body.extend([0x20, 0x02, 0x20, 0x03, 0x85, 0x20, 0x02, 0x20, 0x04, 0x85]);
497            }
498            body.extend([0x83]);
499            i64_const(body, 0);
500            body.extend([0x53, 0x04, 0x40]);
501            i32_const(body, -1);
502            body.extend([0x21, 0x01, 0x0c, 0x02, 0x0b, 0x20, 0x04]);
503        }
504        IntrinsicOp::Multiply => {
505            body.extend([0x20, 0x02, 0x20, 0x03, 0x7e, 0x21, 0x04]);
506            // `MIN / -1` traps in wasm, so reject that overflow before using
507            // division to prove that the wrapped product is representable.
508            body.extend([0x20, 0x02]);
509            i64_const(body, i64::MIN);
510            body.extend([0x51, 0x20, 0x03]);
511            i64_const(body, -1);
512            body.extend([0x51, 0x71, 0x04, 0x40]);
513            native_exit(body, -1);
514            body.push(0x0b);
515            body.extend([0x20, 0x03, 0x50, 0x04, 0x40, 0x05]);
516            body.extend([0x20, 0x04, 0x20, 0x03, 0x7f, 0x20, 0x02, 0x52]);
517            body.extend([0x04, 0x40]);
518            i32_const(body, -1);
519            body.extend([0x21, 0x01, 0x0c, 0x03]);
520            body.extend([0x0b, 0x0b, 0x20, 0x04]);
521        }
522        IntrinsicOp::Divide => {
523            body.extend([0x20, 0x03, 0x50, 0x04, 0x40]);
524            native_exit(body, -2);
525            body.push(0x0b);
526            body.extend([0x20, 0x02]);
527            i64_const(body, i64::MIN);
528            body.extend([0x51, 0x20, 0x03]);
529            i64_const(body, -1);
530            body.extend([0x51, 0x71, 0x04, 0x40]);
531            native_exit(body, -1);
532            body.push(0x0b);
533            body.extend([0x20, 0x02, 0x20, 0x03, 0x7f]);
534        }
535        IntrinsicOp::Remainder | IntrinsicOp::Modulo => {
536            body.extend([0x20, 0x03, 0x50, 0x04, 0x40]);
537            native_exit(body, -2);
538            body.push(0x0b);
539            body.extend([0x20, 0x02]);
540            i64_const(body, i64::MIN);
541            body.extend([0x51, 0x20, 0x03]);
542            i64_const(body, -1);
543            body.extend([0x51, 0x71, 0x04, 0x40]);
544            native_exit(body, -1);
545            body.push(0x0b);
546            body.extend([0x20, 0x02, 0x20, 0x03, 0x81]);
547        }
548        IntrinsicOp::Less
549        | IntrinsicOp::LessOrEqual
550        | IntrinsicOp::Greater
551        | IntrinsicOp::GreaterOrEqual
552        | IntrinsicOp::Equal => {
553            body.extend([
554                0x20,
555                0x02,
556                0x20,
557                0x03,
558                match op {
559                    IntrinsicOp::Less => 0x53,
560                    IntrinsicOp::LessOrEqual => 0x57,
561                    IntrinsicOp::Greater => 0x55,
562                    IntrinsicOp::GreaterOrEqual => 0x59,
563                    _ => 0x51,
564                },
565            ]);
566        }
567    }
568    Ok(())
569}
570
571fn vector_nth(body: &mut Vec<u8>) {
572    body.extend([0x21, 0x02]); // index i64
573    body.extend([0xad, 0x21, 0x03]); // vector pointer i32 -> i64
574
575    body.extend([0x20, 0x02]);
576    i64_const(body, 0);
577    body.extend([0x53, 0x04, 0x40]); // index < 0
578    native_exit(body, -3);
579    body.push(0x0b);
580
581    body.extend([0x20, 0x02, 0x20, 0x03, 0xa7, 0x29, 0x03, 0x00, 0x5a]);
582    body.extend([0x04, 0x40]); // index >= vector length
583    native_exit(body, -3);
584    body.push(0x0b);
585
586    body.extend([0x20, 0x03, 0xa7]);
587    i32_const(body, 8);
588    body.push(0x6a);
589    body.extend([0x20, 0x02, 0xa7]);
590    i32_const(body, 16);
591    body.extend([0x6c, 0x6a, 0x29, 0x03, 0x00]);
592}
593
594fn vector_count(body: &mut Vec<u8>) {
595    body.extend([0x29, 0x03, 0x00]);
596}
597
598fn vector_element(body: &mut Vec<u8>, index: i32) {
599    body.extend([0xad, 0x21, 0x03]); // vector pointer i32 -> scratch i64
600    body.extend([0x20, 0x03, 0xa7, 0x29, 0x03, 0x00]);
601    i64_const(body, i64::from(index + 1));
602    body.extend([0x54, 0x04, 0x40]); // length < required
603    native_exit(body, -3);
604    body.push(0x0b);
605    body.extend([0x20, 0x03, 0xa7]);
606    i32_const(body, 8 + index * 16);
607    body.extend([0x6a, 0x29, 0x03, 0x00]);
608}
609
610fn native_exit(body: &mut Vec<u8>, code: i32) {
611    i32_const(body, code);
612    body.extend([0x21, 0x01, 0x0c, 0x02]);
613}
614
615fn section(module: &mut Vec<u8>, id: u8, payload: Vec<u8>) {
616    module.push(id);
617    uleb(module, payload.len() as u32);
618    module.extend(payload);
619}
620fn uleb(output: &mut Vec<u8>, mut value: u32) {
621    loop {
622        let mut byte = (value & 0x7f) as u8;
623        value >>= 7;
624        if value != 0 {
625            byte |= 0x80;
626        }
627        output.push(byte);
628        if value == 0 {
629            break;
630        }
631    }
632}
633fn i32_const(output: &mut Vec<u8>, value: i32) {
634    output.push(0x41);
635    sleb(output, value as i64);
636}
637fn i64_const(output: &mut Vec<u8>, value: i64) {
638    output.push(0x42);
639    sleb(output, value);
640}
641fn sleb(output: &mut Vec<u8>, mut value: i64) {
642    loop {
643        let byte = (value & 0x7f) as u8;
644        value >>= 7;
645        let done = (value == 0 && byte & 0x40 == 0) || (value == -1 && byte & 0x40 != 0);
646        output.push(if done { byte } else { byte | 0x80 });
647        if done {
648            break;
649        }
650    }
651}
652
653#[cfg(test)]
654#[path = "native/tests.rs"]
655mod tests;