mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
wasm_interp: Make ValueStack a dumb Vec<Value> => Zig test 146.3->124.6ms
This commit is contained in:
parent
1f90286d64
commit
8c7d9dbff8
5 changed files with 156 additions and 304 deletions
|
@ -6,7 +6,11 @@ set -euxo pipefail
|
|||
# Test failures will always point at the _start function
|
||||
# Make sure to look at the rest of the stack trace!
|
||||
|
||||
# Zig will try to run the test binary it produced, but it is a wasm object and hence your OS won't
|
||||
# know how to run it. In the error message, it prints the binary it tried to run. We use some fun
|
||||
# unix tools to get that path, then feed it to wasmer
|
||||
zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd wasmer --test-cmd-bin
|
||||
# Zig will try to run the test binary it produced, but since your OS doesn't know how to
|
||||
# run Wasm binaries natively, we need to provide a Wasm interpreter as a "test command".
|
||||
zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd ~/roc/target/release/roc_wasm_interp --test-cmd-bin
|
||||
|
||||
hyperfine --warmup 1 \
|
||||
'zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd wasmer --test-cmd-bin' \
|
||||
'zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd wasm3 --test-cmd-bin' \
|
||||
'zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd ~/roc/target/release/roc_wasm_interp --test-cmd-bin'
|
||||
|
|
|
@ -197,93 +197,93 @@ impl<'a> CallStack<'a> {
|
|||
pc: usize,
|
||||
buffer: &mut String,
|
||||
) -> fmt::Result {
|
||||
let divider = "-------------------";
|
||||
writeln!(buffer, "{}", divider)?;
|
||||
// let divider = "-------------------";
|
||||
// writeln!(buffer, "{}", divider)?;
|
||||
|
||||
let mut value_stack_iter = value_stack.iter();
|
||||
// let mut value_stack_iter = value_stack.iter();
|
||||
|
||||
for frame in 0..self.frame_offsets.len() {
|
||||
let next_frame = frame + 1;
|
||||
let op_offset = if next_frame < self.frame_offsets.len() {
|
||||
// return address of next frame = next op in this frame
|
||||
let next_op = self.return_addrs_and_block_depths[next_frame].0 as usize;
|
||||
// Call address is more intuitive than the return address when debugging. Search backward for it.
|
||||
// Skip last byte of function index to avoid a false match with CALL/CALLINDIRECT.
|
||||
// The more significant bytes won't match because of LEB-128 encoding.
|
||||
let mut call_op = next_op - 2;
|
||||
loop {
|
||||
let byte = module.code.bytes[call_op];
|
||||
if byte == OpCode::CALL as u8 || byte == OpCode::CALLINDIRECT as u8 {
|
||||
break;
|
||||
} else {
|
||||
call_op -= 1;
|
||||
}
|
||||
}
|
||||
call_op
|
||||
} else {
|
||||
pc
|
||||
};
|
||||
// for frame in 0..self.frame_offsets.len() {
|
||||
// let next_frame = frame + 1;
|
||||
// let op_offset = if next_frame < self.frame_offsets.len() {
|
||||
// // return address of next frame = next op in this frame
|
||||
// let next_op = self.return_addrs_and_block_depths[next_frame].0 as usize;
|
||||
// // Call address is more intuitive than the return address when debugging. Search backward for it.
|
||||
// // Skip last byte of function index to avoid a false match with CALL/CALLINDIRECT.
|
||||
// // The more significant bytes won't match because of LEB-128 encoding.
|
||||
// let mut call_op = next_op - 2;
|
||||
// loop {
|
||||
// let byte = module.code.bytes[call_op];
|
||||
// if byte == OpCode::CALL as u8 || byte == OpCode::CALLINDIRECT as u8 {
|
||||
// break;
|
||||
// } else {
|
||||
// call_op -= 1;
|
||||
// }
|
||||
// }
|
||||
// call_op
|
||||
// } else {
|
||||
// pc
|
||||
// };
|
||||
|
||||
let fn_index = pc_to_fn_index(op_offset, module);
|
||||
let address = op_offset + module.code.section_offset as usize;
|
||||
writeln!(buffer, "function {}", fn_index)?;
|
||||
writeln!(buffer, " address {:06x}", address)?; // format matches wasm-objdump, for easy search
|
||||
// let fn_index = pc_to_fn_index(op_offset, module);
|
||||
// let address = op_offset + module.code.section_offset as usize;
|
||||
// writeln!(buffer, "function {}", fn_index)?;
|
||||
// writeln!(buffer, " address {:06x}", address)?; // format matches wasm-objdump, for easy search
|
||||
|
||||
write!(buffer, " args ")?;
|
||||
let arg_count = {
|
||||
let n_import_fns = module.import.imports.len();
|
||||
let signature_index = if fn_index < n_import_fns {
|
||||
match module.import.imports[fn_index].description {
|
||||
ImportDesc::Func { signature_index } => signature_index,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
module.function.signatures[fn_index - n_import_fns]
|
||||
};
|
||||
module.types.look_up_arg_type_bytes(signature_index).len()
|
||||
};
|
||||
let args_and_locals_count = {
|
||||
let frame_offset = self.frame_offsets[frame] as usize;
|
||||
let next_frame_offset = if frame == self.frame_offsets.len() - 1 {
|
||||
self.locals_data.len()
|
||||
} else {
|
||||
self.frame_offsets[frame + 1] as usize
|
||||
};
|
||||
next_frame_offset - frame_offset
|
||||
};
|
||||
for index in 0..args_and_locals_count {
|
||||
let value = self.get_local_help(frame, index as u32);
|
||||
if index != 0 {
|
||||
write!(buffer, ", ")?;
|
||||
}
|
||||
if index == arg_count {
|
||||
write!(buffer, "\n locals ")?;
|
||||
}
|
||||
write!(buffer, "{}: {:?}", index, value)?;
|
||||
}
|
||||
write!(buffer, "\n stack [")?;
|
||||
// write!(buffer, " args ")?;
|
||||
// let arg_count = {
|
||||
// let n_import_fns = module.import.imports.len();
|
||||
// let signature_index = if fn_index < n_import_fns {
|
||||
// match module.import.imports[fn_index].description {
|
||||
// ImportDesc::Func { signature_index } => signature_index,
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
// } else {
|
||||
// module.function.signatures[fn_index - n_import_fns]
|
||||
// };
|
||||
// module.types.look_up_arg_type_bytes(signature_index).len()
|
||||
// };
|
||||
// let args_and_locals_count = {
|
||||
// let frame_offset = self.frame_offsets[frame] as usize;
|
||||
// let next_frame_offset = if frame == self.frame_offsets.len() - 1 {
|
||||
// self.locals_data.len()
|
||||
// } else {
|
||||
// self.frame_offsets[frame + 1] as usize
|
||||
// };
|
||||
// next_frame_offset - frame_offset
|
||||
// };
|
||||
// for index in 0..args_and_locals_count {
|
||||
// let value = self.get_local_help(frame, index as u32);
|
||||
// if index != 0 {
|
||||
// write!(buffer, ", ")?;
|
||||
// }
|
||||
// if index == arg_count {
|
||||
// write!(buffer, "\n locals ")?;
|
||||
// }
|
||||
// write!(buffer, "{}: {:?}", index, value)?;
|
||||
// }
|
||||
// write!(buffer, "\n stack [")?;
|
||||
|
||||
let frame_value_count = {
|
||||
let value_stack_base = self.value_stack_bases[frame];
|
||||
let next_value_stack_base = if frame == self.frame_offsets.len() - 1 {
|
||||
value_stack.len() as u32
|
||||
} else {
|
||||
self.value_stack_bases[frame + 1]
|
||||
};
|
||||
next_value_stack_base - value_stack_base
|
||||
};
|
||||
for i in 0..frame_value_count {
|
||||
if i != 0 {
|
||||
write!(buffer, ", ")?;
|
||||
}
|
||||
if let Some(value) = value_stack_iter.next() {
|
||||
write!(buffer, "{:?}", value)?;
|
||||
}
|
||||
}
|
||||
// let frame_value_count = {
|
||||
// let value_stack_base = self.value_stack_bases[frame];
|
||||
// let next_value_stack_base = if frame == self.frame_offsets.len() - 1 {
|
||||
// value_stack.len() as u32
|
||||
// } else {
|
||||
// self.value_stack_bases[frame + 1]
|
||||
// };
|
||||
// next_value_stack_base - value_stack_base
|
||||
// };
|
||||
// for i in 0..frame_value_count {
|
||||
// if i != 0 {
|
||||
// write!(buffer, ", ")?;
|
||||
// }
|
||||
// if let Some(value) = value_stack_iter.next() {
|
||||
// write!(buffer, "{:?}", value)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
writeln!(buffer, "]")?;
|
||||
writeln!(buffer, "{}", divider)?;
|
||||
}
|
||||
// writeln!(buffer, "]")?;
|
||||
// writeln!(buffer, "{}", divider)?;
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use roc_wasm_module::{Value, ValueType};
|
|||
|
||||
use crate::call_stack::CallStack;
|
||||
use crate::value_stack::ValueStack;
|
||||
use crate::{pc_to_fn_index, Error, ImportDispatcher};
|
||||
use crate::{Error, ImportDispatcher};
|
||||
|
||||
pub enum Action {
|
||||
Continue,
|
||||
|
@ -451,7 +451,7 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
|
|||
}
|
||||
|
||||
let mut action = Action::Continue;
|
||||
let mut implicit_return = false;
|
||||
// let mut implicit_return = false;
|
||||
|
||||
match op_code {
|
||||
UNREACHABLE => {
|
||||
|
@ -503,7 +503,7 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
|
|||
if self.block_loop_addrs.len() == self.outermost_block as usize {
|
||||
// implicit RETURN at end of function
|
||||
action = self.do_return();
|
||||
implicit_return = true;
|
||||
// implicit_return = true;
|
||||
} else {
|
||||
self.block_loop_addrs.pop().unwrap();
|
||||
}
|
||||
|
@ -1504,17 +1504,17 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(debug_string) = &self.debug_string {
|
||||
let base = self.call_stack.value_stack_base();
|
||||
let slice = self.value_stack.get_slice(base as usize);
|
||||
eprintln!("{:06x} {:17} {:?}", file_offset, debug_string, slice);
|
||||
if op_code == RETURN || (op_code == END && implicit_return) {
|
||||
let fn_index = pc_to_fn_index(self.program_counter, module);
|
||||
eprintln!("returning to function {}\n", fn_index);
|
||||
} else if op_code == CALL || op_code == CALLINDIRECT {
|
||||
eprintln!();
|
||||
}
|
||||
}
|
||||
// if let Some(debug_string) = &self.debug_string {
|
||||
// let base = self.call_stack.value_stack_base();
|
||||
// let slice = self.value_stack.get_slice(base as usize);
|
||||
// eprintln!("{:06x} {:17} {:?}", file_offset, debug_string, slice);
|
||||
// if op_code == RETURN || (op_code == END && implicit_return) {
|
||||
// let fn_index = pc_to_fn_index(self.program_counter, module);
|
||||
// eprintln!("returning to function {}\n", fn_index);
|
||||
// } else if op_code == CALL || op_code == CALLINDIRECT {
|
||||
// eprintln!();
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(action)
|
||||
}
|
||||
|
|
|
@ -94,23 +94,26 @@ fn main() -> io::Result<()> {
|
|||
});
|
||||
|
||||
// Run
|
||||
// let end= 10000;
|
||||
let end = 1;
|
||||
for _ in 0..end {
|
||||
let result = inst.call_export_from_cli(&module, start_fn_name, &wasi_argv);
|
||||
|
||||
let result = inst.call_export_from_cli(&module, start_fn_name, &wasi_argv);
|
||||
// Print out return value, if any
|
||||
|
||||
// Print out return value, if any
|
||||
|
||||
match result {
|
||||
Ok(Some(val)) => {
|
||||
if is_hex_format {
|
||||
println!("{:#x?}", val)
|
||||
} else {
|
||||
println!("{:?}", val)
|
||||
match result {
|
||||
Ok(Some(val)) => {
|
||||
if is_hex_format {
|
||||
println!("{:#x?}", val)
|
||||
} else {
|
||||
println!("{:?}", val)
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
process::exit(3);
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
process::exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,251 +1,96 @@
|
|||
use bitvec::vec::BitVec;
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_wasm_module::{Value, ValueType};
|
||||
use std::{fmt::Debug, mem::size_of};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// Memory-efficient Struct-of-Arrays storage for the value stack.
|
||||
/// Pack the values and their types as densely as possible,
|
||||
/// to get better cache usage, at the expense of some extra logic.
|
||||
// Very simple and easy-to-debug storage for the Wasm stack machine
|
||||
// It wastes a lot of memory but we tried more complex schemes with packed bytes
|
||||
// and it made no measurable difference to performance.
|
||||
pub struct ValueStack<'a> {
|
||||
bytes: Vec<'a, u8>,
|
||||
is_float: BitVec,
|
||||
is_64: BitVec,
|
||||
}
|
||||
|
||||
macro_rules! pop_bytes {
|
||||
($ty: ty, $bytes: expr) => {{
|
||||
const SIZE: usize = size_of::<$ty>();
|
||||
if $bytes.len() < SIZE {
|
||||
Err(Error::ValueStackEmpty)
|
||||
} else {
|
||||
let bytes_idx = $bytes.len() - SIZE;
|
||||
let mut b = [0; SIZE];
|
||||
b.copy_from_slice(&$bytes[bytes_idx..][..SIZE]);
|
||||
$bytes.truncate(bytes_idx);
|
||||
Ok(<$ty>::from_ne_bytes(b))
|
||||
}
|
||||
}};
|
||||
values: Vec<'a, Value>,
|
||||
}
|
||||
|
||||
impl<'a> ValueStack<'a> {
|
||||
pub(crate) fn new(arena: &'a Bump) -> Self {
|
||||
ValueStack {
|
||||
bytes: Vec::with_capacity_in(1024, arena),
|
||||
is_float: BitVec::with_capacity(1024),
|
||||
is_64: BitVec::with_capacity(1024),
|
||||
values: Vec::with_capacity_in(1024, arena),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.is_64.len()
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.is_64.is_empty()
|
||||
self.values.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, value: Value) {
|
||||
match value {
|
||||
Value::I32(x) => {
|
||||
self.bytes.extend_from_slice(&x.to_ne_bytes());
|
||||
self.is_float.push(false);
|
||||
self.is_64.push(false);
|
||||
}
|
||||
Value::I64(x) => {
|
||||
self.bytes.extend_from_slice(&x.to_ne_bytes());
|
||||
self.is_float.push(false);
|
||||
self.is_64.push(true);
|
||||
}
|
||||
Value::F32(x) => {
|
||||
self.bytes.extend_from_slice(&x.to_ne_bytes());
|
||||
self.is_float.push(true);
|
||||
self.is_64.push(false);
|
||||
}
|
||||
Value::F64(x) => {
|
||||
self.bytes.extend_from_slice(&x.to_ne_bytes());
|
||||
self.is_float.push(true);
|
||||
self.is_64.push(true);
|
||||
}
|
||||
}
|
||||
self.values.push(value);
|
||||
}
|
||||
|
||||
pub(crate) fn pop(&mut self) -> Value {
|
||||
let is_64 = self.is_64.pop().unwrap();
|
||||
let is_float = self.is_float.pop().unwrap();
|
||||
let size = if is_64 { 8 } else { 4 };
|
||||
let bytes_idx = self.bytes.len() - size;
|
||||
let value = self.get(is_64, is_float, bytes_idx);
|
||||
self.bytes.truncate(bytes_idx);
|
||||
value
|
||||
self.values.pop().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn peek(&self) -> Value {
|
||||
let is_64 = *self.is_64.last().unwrap();
|
||||
let is_float = *self.is_float.last().unwrap();
|
||||
let size = if is_64 { 8 } else { 4 };
|
||||
let bytes_idx = self.bytes.len() - size;
|
||||
self.get(is_64, is_float, bytes_idx)
|
||||
}
|
||||
|
||||
fn get(&self, is_64: bool, is_float: bool, bytes_idx: usize) -> Value {
|
||||
if is_64 {
|
||||
let mut b = [0; 8];
|
||||
b.copy_from_slice(&self.bytes[bytes_idx..][..8]);
|
||||
if is_float {
|
||||
Value::F64(f64::from_ne_bytes(b))
|
||||
} else {
|
||||
Value::I64(i64::from_ne_bytes(b))
|
||||
}
|
||||
} else {
|
||||
let mut b = [0; 4];
|
||||
b.copy_from_slice(&self.bytes[bytes_idx..][..4]);
|
||||
if is_float {
|
||||
Value::F32(f32::from_ne_bytes(b))
|
||||
} else {
|
||||
Value::I32(i32::from_ne_bytes(b))
|
||||
}
|
||||
}
|
||||
*self.values.last().unwrap()
|
||||
}
|
||||
|
||||
/// Memory addresses etc
|
||||
pub(crate) fn pop_u32(&mut self) -> Result<u32, Error> {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(false), Some(false)) => pop_bytes!(u32, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => {
|
||||
Err(Error::value_stack_type(ValueType::I32, is_float, is_64))
|
||||
}
|
||||
_ => Err(Error::ValueStackEmpty),
|
||||
match self.values.pop() {
|
||||
Some(Value::I32(x)) => Ok(u32::from_ne_bytes(x.to_ne_bytes())),
|
||||
Some(bad) => Err(Error::ValueStackType(ValueType::I32, ValueType::from(bad))),
|
||||
None => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_i32(&mut self) -> Result<i32, Error> {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(false), Some(false)) => pop_bytes!(i32, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => {
|
||||
Err(Error::value_stack_type(ValueType::I32, is_float, is_64))
|
||||
}
|
||||
_ => Err(Error::ValueStackEmpty),
|
||||
match self.values.pop() {
|
||||
Some(Value::I32(x)) => Ok(x),
|
||||
Some(bad) => Err(Error::ValueStackType(ValueType::I32, ValueType::from(bad))),
|
||||
None => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_u64(&mut self) -> Result<u64, Error> {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(false), Some(true)) => pop_bytes!(u64, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => {
|
||||
Err(Error::value_stack_type(ValueType::I64, is_float, is_64))
|
||||
}
|
||||
_ => Err(Error::ValueStackEmpty),
|
||||
match self.values.pop() {
|
||||
Some(Value::I64(x)) => Ok(u64::from_ne_bytes(x.to_ne_bytes())),
|
||||
Some(bad) => Err(Error::ValueStackType(ValueType::I64, ValueType::from(bad))),
|
||||
None => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_i64(&mut self) -> Result<i64, Error> {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(false), Some(true)) => pop_bytes!(i64, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => {
|
||||
Err(Error::value_stack_type(ValueType::I64, is_float, is_64))
|
||||
}
|
||||
_ => Err(Error::ValueStackEmpty),
|
||||
match self.values.pop() {
|
||||
Some(Value::I64(x)) => Ok(x),
|
||||
Some(bad) => Err(Error::ValueStackType(ValueType::I64, ValueType::from(bad))),
|
||||
None => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_f32(&mut self) -> Result<f32, Error> {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(true), Some(false)) => pop_bytes!(f32, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => {
|
||||
Err(Error::value_stack_type(ValueType::F32, is_float, is_64))
|
||||
}
|
||||
_ => Err(Error::ValueStackEmpty),
|
||||
match self.values.pop() {
|
||||
Some(Value::F32(x)) => Ok(x),
|
||||
Some(bad) => Err(Error::ValueStackType(ValueType::F32, ValueType::from(bad))),
|
||||
None => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_f64(&mut self) -> Result<f64, Error> {
|
||||
match (self.is_float.pop(), self.is_64.pop()) {
|
||||
(Some(true), Some(true)) => pop_bytes!(f64, self.bytes),
|
||||
(Some(is_float), Some(is_64)) => {
|
||||
Err(Error::value_stack_type(ValueType::F64, is_float, is_64))
|
||||
}
|
||||
_ => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_from_index(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
from_index: usize,
|
||||
) -> std::fmt::Result {
|
||||
write!(f, "[")?;
|
||||
let mut bytes_index = 0;
|
||||
assert_eq!(self.is_64.len(), self.is_float.len());
|
||||
if from_index < self.is_64.len() {
|
||||
let iter_64 = self.is_64.iter().by_vals();
|
||||
let iter_float = self.is_float.iter().by_vals();
|
||||
for (i, (is_64, is_float)) in iter_64.zip(iter_float).enumerate() {
|
||||
if i < from_index {
|
||||
continue;
|
||||
}
|
||||
let value = self.get(is_64, is_float, bytes_index);
|
||||
bytes_index += if is_64 { 8 } else { 4 };
|
||||
value.fmt(f)?;
|
||||
if i < self.is_64.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
|
||||
pub(crate) fn get_slice<'b>(&'b self, index: usize) -> ValueStackSlice<'a, 'b> {
|
||||
ValueStackSlice { stack: self, index }
|
||||
}
|
||||
|
||||
pub(crate) fn iter<'b>(&'b self) -> ValueStackIter<'a, 'b> {
|
||||
ValueStackIter {
|
||||
stack: self,
|
||||
index: 0,
|
||||
bytes_index: 0,
|
||||
match self.values.pop() {
|
||||
Some(Value::F64(x)) => Ok(x),
|
||||
Some(bad) => Err(Error::ValueStackType(ValueType::F64, ValueType::from(bad))),
|
||||
None => Err(Error::ValueStackEmpty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ValueStack<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmt_from_index(f, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValueStackSlice<'a, 'b> {
|
||||
stack: &'b ValueStack<'a>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Debug for ValueStackSlice<'_, '_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.stack.fmt_from_index(f, self.index)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValueStackIter<'a, 'b> {
|
||||
stack: &'b ValueStack<'a>,
|
||||
index: usize,
|
||||
bytes_index: usize,
|
||||
}
|
||||
|
||||
impl Iterator for ValueStackIter<'_, '_> {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.index >= self.stack.is_64.len() {
|
||||
None
|
||||
} else {
|
||||
let is_64 = self.stack.is_64[self.index];
|
||||
let is_float = self.stack.is_float[self.index];
|
||||
let value = self.stack.get(is_64, is_float, self.bytes_index);
|
||||
self.index += 1;
|
||||
self.bytes_index += if is_64 { 8 } else { 4 };
|
||||
Some(value)
|
||||
}
|
||||
write!(f, "{:?}", &self.values)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue