mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into wasm-linking-zig9
This commit is contained in:
commit
c950f6d834
106 changed files with 4163 additions and 1740 deletions
|
@ -2,7 +2,7 @@ use bumpalo::{self, collections::Vec};
|
|||
use std::fmt::Write;
|
||||
|
||||
use code_builder::Align;
|
||||
use roc_builtins::bitcode::IntWidth;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
|
||||
|
@ -17,10 +17,10 @@ use roc_error_macros::internal_error;
|
|||
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
|
||||
|
||||
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
||||
use crate::low_level::LowLevelCall;
|
||||
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||
use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol};
|
||||
use crate::wasm_module::sections::{DataMode, DataSegment};
|
||||
use crate::wasm_module::sections::{DataMode, DataSegment, Limits};
|
||||
use crate::wasm_module::{
|
||||
code_builder, CodeBuilder, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule,
|
||||
};
|
||||
|
@ -29,6 +29,22 @@ use crate::{
|
|||
PTR_TYPE, TARGET_INFO,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ProcSource {
|
||||
Roc,
|
||||
Helper,
|
||||
/// Wrapper function for higher-order calls from Zig to Roc
|
||||
HigherOrderWrapper(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcLookupData<'a> {
|
||||
pub name: Symbol,
|
||||
pub layout: ProcLayout<'a>,
|
||||
pub linker_index: u32,
|
||||
pub source: ProcSource,
|
||||
}
|
||||
|
||||
pub struct WasmBackend<'a> {
|
||||
pub env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
|
@ -37,9 +53,9 @@ pub struct WasmBackend<'a> {
|
|||
module: WasmModule<'a>,
|
||||
layout_ids: LayoutIds<'a>,
|
||||
next_constant_addr: u32,
|
||||
fn_index_offset: u32,
|
||||
pub fn_index_offset: u32,
|
||||
called_preload_fns: Vec<'a, u32>,
|
||||
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>,
|
||||
pub proc_lookup: Vec<'a, ProcLookupData<'a>>,
|
||||
helper_proc_gen: CodeGenHelp<'a>,
|
||||
|
||||
// Function-level data
|
||||
|
@ -56,7 +72,7 @@ impl<'a> WasmBackend<'a> {
|
|||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
layout_ids: LayoutIds<'a>,
|
||||
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>,
|
||||
proc_lookup: Vec<'a, ProcLookupData<'a>>,
|
||||
mut module: WasmModule<'a>,
|
||||
fn_index_offset: u32,
|
||||
helper_proc_gen: CodeGenHelp<'a>,
|
||||
|
@ -109,31 +125,45 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn generate_helpers(&mut self) -> Vec<'a, Proc<'a>> {
|
||||
pub fn get_helpers(&mut self) -> Vec<'a, Proc<'a>> {
|
||||
self.helper_proc_gen.take_procs()
|
||||
}
|
||||
|
||||
fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) {
|
||||
let (new_proc_sym, new_proc_layout) = new_proc_info;
|
||||
let wasm_fn_index = self.proc_lookup.len() as u32;
|
||||
pub fn register_helper_proc(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
layout: ProcLayout<'a>,
|
||||
source: ProcSource,
|
||||
) -> u32 {
|
||||
let proc_index = self.proc_lookup.len();
|
||||
let wasm_fn_index = self.fn_index_offset + proc_index as u32;
|
||||
let linker_sym_index = self.module.linking.symbol_table.len() as u32;
|
||||
|
||||
let name = self
|
||||
.layout_ids
|
||||
.get_toplevel(new_proc_sym, &new_proc_layout)
|
||||
.to_symbol_string(new_proc_sym, self.interns);
|
||||
.get_toplevel(symbol, &layout)
|
||||
.to_symbol_string(symbol, self.interns);
|
||||
|
||||
self.proc_lookup.push(ProcLookupData {
|
||||
name: symbol,
|
||||
layout,
|
||||
linker_index: linker_sym_index,
|
||||
source,
|
||||
});
|
||||
|
||||
self.proc_lookup
|
||||
.push((new_proc_sym, new_proc_layout, linker_sym_index));
|
||||
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
|
||||
flags: 0,
|
||||
index: wasm_fn_index,
|
||||
name,
|
||||
});
|
||||
self.module.linking.symbol_table.push(linker_symbol);
|
||||
|
||||
wasm_fn_index
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) {
|
||||
pub fn finalize(mut self) -> (WasmModule<'a>, Vec<'a, u32>) {
|
||||
let fn_table_size = 1 + self.module.element.max_table_index();
|
||||
self.module.table.function_table.limits = Limits::MinMax(fn_table_size, fn_table_size);
|
||||
(self.module, self.called_preload_fns)
|
||||
}
|
||||
|
||||
|
@ -150,8 +180,12 @@ impl<'a> WasmBackend<'a> {
|
|||
#[cfg(not(debug_assertions))]
|
||||
pub fn register_symbol_debug_names(&self) {}
|
||||
|
||||
pub fn get_fn_table_index(&mut self, fn_index: u32) -> i32 {
|
||||
self.module.element.get_fn_table_index(fn_index)
|
||||
}
|
||||
|
||||
/// Create an IR Symbol for an anonymous value (such as ListLiteral)
|
||||
fn create_symbol(&mut self, debug_name: &str) -> Symbol {
|
||||
pub fn create_symbol(&mut self, debug_name: &str) -> Symbol {
|
||||
let ident_ids = self
|
||||
.interns
|
||||
.all_ident_ids
|
||||
|
@ -204,7 +238,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
||||
|
||||
let ret_type = match ret_layout.return_method(CallConv::C) {
|
||||
Primitive(ty) => Some(ty),
|
||||
Primitive(ty, _) => Some(ty),
|
||||
NoReturnValue => None,
|
||||
WriteToPointerArg => {
|
||||
self.storage.arg_types.push(PTR_TYPE);
|
||||
|
@ -251,20 +285,156 @@ impl<'a> WasmBackend<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
fn append_proc_debug_name(&mut self, name: Symbol) {
|
||||
fn append_proc_debug_name(&mut self, sym: Symbol) {
|
||||
let proc_index = self
|
||||
.proc_lookup
|
||||
.iter()
|
||||
.position(|(n, _, _)| *n == name)
|
||||
.position(|ProcLookupData { name, .. }| *name == sym)
|
||||
.unwrap();
|
||||
let wasm_fn_index = self.fn_index_offset + proc_index as u32;
|
||||
|
||||
let mut debug_name = bumpalo::collections::String::with_capacity_in(64, self.env.arena);
|
||||
write!(debug_name, "{:?}", name).unwrap();
|
||||
write!(debug_name, "{:?}", sym).unwrap();
|
||||
let name_bytes = debug_name.into_bytes().into_bump_slice();
|
||||
self.module.names.append_function(wasm_fn_index, name_bytes);
|
||||
}
|
||||
|
||||
/// Build a wrapper around a Roc procedure so that it can be called from our higher-order Zig builtins.
|
||||
///
|
||||
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
|
||||
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
|
||||
/// Everything else is passed by reference, so we can just pass the pointer through.
|
||||
///
|
||||
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
|
||||
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
|
||||
pub fn build_higher_order_wrapper(
|
||||
&mut self,
|
||||
wrapper_lookup_idx: usize,
|
||||
inner_lookup_idx: usize,
|
||||
) {
|
||||
use Align::*;
|
||||
use ValueType::*;
|
||||
|
||||
let ProcLookupData {
|
||||
name: wrapper_name,
|
||||
layout: wrapper_proc_layout,
|
||||
..
|
||||
} = self.proc_lookup[wrapper_lookup_idx];
|
||||
let wrapper_arg_layouts = wrapper_proc_layout.arguments;
|
||||
|
||||
// Our convention is that the last arg of the wrapper is the heap return pointer
|
||||
let heap_return_ptr_id = LocalId(wrapper_arg_layouts.len() as u32 - 1);
|
||||
let inner_ret_layout = match wrapper_arg_layouts.last() {
|
||||
Some(Layout::Boxed(inner)) => WasmLayout::new(inner),
|
||||
x => internal_error!("Higher-order wrapper: invalid return layout {:?}", x),
|
||||
};
|
||||
|
||||
let mut n_inner_wasm_args = 0;
|
||||
let ret_type_and_size = match inner_ret_layout.return_method(CallConv::C) {
|
||||
ReturnMethod::NoReturnValue => None,
|
||||
ReturnMethod::Primitive(ty, size) => {
|
||||
// If the inner function returns a primitive, load the address to store it at
|
||||
// After the call, it will be under the call result in the value stack
|
||||
self.code_builder.get_local(heap_return_ptr_id);
|
||||
Some((ty, size))
|
||||
}
|
||||
ReturnMethod::WriteToPointerArg => {
|
||||
// If the inner function writes to a return pointer, load its address
|
||||
self.code_builder.get_local(heap_return_ptr_id);
|
||||
n_inner_wasm_args += 1;
|
||||
None
|
||||
}
|
||||
x => internal_error!("A Roc function should never use ReturnMethod {:?}", x),
|
||||
};
|
||||
|
||||
// Load all the arguments for the inner function
|
||||
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
|
||||
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner)
|
||||
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
|
||||
if is_closure_data || is_return_pointer || wrapper_arg.stack_size(TARGET_INFO) == 0 {
|
||||
continue;
|
||||
}
|
||||
n_inner_wasm_args += 1;
|
||||
|
||||
// Load wrapper argument. They're all pointers.
|
||||
self.code_builder.get_local(LocalId(i as u32));
|
||||
|
||||
// Dereference any primitive-valued arguments
|
||||
match wrapper_arg {
|
||||
Layout::Boxed(inner_arg) => match inner_arg {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
|
||||
self.code_builder.i32_load8_u(Bytes1, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
|
||||
self.code_builder.i32_load16_u(Bytes2, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
|
||||
self.code_builder.i32_load(Bytes4, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
|
||||
self.code_builder.i64_load(Bytes8, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
self.code_builder.f32_load(Bytes4, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
self.code_builder.f64_load(Bytes8, 0);
|
||||
}
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
self.code_builder.i32_load8_u(Bytes1, 0);
|
||||
}
|
||||
_ => {
|
||||
// Any other layout is a pointer, which we've already loaded. Nothing to do!
|
||||
}
|
||||
},
|
||||
x => internal_error!("Higher-order wrapper: expected a Box layout, got {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
// If the inner function has closure data, it's the last arg of the inner fn
|
||||
let closure_data_layout = wrapper_arg_layouts[0];
|
||||
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
|
||||
self.code_builder.get_local(LocalId(0));
|
||||
}
|
||||
|
||||
// Call the wrapped inner function
|
||||
let lookup = &self.proc_lookup[inner_lookup_idx];
|
||||
let inner_wasm_fn_index = self.fn_index_offset + inner_lookup_idx as u32;
|
||||
let has_return_val = ret_type_and_size.is_some();
|
||||
self.code_builder.call(
|
||||
inner_wasm_fn_index,
|
||||
lookup.linker_index,
|
||||
n_inner_wasm_args,
|
||||
has_return_val,
|
||||
);
|
||||
|
||||
// If the inner function returns a primitive, store it to the address we loaded at the very beginning
|
||||
if let Some((ty, size)) = ret_type_and_size {
|
||||
match (ty, size) {
|
||||
(I64, 8) => self.code_builder.i64_store(Bytes8, 0),
|
||||
(I32, 4) => self.code_builder.i32_store(Bytes4, 0),
|
||||
(I32, 2) => self.code_builder.i32_store16(Bytes2, 0),
|
||||
(I32, 1) => self.code_builder.i32_store8(Bytes1, 0),
|
||||
(F32, 4) => self.code_builder.f32_store(Bytes4, 0),
|
||||
(F64, 8) => self.code_builder.f64_store(Bytes8, 0),
|
||||
_ => {
|
||||
internal_error!("Cannot store {:?} with alignment of {:?}", ty, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write empty function header (local variables array with zero length)
|
||||
self.code_builder.build_fn_header_and_footer(&[], 0, None);
|
||||
|
||||
self.module.add_function_signature(Signature {
|
||||
param_types: bumpalo::vec![in self.env.arena; I32; wrapper_arg_layouts.len()],
|
||||
ret_type: None,
|
||||
});
|
||||
|
||||
self.append_proc_debug_name(wrapper_name);
|
||||
self.reset();
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
STATEMENTS
|
||||
|
@ -551,8 +721,8 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
// If any new specializations were created, register their symbol data
|
||||
for spec in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec);
|
||||
for (spec_sym, spec_layout) in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
|
||||
}
|
||||
|
||||
self.stmt(rc_stmt);
|
||||
|
@ -827,11 +997,16 @@ impl<'a> WasmBackend<'a> {
|
|||
ret_storage,
|
||||
)
|
||||
}
|
||||
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage)
|
||||
}
|
||||
|
||||
x => todo!("call type {:?}", x),
|
||||
CallType::HigherOrder(higher_order_lowlevel) => {
|
||||
call_higher_order_lowlevel(self, ret_sym, ret_layout, *higher_order_lowlevel)
|
||||
}
|
||||
|
||||
CallType::Foreign { .. } => todo!("CallType::Foreign"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -864,8 +1039,13 @@ impl<'a> WasmBackend<'a> {
|
|||
);
|
||||
debug_assert!(!ret_zig_packed_struct);
|
||||
|
||||
let iter = self.proc_lookup.iter().enumerate();
|
||||
for (roc_proc_index, (ir_sym, pl, linker_sym_index)) in iter {
|
||||
for (roc_proc_index, lookup) in self.proc_lookup.iter().enumerate() {
|
||||
let ProcLookupData {
|
||||
name: ir_sym,
|
||||
layout: pl,
|
||||
linker_index: linker_sym_index,
|
||||
..
|
||||
} = lookup;
|
||||
if *ir_sym == func_sym && pl == proc_layout {
|
||||
let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32;
|
||||
self.code_builder.call(
|
||||
|
@ -945,8 +1125,8 @@ impl<'a> WasmBackend<'a> {
|
|||
.call_specialized_equals(ident_ids, arg_layout, arguments);
|
||||
|
||||
// If any new specializations were created, register their symbol data
|
||||
for spec in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec);
|
||||
for (spec_sym, spec_layout) in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
|
||||
}
|
||||
|
||||
// Generate Wasm code for the IR call expression
|
||||
|
@ -1430,8 +1610,8 @@ impl<'a> WasmBackend<'a> {
|
|||
.call_reset_refcount(ident_ids, layout, argument);
|
||||
|
||||
// If any new specializations were created, register their symbol data
|
||||
for spec in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec);
|
||||
for (spec_sym, spec_layout) in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
|
||||
}
|
||||
|
||||
// Generate Wasm code for the IR call expression
|
||||
|
@ -1442,4 +1622,30 @@ impl<'a> WasmBackend<'a> {
|
|||
ret_storage,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a refcount increment procedure and return its Wasm function index
|
||||
pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 {
|
||||
let ident_ids = self
|
||||
.interns
|
||||
.all_ident_ids
|
||||
.get_mut(&self.env.module_id)
|
||||
.unwrap();
|
||||
|
||||
let (proc_symbol, new_specializations) = self
|
||||
.helper_proc_gen
|
||||
.gen_refcount_inc_proc(ident_ids, layout);
|
||||
|
||||
// If any new specializations were created, register their symbol data
|
||||
for (spec_sym, spec_layout) in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
|
||||
}
|
||||
|
||||
let proc_index = self
|
||||
.proc_lookup
|
||||
.iter()
|
||||
.position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout)
|
||||
.unwrap();
|
||||
|
||||
self.fn_index_offset + proc_index as u32
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO};
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ReturnMethod {
|
||||
/// This layout is returned from a Wasm function "normally" as a Primitive
|
||||
Primitive(ValueType),
|
||||
Primitive(ValueType, u32),
|
||||
/// This layout is returned by writing to a pointer passed as the first argument
|
||||
WriteToPointerArg,
|
||||
/// This layout is empty and requires no return value or argument (e.g. refcount helpers)
|
||||
|
@ -46,8 +46,7 @@ impl WasmLayout {
|
|||
use UnionLayout::*;
|
||||
use ValueType::*;
|
||||
|
||||
let size = layout.stack_size(TARGET_INFO);
|
||||
let alignment_bytes = layout.alignment_bytes(TARGET_INFO);
|
||||
let (size, alignment_bytes) = layout.stack_size_and_alignment(TARGET_INFO);
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Int(int_width)) => {
|
||||
|
@ -86,9 +85,10 @@ impl WasmLayout {
|
|||
format: StackMemoryFormat::Decimal,
|
||||
},
|
||||
|
||||
Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()),
|
||||
|
||||
Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_))
|
||||
| Layout::Struct { .. }
|
||||
| Layout::LambdaSet(_)
|
||||
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
|
||||
size,
|
||||
alignment_bytes,
|
||||
|
@ -125,7 +125,7 @@ impl WasmLayout {
|
|||
|
||||
pub fn return_method(&self, conv: CallConv) -> ReturnMethod {
|
||||
match self {
|
||||
Self::Primitive(ty, _) => ReturnMethod::Primitive(*ty),
|
||||
Self::Primitive(ty, size) => ReturnMethod::Primitive(*ty, *size),
|
||||
Self::StackMemory { size, format, .. } => {
|
||||
conv.stack_memory_return_method(*size, *format)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use roc_mono::ir::{Proc, ProcLayout};
|
|||
use roc_mono::layout::LayoutIds;
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
use crate::backend::WasmBackend;
|
||||
use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
||||
use crate::wasm_module::{
|
||||
Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule,
|
||||
};
|
||||
|
@ -107,7 +107,12 @@ pub fn build_module_without_wrapper<'a>(
|
|||
let linker_sym_index = linker_symbols.len() as u32;
|
||||
|
||||
// linker_sym_index is redundant for these procs from user code, but needed for generated helpers!
|
||||
proc_lookup.push((sym, proc_layout, linker_sym_index));
|
||||
proc_lookup.push(ProcLookupData {
|
||||
name: sym,
|
||||
layout: proc_layout,
|
||||
linker_index: linker_sym_index,
|
||||
source: ProcSource::Roc,
|
||||
});
|
||||
linker_symbols.push(linker_sym);
|
||||
|
||||
fn_index += 1;
|
||||
|
@ -134,7 +139,7 @@ pub fn build_module_without_wrapper<'a>(
|
|||
println!("## procs");
|
||||
for proc in procs.iter() {
|
||||
println!("{}", proc.to_pretty(200));
|
||||
// println!("{:#?}", proc);
|
||||
// println!("{:?}", proc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +149,7 @@ pub fn build_module_without_wrapper<'a>(
|
|||
}
|
||||
|
||||
// Generate specialized helpers for refcounting & equality
|
||||
let helper_procs = backend.generate_helpers();
|
||||
let helper_procs = backend.get_helpers();
|
||||
|
||||
backend.register_symbol_debug_names();
|
||||
|
||||
|
@ -156,9 +161,22 @@ pub fn build_module_without_wrapper<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// Generate Wasm for refcounting procs
|
||||
for proc in helper_procs.iter() {
|
||||
backend.build_proc(proc);
|
||||
// Generate Wasm for helpers and Zig/Roc wrappers
|
||||
let sources = Vec::from_iter_in(
|
||||
backend
|
||||
.proc_lookup
|
||||
.iter()
|
||||
.map(|ProcLookupData { source, .. }| *source),
|
||||
env.arena,
|
||||
);
|
||||
let mut helper_iter = helper_procs.iter();
|
||||
for (idx, source) in sources.iter().enumerate() {
|
||||
use ProcSource::*;
|
||||
match source {
|
||||
Roc => { /* already generated */ }
|
||||
Helper => backend.build_proc(helper_iter.next().unwrap()),
|
||||
HigherOrderWrapper(inner_idx) => backend.build_higher_order_wrapper(idx, *inner_idx),
|
||||
}
|
||||
}
|
||||
|
||||
let (module, called_preload_fns) = backend.finalize();
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::low_level::{LowLevel, LowLevel::*};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout};
|
||||
use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
||||
use roc_mono::low_level::HigherOrder;
|
||||
|
||||
use crate::backend::WasmBackend;
|
||||
use crate::layout::CallConv;
|
||||
use crate::layout::{StackMemoryFormat, WasmLayout};
|
||||
use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
||||
use crate::layout::{CallConv, StackMemoryFormat, WasmLayout};
|
||||
use crate::storage::{StackMemoryLocation, StoredValue};
|
||||
use crate::wasm_module::{Align, ValueType};
|
||||
use crate::TARGET_INFO;
|
||||
|
||||
/// Number types used for Wasm code gen
|
||||
/// Unlike other enums, this contains no details about layout or storage.
|
||||
|
@ -195,6 +198,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
/// Main entrypoint from WasmBackend
|
||||
pub fn generate(&self, backend: &mut WasmBackend<'a>) {
|
||||
use CodeGenNumType::*;
|
||||
use LowLevel::*;
|
||||
|
||||
let panic_ret_type = || {
|
||||
internal_error!(
|
||||
|
@ -286,14 +290,21 @@ impl<'a> LowLevelCall<'a> {
|
|||
_ => internal_error!("invalid storage for List"),
|
||||
},
|
||||
|
||||
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
|
||||
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
|
||||
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
|
||||
internal_error!("HigherOrder lowlevels should not be handled here")
|
||||
}
|
||||
|
||||
ListGetUnsafe | ListReplaceUnsafe | ListSingle | ListRepeat | ListReverse
|
||||
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
|
||||
| ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf
|
||||
| ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs
|
||||
| ListSortWith | ListSublist | ListDropAt | ListSwap | ListAny | ListAll
|
||||
| ListFindUnsafe | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
|
||||
| DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection
|
||||
| DictDifference | DictWalk | SetFromList => {
|
||||
| ListSublist | ListDropAt | ListSwap => {
|
||||
todo!("{:?}", self.lowlevel);
|
||||
}
|
||||
|
||||
DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
|
||||
| DictKeys | DictValues | DictUnion | DictIntersection | DictDifference
|
||||
| SetFromList => {
|
||||
todo!("{:?}", self.lowlevel);
|
||||
}
|
||||
|
||||
|
@ -687,9 +698,15 @@ impl<'a> LowLevelCall<'a> {
|
|||
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
|
||||
}
|
||||
}
|
||||
NumToFloatCast => {
|
||||
todo!("implement toF32 and toF64");
|
||||
}
|
||||
NumToIntChecked => {
|
||||
todo!()
|
||||
}
|
||||
NumToFloatChecked => {
|
||||
todo!("implement toF32Checked and toF64Checked");
|
||||
}
|
||||
And => {
|
||||
self.load_args(backend);
|
||||
backend.code_builder.i32_and();
|
||||
|
@ -731,7 +748,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
"Cannot do `==` comparison on different types"
|
||||
);
|
||||
|
||||
let invert_result = matches!(self.lowlevel, NotEq);
|
||||
let invert_result = matches!(self.lowlevel, LowLevel::NotEq);
|
||||
|
||||
match arg_layout {
|
||||
Layout::Builtin(
|
||||
|
@ -942,3 +959,144 @@ fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_higher_order_lowlevel<'a>(
|
||||
backend: &mut WasmBackend<'a>,
|
||||
return_sym: Symbol,
|
||||
return_layout: &Layout<'a>,
|
||||
higher_order: &'a HigherOrderLowLevel<'a>,
|
||||
) {
|
||||
use HigherOrder::*;
|
||||
|
||||
let HigherOrderLowLevel {
|
||||
op,
|
||||
passed_function,
|
||||
..
|
||||
} = higher_order;
|
||||
|
||||
let PassedFunction {
|
||||
name: fn_name,
|
||||
argument_layouts,
|
||||
return_layout: result_layout,
|
||||
owns_captured_environment,
|
||||
captured_environment,
|
||||
..
|
||||
} = passed_function;
|
||||
|
||||
let closure_data_layout = match backend.storage.symbol_layouts[captured_environment] {
|
||||
Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation(),
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => Layout::UNIT,
|
||||
x => internal_error!("Closure data has an invalid layout\n{:?}", x),
|
||||
};
|
||||
let closure_data_exists: bool = closure_data_layout != Layout::UNIT;
|
||||
|
||||
// We create a wrapper around the passed function, which just unboxes the arguments.
|
||||
// This allows Zig builtins to have a generic pointer-based interface.
|
||||
let source = {
|
||||
let passed_proc_layout = ProcLayout {
|
||||
arguments: argument_layouts,
|
||||
result: *result_layout,
|
||||
};
|
||||
let passed_proc_index = backend
|
||||
.proc_lookup
|
||||
.iter()
|
||||
.position(|ProcLookupData { name, layout, .. }| {
|
||||
name == fn_name && layout == &passed_proc_layout
|
||||
})
|
||||
.unwrap();
|
||||
ProcSource::HigherOrderWrapper(passed_proc_index)
|
||||
};
|
||||
let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name));
|
||||
let wrapper_layout = {
|
||||
let mut wrapper_arg_layouts: Vec<Layout<'a>> =
|
||||
Vec::with_capacity_in(argument_layouts.len() + 1, backend.env.arena);
|
||||
|
||||
let n_non_closure_args = if closure_data_exists {
|
||||
argument_layouts.len() - 1
|
||||
} else {
|
||||
argument_layouts.len()
|
||||
};
|
||||
|
||||
wrapper_arg_layouts.push(closure_data_layout);
|
||||
wrapper_arg_layouts.extend(
|
||||
argument_layouts
|
||||
.iter()
|
||||
.take(n_non_closure_args)
|
||||
.map(Layout::Boxed),
|
||||
);
|
||||
wrapper_arg_layouts.push(Layout::Boxed(result_layout));
|
||||
|
||||
ProcLayout {
|
||||
arguments: wrapper_arg_layouts.into_bump_slice(),
|
||||
result: Layout::UNIT,
|
||||
}
|
||||
};
|
||||
|
||||
let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source);
|
||||
let inc_fn_idx = backend.gen_refcount_inc_for_zig(closure_data_layout);
|
||||
|
||||
let wrapper_fn_ptr = backend.get_fn_table_index(wrapper_fn_idx);
|
||||
let inc_fn_ptr = backend.get_fn_table_index(inc_fn_idx);
|
||||
|
||||
match op {
|
||||
// List.map : List elem_x, (elem_x -> elem_ret) -> List elem_ret
|
||||
ListMap { xs } => {
|
||||
let list_layout_in = backend.storage.symbol_layouts[xs];
|
||||
|
||||
let (elem_x, elem_ret) = match (list_layout_in, return_layout) {
|
||||
(
|
||||
Layout::Builtin(Builtin::List(elem_x)),
|
||||
Layout::Builtin(Builtin::List(elem_ret)),
|
||||
) => (elem_x, elem_ret),
|
||||
_ => unreachable!("invalid layout for List.map arguments"),
|
||||
};
|
||||
let elem_x_size = elem_x.stack_size(TARGET_INFO);
|
||||
let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO);
|
||||
|
||||
let cb = &mut backend.code_builder;
|
||||
|
||||
// Load return pointer & argument values
|
||||
// Wasm signature: (i32, i64, i64, i32, i32, i32, i32, i32, i32, i32) -> nil
|
||||
backend.storage.load_symbols(cb, &[return_sym]);
|
||||
backend.storage.load_symbol_zig(cb, *xs); // list with capacity = 2 x i64 args
|
||||
cb.i32_const(wrapper_fn_ptr);
|
||||
if closure_data_exists {
|
||||
backend.storage.load_symbols(cb, &[*captured_environment]);
|
||||
} else {
|
||||
// Normally, a zero-size arg would be eliminated in code gen, but Zig expects one!
|
||||
cb.i32_const(0); // null pointer
|
||||
}
|
||||
cb.i32_const(inc_fn_ptr);
|
||||
cb.i32_const(*owns_captured_environment as i32);
|
||||
cb.i32_const(elem_ret_align as i32); // used for allocating the new list
|
||||
cb.i32_const(elem_x_size as i32);
|
||||
cb.i32_const(elem_ret_size as i32);
|
||||
|
||||
let num_wasm_args = 10; // 1 return pointer + 8 Zig args + list 2nd i64
|
||||
let has_return_val = false;
|
||||
backend.call_zig_builtin_after_loading_args(
|
||||
bitcode::LIST_MAP,
|
||||
num_wasm_args,
|
||||
has_return_val,
|
||||
);
|
||||
}
|
||||
|
||||
ListMap2 { .. }
|
||||
| ListMap3 { .. }
|
||||
| ListMap4 { .. }
|
||||
| ListMapWithIndex { .. }
|
||||
| ListKeepIf { .. }
|
||||
| ListWalk { .. }
|
||||
| ListWalkUntil { .. }
|
||||
| ListWalkBackwards { .. }
|
||||
| ListKeepOks { .. }
|
||||
| ListKeepErrs { .. }
|
||||
| ListSortWith { .. }
|
||||
| ListAny { .. }
|
||||
| ListAll { .. }
|
||||
| ListFindUnsafe { .. }
|
||||
| DictWalk { .. } => todo!("{:?}", op),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -299,7 +299,8 @@ impl<'a> Storage<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_symbol_zig(&mut self, code_builder: &mut CodeBuilder, arg: Symbol) {
|
||||
// TODO: expose something higher level instead, shared among higher-order calls
|
||||
pub fn load_symbol_zig(&mut self, code_builder: &mut CodeBuilder, arg: Symbol) {
|
||||
if let StoredValue::StackMemory {
|
||||
location,
|
||||
size,
|
||||
|
@ -402,7 +403,7 @@ impl<'a> Storage<'a> {
|
|||
|
||||
let return_method = return_layout.return_method(call_conv);
|
||||
let has_return_val = match return_method {
|
||||
Primitive(_) => true,
|
||||
Primitive(..) => true,
|
||||
NoReturnValue => false,
|
||||
WriteToPointerArg => {
|
||||
num_wasm_args += 1;
|
||||
|
@ -508,7 +509,7 @@ impl<'a> Storage<'a> {
|
|||
size
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ impl std::fmt::Debug for VmBlock<'_> {
|
|||
/// Rust representation matches Wasm encoding.
|
||||
/// It's an error to specify alignment higher than the "natural" alignment of the instruction
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
pub enum Align {
|
||||
Bytes1 = 0,
|
||||
Bytes2 = 1,
|
||||
|
@ -78,6 +78,23 @@ pub enum Align {
|
|||
Bytes8 = 3,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
/// Calculate the largest possible alignment for a load/store at a given stack frame offset
|
||||
/// Assumes the stack frame is aligned to at least 8 bytes
|
||||
pub fn from_stack_offset(max_align: Align, offset: u32) -> Align {
|
||||
if (max_align == Align::Bytes8) && (offset & 7 == 0) {
|
||||
return Align::Bytes8;
|
||||
}
|
||||
if (max_align >= Align::Bytes4) && (offset & 3 == 0) {
|
||||
return Align::Bytes4;
|
||||
}
|
||||
if (max_align >= Align::Bytes2) && (offset & 1 == 0) {
|
||||
return Align::Bytes2;
|
||||
}
|
||||
Align::Bytes1
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Align {
|
||||
fn from(x: u32) -> Align {
|
||||
match x {
|
||||
|
|
|
@ -14,7 +14,8 @@ pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}
|
|||
use self::linking::{LinkingSection, RelocationSection};
|
||||
use self::sections::{
|
||||
CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection,
|
||||
ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection,
|
||||
ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TableSection,
|
||||
TypeSection,
|
||||
};
|
||||
use self::serialize::{SerialBuffer, Serialize};
|
||||
|
||||
|
@ -25,7 +26,7 @@ pub struct WasmModule<'a> {
|
|||
pub types: TypeSection<'a>,
|
||||
pub import: ImportSection<'a>,
|
||||
pub function: FunctionSection<'a>,
|
||||
pub table: OpaqueSection<'a>,
|
||||
pub table: TableSection,
|
||||
pub memory: MemorySection<'a>,
|
||||
pub global: GlobalSection<'a>,
|
||||
pub export: ExportSection<'a>,
|
||||
|
@ -138,7 +139,7 @@ impl<'a> WasmModule<'a> {
|
|||
let function = FunctionSection::preload(arena, bytes, &mut cursor);
|
||||
let defined_fn_signatures = function.parse(arena);
|
||||
|
||||
let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor);
|
||||
let table = TableSection::preload(bytes, &mut cursor);
|
||||
|
||||
let memory = MemorySection::preload(arena, bytes, &mut cursor);
|
||||
|
||||
|
|
|
@ -276,33 +276,6 @@ impl<'a> Section<'a> for TypeSection<'a> {
|
|||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum RefType {
|
||||
Func = 0x70,
|
||||
Extern = 0x6f,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableType {
|
||||
pub ref_type: RefType,
|
||||
pub limits: Limits,
|
||||
}
|
||||
|
||||
impl Serialize for TableType {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(self.ref_type as u8);
|
||||
self.limits.serialize(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SkipBytes for TableType {
|
||||
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
|
||||
u8::skip_bytes(bytes, cursor);
|
||||
Limits::skip_bytes(bytes, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImportDesc {
|
||||
Func { signature_index: u32 },
|
||||
|
@ -457,6 +430,104 @@ impl<'a> FunctionSection<'a> {
|
|||
|
||||
section_impl!(FunctionSection, SectionId::Function);
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Table section
|
||||
*
|
||||
* Defines tables used for indirect references to host memory.
|
||||
* The table *contents* are elsewhere, in the ElementSection.
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum RefType {
|
||||
Func = 0x70,
|
||||
Extern = 0x6f,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableType {
|
||||
pub ref_type: RefType,
|
||||
pub limits: Limits,
|
||||
}
|
||||
|
||||
impl Serialize for TableType {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(self.ref_type as u8);
|
||||
self.limits.serialize(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SkipBytes for TableType {
|
||||
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
|
||||
u8::skip_bytes(bytes, cursor);
|
||||
Limits::skip_bytes(bytes, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableSection {
|
||||
pub function_table: TableType,
|
||||
}
|
||||
|
||||
impl TableSection {
|
||||
const ID: SectionId = SectionId::Table;
|
||||
|
||||
pub fn preload(module_bytes: &[u8], mod_cursor: &mut usize) -> Self {
|
||||
let (count, section_bytes) = parse_section(Self::ID, module_bytes, mod_cursor);
|
||||
|
||||
match count {
|
||||
0 => TableSection {
|
||||
function_table: TableType {
|
||||
ref_type: RefType::Func,
|
||||
limits: Limits::MinMax(0, 0),
|
||||
},
|
||||
},
|
||||
1 => {
|
||||
if section_bytes[0] != RefType::Func as u8 {
|
||||
internal_error!("Only funcref tables are supported")
|
||||
}
|
||||
let mut section_cursor = 1;
|
||||
let limits = Limits::parse(section_bytes, &mut section_cursor);
|
||||
|
||||
TableSection {
|
||||
function_table: TableType {
|
||||
ref_type: RefType::Func,
|
||||
limits,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => internal_error!("Multiple tables are not supported"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
let section_id_bytes = 1;
|
||||
let section_length_bytes = 1;
|
||||
let num_tables_bytes = 1;
|
||||
let ref_type_bytes = 1;
|
||||
let limits_bytes = match self.function_table.limits {
|
||||
Limits::Min(_) => MAX_SIZE_ENCODED_U32,
|
||||
Limits::MinMax(..) => 2 * MAX_SIZE_ENCODED_U32,
|
||||
};
|
||||
|
||||
section_id_bytes + section_length_bytes + num_tables_bytes + ref_type_bytes + limits_bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for TableSection {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
let header_indices = write_section_header(buffer, Self::ID);
|
||||
|
||||
let num_tables: u32 = 1;
|
||||
num_tables.serialize(buffer);
|
||||
self.function_table.serialize(buffer);
|
||||
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Memory section
|
||||
|
@ -502,6 +573,21 @@ impl SkipBytes for Limits {
|
|||
}
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
fn parse(bytes: &[u8], cursor: &mut usize) -> Self {
|
||||
let variant_id = bytes[*cursor];
|
||||
*cursor += 1;
|
||||
|
||||
let min = parse_u32_or_panic(bytes, cursor);
|
||||
if variant_id == LimitsId::MinMax as u8 {
|
||||
let max = parse_u32_or_panic(bytes, cursor);
|
||||
Limits::MinMax(min, max)
|
||||
} else {
|
||||
Limits::Min(min)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MemorySection<'a> {
|
||||
pub count: u32,
|
||||
|
@ -584,6 +670,13 @@ impl ConstExpr {
|
|||
|
||||
value
|
||||
}
|
||||
|
||||
fn unwrap_i32(&self) -> i32 {
|
||||
match self {
|
||||
Self::I32(x) => *x,
|
||||
_ => internal_error!("Expected ConstExpr to be I32"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ConstExpr {
|
||||
|
@ -781,9 +874,10 @@ enum ElementSegmentFormatId {
|
|||
ActiveImplicitTableIndex = 0x00,
|
||||
}
|
||||
|
||||
/// A Segment initialises a subrange of elements in a table. Normally there's just one Segment.
|
||||
#[derive(Debug)]
|
||||
struct ElementSegment<'a> {
|
||||
offset: ConstExpr,
|
||||
offset: ConstExpr, // The starting table index for the segment
|
||||
fn_indices: Vec<'a, u32>,
|
||||
}
|
||||
|
||||
|
@ -834,6 +928,8 @@ impl<'a> Serialize for ElementSegment<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// An Element is an entry in a Table (see TableSection)
|
||||
/// The only currently supported Element type is a function reference, used for indirect calls.
|
||||
#[derive(Debug)]
|
||||
pub struct ElementSection<'a> {
|
||||
segments: Vec<'a, ElementSegment<'a>>,
|
||||
|
@ -845,17 +941,57 @@ impl<'a> ElementSection<'a> {
|
|||
pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self {
|
||||
let (num_segments, body_bytes) = parse_section(Self::ID, module_bytes, cursor);
|
||||
|
||||
let mut segments = Vec::with_capacity_in(num_segments as usize, arena);
|
||||
if num_segments == 0 {
|
||||
let seg = ElementSegment {
|
||||
offset: ConstExpr::I32(1),
|
||||
fn_indices: bumpalo::vec![in arena],
|
||||
};
|
||||
ElementSection {
|
||||
segments: bumpalo::vec![in arena; seg],
|
||||
}
|
||||
} else {
|
||||
let mut segments = Vec::with_capacity_in(num_segments as usize, arena);
|
||||
|
||||
let mut body_cursor = 0;
|
||||
for _ in 0..num_segments {
|
||||
let seg = ElementSegment::parse(arena, body_bytes, &mut body_cursor);
|
||||
segments.push(seg);
|
||||
let mut body_cursor = 0;
|
||||
for _ in 0..num_segments {
|
||||
let seg = ElementSegment::parse(arena, body_bytes, &mut body_cursor);
|
||||
segments.push(seg);
|
||||
}
|
||||
ElementSection { segments }
|
||||
}
|
||||
|
||||
ElementSection { segments }
|
||||
}
|
||||
|
||||
/// Get a table index for a function (equivalent to a function pointer)
|
||||
/// The function will be inserted into the table if it's not already there.
|
||||
/// This index is what the call_indirect instruction expects.
|
||||
/// (This works mostly the same as function pointers, except hackers can't jump to arbitrary code)
|
||||
pub fn get_fn_table_index(&mut self, fn_index: u32) -> i32 {
|
||||
// In practice there is always one segment. We allow a bit more generality by using the last one.
|
||||
let segment = self.segments.last_mut().unwrap();
|
||||
let offset = segment.offset.unwrap_i32();
|
||||
let pos = segment.fn_indices.iter().position(|f| *f == fn_index);
|
||||
if let Some(existing_table_index) = pos {
|
||||
offset + existing_table_index as i32
|
||||
} else {
|
||||
let new_table_index = segment.fn_indices.len();
|
||||
segment.fn_indices.push(fn_index);
|
||||
offset + new_table_index as i32
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of elements in the table
|
||||
pub fn max_table_index(&self) -> u32 {
|
||||
let mut result = 0;
|
||||
for s in self.segments.iter() {
|
||||
let max_index = s.offset.unwrap_i32() + s.fn_indices.len() as i32;
|
||||
if max_index > result {
|
||||
result = max_index;
|
||||
}
|
||||
}
|
||||
result as u32
|
||||
}
|
||||
|
||||
/// Approximate serialized byte size (for buffer capacity)
|
||||
pub fn size(&self) -> usize {
|
||||
self.segments.iter().map(|seg| seg.size()).sum()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue