#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
use crate::common::error::ErrorGen;
use crate::emitter::memory_allocator::MemoryAllocator;
use crate::emitter::tag_handler::get_tag_for;
use crate::lang_features::libraries::core::io::io_adapter::IOAdapter;
use crate::lang_features::libraries::core::maps::map_adapter::MapLibAdapter;
use crate::parser::types::DataType;
use crate::verifier::types::VarAddr;
use itertools::Itertools;
use log::info;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::hash::{DefaultHasher, Hash, Hasher};
use wirm::ir::function::FunctionBuilder;
use wirm::ir::id::{FunctionID, GlobalID, LocalID};
use wirm::ir::types::{BlockType, DataType as WirmType, InitExpr, Value};
use wirm::module_builder::AddLocal;
use wirm::opcode::MacroOpcode;
use wirm::wasmparser::MemArg;
use wirm::{InitInstr, Module, Opcode};
pub const NULL_PTR_IN_MEM: i32 = -1;
pub const NULL_PTR_IN_GLOBAL: i32 = -1;
pub struct ReportVars {
pub variable_metadata: HashMap<Vec<VarAddr>, (Vec<WirmType>, Metadata)>,
pub all_metadata: HashSet<Metadata>,
pub all_used_report_dts: HashSet<DataType>,
pub curr_location: LocationData,
flush_tracker: FlushTracker,
alloc_tracker: HashMap<DataType, ReportAllocTracker>,
}
impl Default for ReportVars {
fn default() -> Self {
Self::new()
}
}
impl ReportVars {
pub fn new() -> Self {
ReportVars {
variable_metadata: HashMap::new(),
all_metadata: HashSet::new(),
all_used_report_dts: HashSet::new(),
curr_location: LocationData::Global { script_id: u8::MAX },
alloc_tracker: HashMap::default(),
flush_tracker: FlushTracker {
flush_var_metadata_fid: None,
},
}
}
pub fn put_global_metadata(
&mut self,
addrs: Vec<VarAddr>,
name: String,
whamm_ty: &DataType,
) -> bool {
if !matches!(self.curr_location, LocationData::Global { .. }) {
unreachable!(
"Expected global location data, but got: {:?}",
self.curr_location
);
}
self.all_used_report_dts.insert(whamm_ty.clone());
let metadata = Metadata::new(name.clone(), whamm_ty.clone(), &self.curr_location);
let wasm_tys = metadata.get_wasm_tys();
assert_eq!(wasm_tys.len(), addrs.len());
self.variable_metadata
.insert(addrs, (wasm_tys.clone(), metadata.clone()));
if !self.all_metadata.insert(metadata) {
unreachable!("Duplicate metadata for global with name: {}", name);
}
true
}
pub fn put_map_metadata(&mut self, map_id: u32, name: String, ty: DataType) {
self.all_used_report_dts.insert(ty.clone());
let metadata = Metadata::new(name.clone(), ty, &self.curr_location);
self.variable_metadata.insert(
vec![VarAddr::MapId { addr: map_id }],
(metadata.get_wasm_tys().clone(), metadata.clone()),
);
if !self.all_metadata.insert(metadata) {
unreachable!("Duplicate metadata for map with name: {}", name);
};
}
pub fn print_metadata(&self) {
if self.all_metadata.is_empty() {
return;
}
let mut info = "Metadata:\n".to_string();
let mut sorted_variable_metadata: Vec<_> = self.variable_metadata.iter().collect();
sorted_variable_metadata.sort_by_key(|&(key, _)| key);
for (keys, value) in sorted_variable_metadata {
for key in keys {
match key {
VarAddr::Local { addr } => info += &format!("LocalID: {} -> {:?}", addr, value),
VarAddr::Global { addr } => {
info += &format!("GlobalID: {} -> {:?}", addr, value)
}
VarAddr::MapId { addr } => info += &format!("MapID: {} -> {:?}", addr, value),
VarAddr::MemLoc {
mem_id, var_offset, ..
} => info += &format!("MemAddr: ({}@{}) -> {:?}", mem_id, var_offset, value),
}
}
}
info!("{info}");
}
}
impl ReportVars {
pub fn setup_flush_data_segments(
&mut self,
wasm: &mut Module,
memory_allocator: &mut MemoryAllocator,
) -> HashMap<Vec<VarAddr>, (DataType, (u32, u32))> {
let id_type = "memaddr".to_string();
memory_allocator.emit_string(wasm, &mut format!(", {id_type}, "));
memory_allocator.emit_string(wasm, &mut ", ".to_string());
memory_allocator.emit_string(wasm, &mut "script".to_string());
for dt in self.all_used_report_dts.iter() {
memory_allocator.emit_string(wasm, &mut format!("{dt}, "));
let wasm_ty_str = get_wasm_tys_str(&dt.to_wasm_type());
memory_allocator.emit_string(wasm, &mut format!("{wasm_ty_str}, "));
}
self.get_var_metadata(wasm, memory_allocator)
}
pub fn get_var_metadata(
&mut self,
wasm: &mut Module,
memory_allocator: &mut MemoryAllocator,
) -> HashMap<Vec<VarAddr>, (DataType, (u32, u32))> {
self.variable_metadata
.iter()
.map(|(keys, (_, value))| {
let mut addr_vals = String::new();
let mut addr_tys = String::new();
if keys.len() > 1 {
addr_vals.push('(');
addr_tys.push('(');
}
let mut first = true;
for key in keys {
if !first {
addr_vals.push_str("; ");
addr_tys.push_str("; ");
}
addr_vals.push_str(key.to_string().as_str());
addr_tys.push_str(&key.ty());
first = false;
}
if keys.len() > 1 {
addr_vals.push(')');
addr_tys.push(')');
}
let mut s = format!("{addr_vals}, {}, ", value.to_csv(&addr_tys));
memory_allocator.emit_string(wasm, &mut s);
let addr = memory_allocator.emitted_strings.get(&s).unwrap();
(
keys.clone(),
(
value.get_whamm_ty(),
(addr.mem_offset as u32, addr.len as u32),
),
)
})
.collect()
}
pub fn emit_flush_logic(
&mut self,
func: &mut FunctionBuilder,
var_meta: &HashMap<Vec<VarAddr>, (DataType, (u32, u32))>,
mem_allocator: &MemoryAllocator,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
header_info: (u32, u32),
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) {
self.emit_locals_flush(
func,
mem_allocator,
io_adapter,
map_lib_adapter,
header_info,
mem_id,
wasm,
err,
);
self.emit_globals_flush(func, var_meta, io_adapter, map_lib_adapter, err);
}
pub fn configure_trackers(&mut self, trackers: HashMap<DataType, u32>) {
for (ty, id) in trackers.iter() {
self.alloc_tracker
.entry(ty.clone())
.and_modify(|tracker| {
tracker.first_var = Some(*id);
})
.or_insert(ReportAllocTracker {
first_var: Some(*id),
last_var: None,
});
}
}
pub fn emit_locals_flush(
&mut self,
func: &mut FunctionBuilder,
mem_allocator: &MemoryAllocator,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
header_info: (u32, u32),
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) {
io_adapter.putsln(header_info.0, header_info.1, func, err);
self.call_dt_flushers(
func,
mem_allocator,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
);
}
pub fn emit_globals_flush(
&self,
func: &mut FunctionBuilder,
var_meta_str: &HashMap<Vec<VarAddr>, (DataType, (u32, u32))>,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
let sorted_metadata = var_meta_str.iter().sorted_by_key(|data| data.0);
for (addrs, (whamm_ty, (str_addr, str_len))) in sorted_metadata.into_iter() {
io_adapter.puts(*str_addr, *str_len, func, err);
let wasm_tys = whamm_ty.to_wasm_type();
assert_eq!(addrs.len(), wasm_tys.len());
for addr in addrs.iter() {
match addr {
VarAddr::Global { addr } => {
func.global_get(GlobalID(*addr));
}
VarAddr::MapId { addr } => {
func.u32_const(*addr);
}
VarAddr::Local { .. } => {
panic!("Shouldn't be trying to flush a local variable...")
}
VarAddr::MemLoc { .. } => {
panic!("Shouldn't be trying to flush a memaddr in this function...")
}
}
}
match whamm_ty {
DataType::U8 => io_adapter.call_putu8(func, err),
DataType::I8 => io_adapter.call_puti8(func, err),
DataType::U16 => io_adapter.call_putu16(func, err),
DataType::I16 => io_adapter.call_puti16(func, err),
DataType::I32 => io_adapter.call_puti32(func, err),
DataType::U32 => io_adapter.call_putu32(func, err),
DataType::I64 => io_adapter.call_puti64(func, err),
DataType::U64 => io_adapter.call_putu64(func, err),
DataType::F32 => io_adapter.call_putf32(func, err),
DataType::F64 => io_adapter.call_putf64(func, err),
DataType::Boolean => io_adapter.call_putbool(func, err),
DataType::Map { .. } => map_lib_adapter.print_map(func, err),
DataType::Str => io_adapter.call_puts_internal(func, err),
other => {
unimplemented!("printing for this type has not been implemented: {}", other)
}
}
io_adapter.putln(func, err);
}
}
pub fn call_dt_flushers(
&mut self,
func: &mut FunctionBuilder,
mem_allocator: &MemoryAllocator,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) {
self.emit_flush_var_metadata_fn(mem_allocator, io_adapter, mem_id, wasm, err);
let sorted_keys = self.alloc_tracker.keys().sorted_by_key(|ty| ty.id());
for dt in sorted_keys.into_iter() {
match dt {
DataType::U8 => {
let fid = self.emit_flush_u8_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::I8 => {
let fid = self.emit_flush_i8_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::U16 => {
let fid =
self.emit_flush_u16_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::I16 => {
let fid =
self.emit_flush_i16_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::U32 => {
let fid =
self.emit_flush_u32_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::I32 => {
let fid =
self.emit_flush_i32_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::U64 => {
let fid =
self.emit_flush_u64_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::I64 => {
let fid =
self.emit_flush_i64_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::F32 => {
let fid =
self.emit_flush_f32_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::F64 => {
let fid =
self.emit_flush_f64_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::Boolean => {
let fid =
self.emit_flush_bool_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::Map { .. } => {
let fid =
self.emit_flush_map_fn(dt, io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
DataType::Str => {
let fid =
self.emit_flush_str_fn(io_adapter, map_lib_adapter, mem_id, wasm, err);
func.call(FunctionID(fid));
}
dt => {
unimplemented!("Flushing {dt} is not supported yet.")
}
}
}
}
fn emit_flush_var_metadata_fn(
&mut self,
mem_allocator: &MemoryAllocator,
io_adapter: &mut IOAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) {
let id_type = "memaddr".to_string();
let i32_bytes = size_of::<i32>() as i32;
let u8_bytes = size_of::<u8>() as i32;
let dt = LocalID(0); let orig_addr = LocalID(1);
let mut flush_fn = FunctionBuilder::new(&[WirmType::I64, WirmType::I32], &[WirmType::I32]);
let fname_ptr = flush_fn.add_local(WirmType::I32); let fname_len = flush_fn.add_local(WirmType::I32);
let fid = flush_fn.add_local(WirmType::I32); let pc = flush_fn.add_local(WirmType::I32);
let name_ptr = flush_fn.add_local(WirmType::I32); let name_len = flush_fn.add_local(WirmType::I32);
let script_id = flush_fn.add_local(WirmType::I32);
let probe_id_ptr = flush_fn.add_local(WirmType::I32); let probe_id_len = flush_fn.add_local(WirmType::I32);
let mut curr_offset = 0;
let mut memarg = |num_bytes: i32| -> MemArg {
let arg = MemArg {
align: 0,
max_align: 0,
offset: curr_offset,
memory: mem_id,
};
curr_offset += num_bytes as u64;
arg
};
flush_fn
.local_get(orig_addr)
.i32_load(memarg(i32_bytes))
.local_set(fname_ptr);
flush_fn
.local_get(orig_addr)
.i32_load8_u(memarg(u8_bytes))
.local_set(fname_len);
flush_fn
.local_get(orig_addr)
.i32_load(memarg(i32_bytes))
.local_set(fid);
flush_fn
.local_get(orig_addr)
.i32_load(memarg(i32_bytes))
.local_set(pc);
flush_fn
.local_get(orig_addr)
.i32_load(memarg(i32_bytes))
.local_set(name_ptr);
flush_fn
.local_get(orig_addr)
.i32_load8_u(memarg(u8_bytes))
.local_set(name_len);
flush_fn
.local_get(orig_addr)
.i32_load8_u(memarg(u8_bytes))
.local_set(script_id);
flush_fn
.local_get(orig_addr)
.i32_load(memarg(i32_bytes))
.local_set(probe_id_ptr);
flush_fn
.local_get(orig_addr)
.i32_load8_u(memarg(u8_bytes))
.local_set(probe_id_len);
flush_fn.local_get(orig_addr);
io_adapter.call_puti32(&mut flush_fn, err);
let (addr, len) = mem_allocator.lookup_emitted_string(&format!(", {id_type}, "));
io_adapter.puts(addr, len, &mut flush_fn, err);
flush_fn.local_get(name_ptr).local_get(name_len);
io_adapter.call_puts_internal(&mut flush_fn, err);
let (comma_addr, comma_len) = mem_allocator.lookup_emitted_string(", ");
io_adapter.puts(comma_addr, comma_len, &mut flush_fn, err);
assert!(!self.all_used_report_dts.is_empty());
let mut first = true;
for ty in self.all_used_report_dts.iter() {
if !first {
flush_fn.else_stmt();
}
Self::flush_ty_metadata(&mut flush_fn, ty, &dt, mem_allocator, io_adapter, err);
first = false;
}
flush_fn.else_stmt().unreachable();
for _ in 0..self.all_used_report_dts.len() {
flush_fn.end();
}
let (addr, len) = mem_allocator.lookup_emitted_string("script");
io_adapter.puts(addr, len, &mut flush_fn, err);
flush_fn.local_get(script_id);
io_adapter.call_puti32(&mut flush_fn, err);
io_adapter.puts(comma_addr, comma_len, &mut flush_fn, err);
flush_fn.local_get(fname_ptr).local_get(fname_len);
io_adapter.call_puts_internal(&mut flush_fn, err);
io_adapter.puts(comma_addr, comma_len, &mut flush_fn, err);
flush_fn.local_get(fid);
io_adapter.call_puti32(&mut flush_fn, err);
io_adapter.puts(comma_addr, comma_len, &mut flush_fn, err);
flush_fn.local_get(pc);
io_adapter.call_puti32(&mut flush_fn, err);
io_adapter.puts(comma_addr, comma_len, &mut flush_fn, err);
flush_fn.local_get(probe_id_ptr).local_get(probe_id_len);
io_adapter.call_puts_internal(&mut flush_fn, err);
io_adapter.puts(comma_addr, comma_len, &mut flush_fn, err);
flush_fn
.local_get(orig_addr)
.i32_const(curr_offset as i32)
.i32_add();
let flush_fid = flush_fn.finish_module_with_tag(wasm, get_tag_for(&None));
wasm.set_fn_name(flush_fid, "flush_var_metadata".to_string());
self.flush_tracker.flush_var_metadata_fid = Some(*flush_fid);
}
fn flush_ty_metadata<'a, T: Opcode<'a> + MacroOpcode<'a> + AddLocal>(
flush_fn: &mut T,
dt: &DataType,
dt_local: &LocalID,
mem_allocator: &MemoryAllocator,
io_adapter: &mut IOAdapter,
err: &mut ErrorGen,
) {
let mut s = DefaultHasher::new();
dt.hash(&mut s);
let hash = s.finish();
flush_fn
.local_get(*dt_local)
.i64_const(hash as i64)
.i64_eq()
.if_stmt(BlockType::Empty);
let (addr, len) = mem_allocator.lookup_emitted_string(&format!("{dt}, "));
io_adapter.puts(addr, len, flush_fn, err);
let wasm_ty_str = get_wasm_tys_str(&dt.to_wasm_type());
let (addr, len) = mem_allocator.lookup_emitted_string(&format!("{wasm_ty_str}, "));
io_adapter.puts(addr, len, flush_fn, err);
}
fn emit_flush_fn(
&self,
flush_dt: &dyn Fn(
&mut FunctionBuilder,
LocalID,
&MemArg,
&mut IOAdapter,
&mut MapLibAdapter,
&mut ErrorGen,
),
dt: DataType,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
let Some(flush_metadata_fid) = self.flush_tracker.flush_var_metadata_fid else {
unreachable!("Should have the flush variable metadata function ID, but it's not been generated yet.");
};
let mem_arg = MemArg {
align: 0,
max_align: 0,
offset: 0,
memory: mem_id,
};
let mut flush_fn = FunctionBuilder::new(&[], &[]);
let curr_addr = flush_fn.add_local(WirmType::I32);
let next_addr = flush_fn.add_local(WirmType::I32);
flush_fn
.block(BlockType::Empty)
.global_get(GlobalID(
self.alloc_tracker.get(&dt).unwrap().first_var.unwrap(),
))
.i32_const(NULL_PTR_IN_GLOBAL)
.i32_eq()
.br_if(0);
flush_fn
.global_get(GlobalID(
self.alloc_tracker.get(&dt).unwrap().first_var.unwrap(),
))
.local_set(curr_addr);
flush_fn.loop_stmt(BlockType::Empty);
#[rustfmt::skip]
flush_fn
.local_get(curr_addr)
.i32_load(mem_arg)
.local_tee(next_addr)
.i32_const(NULL_PTR_IN_MEM)
.i32_ne()
.if_stmt(BlockType::Empty)
.local_get(next_addr)
.i32_eqz()
.if_stmt(BlockType::Empty)
.unreachable()
.end()
.local_get(curr_addr)
.local_get(next_addr)
.i32_add()
.local_set(next_addr)
.end();
flush_fn
.i32_const(4)
.local_get(curr_addr)
.i32_add()
.local_set(curr_addr);
let mut s = DefaultHasher::new();
dt.hash(&mut s);
let dt_hash = s.finish();
flush_fn
.i64_const(dt_hash as i64)
.local_get(curr_addr)
.call(FunctionID(flush_metadata_fid))
.local_tee(curr_addr);
flush_dt(
&mut flush_fn,
curr_addr,
&mem_arg,
io_adapter,
map_lib_adapter,
err,
);
#[rustfmt::skip]
flush_fn
.local_get(next_addr)
.i32_const(NULL_PTR_IN_MEM)
.i32_ne()
.if_stmt(BlockType::Empty)
.local_get(next_addr)
.local_set(curr_addr)
.br(1)
.end();
flush_fn.end().end();
let flush_fid = flush_fn.finish_module_with_tag(wasm, get_tag_for(&None));
wasm.set_fn_name(flush_fid, format!("flush_{}_vars", dt));
*flush_fid
}
fn emit_flush_u8_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_u8,
DataType::U8,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_u8(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load8_u(*mem_arg);
io_adapter.call_putu8(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_i8_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_i8,
DataType::I8,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_i8(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load8_s(*mem_arg);
io_adapter.call_puti8(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_u16_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_u16,
DataType::U16,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_u16(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load16_u(*mem_arg);
io_adapter.call_putu16(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_i16_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_i16,
DataType::I16,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_i16(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load16_s(*mem_arg);
io_adapter.call_puti16(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_u32_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_u32,
DataType::U32,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_u32(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load(*mem_arg);
io_adapter.call_putu32(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_i32_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_i32,
DataType::I32,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_i32(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load(*mem_arg);
io_adapter.call_puti32(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_u64_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_u64,
DataType::U64,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_u64(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i64_load(*mem_arg);
io_adapter.call_putu64(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_i64_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_i64,
DataType::I64,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_i64(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i64_load(*mem_arg);
io_adapter.call_puti64(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_f32_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_f32,
DataType::F32,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_f32(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.f32_load(*mem_arg);
io_adapter.call_putf32(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_f64_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_f64,
DataType::F64,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_f64(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.f64_load(*mem_arg);
io_adapter.call_putf64(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_bool_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_bool,
DataType::Boolean,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_bool(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load(*mem_arg);
io_adapter.call_putbool(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_map_fn(
&self,
dt: &DataType,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_map,
dt.clone(),
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_map(
flush_fn: &mut FunctionBuilder,
_: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load(*mem_arg);
map_lib_adapter.call_print_map(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
fn emit_flush_str_fn(
&self,
io_adapter: &mut IOAdapter,
map_lib_adapter: &mut MapLibAdapter,
mem_id: u32,
wasm: &mut Module,
err: &mut ErrorGen,
) -> u32 {
self.emit_flush_fn(
&Self::flush_str,
DataType::Str,
io_adapter,
map_lib_adapter,
mem_id,
wasm,
err,
)
}
fn flush_str(
flush_fn: &mut FunctionBuilder,
curr_addr: LocalID,
mem_arg: &MemArg,
io_adapter: &mut IOAdapter,
_map_lib_adapter: &mut MapLibAdapter,
err: &mut ErrorGen,
) {
flush_fn.i32_load(*mem_arg);
let mut len_mem_arg = *mem_arg;
len_mem_arg.offset += 4;
flush_fn.local_get(curr_addr).i32_load(len_mem_arg);
io_adapter.call_puts_internal(flush_fn, err);
io_adapter.putln(flush_fn, err);
}
pub fn report_var_header_bytes() -> u32 {
size_of::<i32>() as u32
}
pub fn alloc_report_var_header(
&mut self,
data_type: &DataType,
var_offset: u32,
mem_id: u32,
mem_tracker_global: GlobalID,
alloc_func: &mut FunctionBuilder,
wasm: &mut Module,
) -> u32 {
let mut used_bytes = 0;
let tracker = self
.alloc_tracker
.entry(data_type.clone())
.or_insert(ReportAllocTracker {
first_var: None,
last_var: None,
});
let first_var = if let Some(first_var) = tracker.first_var {
GlobalID(first_var)
} else {
let gid = wasm.add_global_with_tag(
InitExpr::new(vec![InitInstr::Value(Value::I32(NULL_PTR_IN_GLOBAL))]),
WirmType::I32,
true,
false,
get_tag_for(&None),
);
tracker.first_var = Some(*gid);
GlobalID(*gid)
};
#[rustfmt::skip]
alloc_func.global_get(first_var)
.i32_const(NULL_PTR_IN_GLOBAL)
.i32_eq()
.if_stmt(BlockType::Empty)
.global_get(mem_tracker_global)
.u32_const(var_offset)
.i32_add()
.global_set(first_var)
.end();
alloc_func.global_get(mem_tracker_global); alloc_func.i32_const(NULL_PTR_IN_GLOBAL); alloc_func.i32_store(MemArg {
align: 0,
max_align: 0,
offset: var_offset as u64,
memory: mem_id,
});
used_bytes += size_of_val(&NULL_PTR_IN_GLOBAL);
assert_eq!(4, used_bytes);
used_bytes as u32
}
pub fn update_next_addr_ptr(
&mut self,
data_type: &DataType,
curr_var_mem_usage: u32,
total_mem_usage: u32,
mem_id: u32,
mem_tracker_global: GlobalID,
alloc_func: &mut FunctionBuilder,
wasm: &mut Module,
) {
let tracker = self
.alloc_tracker
.entry(data_type.clone())
.or_insert(ReportAllocTracker {
first_var: None,
last_var: None,
});
let last_var = if let Some(last_var) = tracker.last_var {
GlobalID(last_var)
} else {
let gid = wasm.add_global_with_tag(
InitExpr::new(vec![InitInstr::Value(Value::I32(NULL_PTR_IN_GLOBAL))]),
WirmType::I32,
true,
false,
get_tag_for(&None),
);
tracker.last_var = Some(*gid);
GlobalID(*gid)
};
#[rustfmt::skip]
alloc_func.global_get(last_var)
.i32_const(NULL_PTR_IN_GLOBAL)
.i32_eq()
.i32_eqz()
.if_stmt(BlockType::Empty)
.global_get(last_var)
.global_get(mem_tracker_global)
.global_get(last_var)
.i32_sub()
.u32_const(total_mem_usage - curr_var_mem_usage)
.i32_add()
.i32_store(MemArg {
align: 0,
max_align: 0,
offset: 0,
memory: mem_id,
})
.end();
alloc_func
.global_get(mem_tracker_global)
.u32_const(total_mem_usage - curr_var_mem_usage)
.i32_add()
.global_set(last_var);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Metadata {
Global {
name: String,
whamm_ty: DataType,
wasm_tys: Vec<WirmType>,
script_id: u8,
},
Local {
name: String,
whamm_ty: DataType,
wasm_tys: Vec<WirmType>,
script_id: u8,
bytecode_loc: BytecodeLoc,
probe_id: String,
},
}
impl From<&LocationData> for Metadata {
fn from(loc: &LocationData) -> Self {
match loc {
LocationData::Local {
script_id,
bytecode_loc,
probe_id,
..
} => Self::Local {
name: "".to_string(),
whamm_ty: DataType::I32,
wasm_tys: vec![WirmType::I32],
script_id: *script_id,
bytecode_loc: bytecode_loc.clone(),
probe_id: probe_id.clone(),
},
LocationData::Global { script_id } => Self::Global {
name: "".to_string(),
whamm_ty: DataType::I32,
wasm_tys: vec![WirmType::I32],
script_id: *script_id,
},
}
}
}
impl Metadata {
pub fn new(name: String, whamm_ty: DataType, loc: &LocationData) -> Self {
let mut meta = Self::from(loc);
meta.set_name(name);
let wasm_tys = whamm_ty.to_wasm_type();
meta.set_wasm_tys(wasm_tys);
meta.set_whamm_ty(whamm_ty);
meta
}
pub fn set_name(&mut self, new_name: String) {
match self {
Self::Local { name, .. } | Self::Global { name, .. } => *name = new_name,
}
}
pub fn set_whamm_ty(&mut self, new_ty: DataType) {
match self {
Self::Local { whamm_ty, .. } | Self::Global { whamm_ty, .. } => *whamm_ty = new_ty,
}
}
pub fn get_whamm_ty(&self) -> DataType {
match self {
Self::Local { whamm_ty, .. } | Self::Global { whamm_ty, .. } => whamm_ty.clone(),
}
}
pub fn set_wasm_tys(&mut self, new_tys: Vec<WirmType>) {
match self {
Self::Local { wasm_tys, .. } | Self::Global { wasm_tys, .. } => *wasm_tys = new_tys,
}
}
pub fn get_wasm_tys(&self) -> &Vec<WirmType> {
match self {
Self::Local { wasm_tys, .. } | Self::Global { wasm_tys, .. } => wasm_tys,
}
}
pub fn setup_csv_header(wasm: &mut Module, mem_allocator: &mut MemoryAllocator) -> (u32, u32) {
let mut header = "\n================================= REPORT CSV FLUSH ====================================\nid, id_type, name, whamm_type, wasm_type, script_id, fname, fid, pc, probe_id, value(s)"
.to_string();
mem_allocator.emit_string(wasm, &mut header);
let addr = mem_allocator.emitted_strings.get(&header).unwrap();
(addr.mem_offset as u32, addr.len as u32)
}
pub fn to_csv(&self, id_ty: &str) -> String {
let (name, whamm_ty, wasm_ty, script_id, bytecode_loc, probe_id) = match self {
Metadata::Global {
name,
whamm_ty,
wasm_tys,
script_id,
} => (
name.as_str(),
whamm_ty.to_string(),
get_wasm_tys_str(wasm_tys),
*script_id,
", , ",
"",
),
Metadata::Local {
name,
whamm_ty,
wasm_tys,
script_id,
bytecode_loc,
probe_id,
} => (
name.as_str(),
whamm_ty.to_string(),
get_wasm_tys_str(wasm_tys),
*script_id,
&*bytecode_loc.to_string(),
probe_id.as_str(),
),
};
format!(
"{id_ty}, {name}, {whamm_ty}, {wasm_ty}, script{script_id}, {}, {probe_id}",
bytecode_loc
)
}
}
#[derive(Clone, Debug)]
pub enum LocationData {
Global {
script_id: u8,
},
Local {
script_id: u8,
bytecode_loc: BytecodeLoc,
probe_id: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BytecodeLoc {
pub fid: u32,
pc: u32,
}
impl Display for BytecodeLoc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}, {}", self.fid, self.pc)
}
}
impl BytecodeLoc {
pub(crate) fn new(fid: u32, pc: u32) -> Self {
Self { fid, pc }
}
}
struct ReportAllocTracker {
first_var: Option<u32>,
last_var: Option<u32>,
}
struct FlushTracker {
flush_var_metadata_fid: Option<u32>,
}
fn get_wasm_tys_str(wasm_tys: &[WirmType]) -> String {
let mut res = String::new();
if wasm_tys.len() > 1 {
res.push('(');
}
let mut first = true;
for ty in wasm_tys.iter() {
if !first {
res.push_str("; ");
}
let s = get_wasm_ty_str(ty);
res.push_str(&s);
first = false;
}
if wasm_tys.len() > 1 {
res.push(')');
}
res
}
fn get_wasm_ty_str(wasm_ty: &WirmType) -> String {
let s = match wasm_ty {
WirmType::I8 => "i8",
WirmType::I16 => "i16",
WirmType::I32 => "i32",
WirmType::I64 => "i64",
WirmType::F32 => "f32",
WirmType::F64 => "f64",
WirmType::V128 => "v128",
WirmType::FuncRef => "funcref",
WirmType::FuncRefNull => "funcref_null",
WirmType::ExternRef => "externref",
WirmType::ExternRefNull => "externref_null",
WirmType::Any => "any",
WirmType::AnyNull => "any_null",
WirmType::None => "none",
WirmType::NoneNull => "none_null",
WirmType::NoExtern => "noextern",
WirmType::NoExternNull => "noextern_null",
WirmType::NoFunc => "nofunc",
WirmType::NoFuncNull => "nofunc_null",
WirmType::Eq => "eq",
WirmType::EqNull => "eq_null",
WirmType::Struct => "struct",
WirmType::StructNull => "struct_null",
WirmType::Array => "array",
WirmType::ArrayNull => "array_null",
WirmType::I31 => "i31",
WirmType::I31Null => "i31_null",
WirmType::Exn => "exn",
WirmType::NoExn => "noexn",
WirmType::Module { .. } => "module",
WirmType::RecGroup(_) => "recgroup",
WirmType::CoreTypeId(_) => "core_type_id",
WirmType::Cont => "cont",
WirmType::NoCont => "nocont",
};
s.to_string()
}