1use 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 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 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]; uleb(
360 &mut body,
361 u32::try_from(trace_local_count).map_err(|_| "native trace local count exceeds u32")?,
362 );
363 body.push(0x7e); 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); }
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); }
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]); body.extend([0x04, 0x7e]); local_get(&mut body, TRACE_LOCAL_BASE + local)?;
434 body.push(0x05); 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 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]); body.extend([0xad, 0x21, 0x03]); body.extend([0x20, 0x02]);
576 i64_const(body, 0);
577 body.extend([0x53, 0x04, 0x40]); 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]); 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]); body.extend([0x20, 0x03, 0xa7, 0x29, 0x03, 0x00]);
601 i64_const(body, i64::from(index + 1));
602 body.extend([0x54, 0x04, 0x40]); 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;