mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Merge remote-tracking branch 'origin/trunk' into decimal-literals
This commit is contained in:
commit
5529841d68
404 changed files with 9919 additions and 6764 deletions
|
@ -2,12 +2,13 @@ use bumpalo::{self, collections::Vec};
|
|||
|
||||
use code_builder::Align;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Layout, LayoutIds};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::low_level::{build_call_low_level, LowlevelBuildResult};
|
||||
use crate::low_level::{decode_low_level, LowlevelBuildResult};
|
||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||
use crate::wasm_module::linking::{
|
||||
DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK,
|
||||
|
@ -22,8 +23,8 @@ use crate::wasm_module::{
|
|||
LocalId, Signature, SymInfo, ValueType,
|
||||
};
|
||||
use crate::{
|
||||
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_TYPE,
|
||||
STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
|
||||
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
|
||||
PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
|
||||
};
|
||||
|
||||
/// The memory address where the constants data will be loaded during module instantiation.
|
||||
|
@ -294,16 +295,17 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
_ => {
|
||||
self.storage.load_symbols(&mut self.code_builder, &[*sym]);
|
||||
self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
|
||||
}
|
||||
}
|
||||
// jump to the "stack frame pop" code at the end of the function
|
||||
self.code_builder.br(self.block_depth - 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Switch {
|
||||
cond_symbol,
|
||||
cond_layout: _,
|
||||
cond_layout,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_layout: _,
|
||||
|
@ -321,21 +323,45 @@ impl<'a> WasmBackend<'a> {
|
|||
cond_storage,
|
||||
);
|
||||
|
||||
// create (number_of_branches - 1) new blocks.
|
||||
// create a block for each branch except the default
|
||||
for _ in 0..branches.len() {
|
||||
self.start_block(BlockType::NoResult)
|
||||
}
|
||||
|
||||
let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Int1));
|
||||
let cond_type = WasmLayout::new(cond_layout).value_type();
|
||||
|
||||
// then, we jump whenever the value under scrutiny is equal to the value of a branch
|
||||
for (i, (value, _, _)) in branches.iter().enumerate() {
|
||||
// put the cond_symbol on the top of the stack
|
||||
self.storage
|
||||
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
|
||||
|
||||
self.code_builder.i32_const(*value as i32);
|
||||
|
||||
// compare the 2 topmost values
|
||||
self.code_builder.i32_eq();
|
||||
if is_bool {
|
||||
// We already have a bool, don't need to compare against a const to get one
|
||||
if *value == 0 {
|
||||
self.code_builder.i32_eqz();
|
||||
}
|
||||
} else {
|
||||
match cond_type {
|
||||
ValueType::I32 => {
|
||||
self.code_builder.i32_const(*value as i32);
|
||||
self.code_builder.i32_eq();
|
||||
}
|
||||
ValueType::I64 => {
|
||||
self.code_builder.i64_const(*value as i64);
|
||||
self.code_builder.i64_eq();
|
||||
}
|
||||
ValueType::F32 => {
|
||||
self.code_builder.f32_const(f32::from_bits(*value as u32));
|
||||
self.code_builder.f32_eq();
|
||||
}
|
||||
ValueType::F64 => {
|
||||
self.code_builder.f64_const(f64::from_bits(*value as u64));
|
||||
self.code_builder.f64_eq();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "break" out of `i` surrounding blocks
|
||||
self.code_builder.br_if(i as u32);
|
||||
|
@ -418,6 +444,13 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Refcounting(_modify, following) => {
|
||||
// TODO: actually deal with refcounting. For hello world, we just skipped it.
|
||||
self.build_stmt(following, ret_layout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
x => Err(format!("statement not yet implemented: {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
@ -444,6 +477,11 @@ impl<'a> WasmBackend<'a> {
|
|||
arguments,
|
||||
}) => match call_type {
|
||||
CallType::ByName { name: func_sym, .. } => {
|
||||
// If this function is just a lowlevel wrapper, then inline it
|
||||
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
|
||||
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
|
||||
}
|
||||
|
||||
let mut wasm_args_tmp: Vec<Symbol>;
|
||||
let (wasm_args, has_return_val) = match wasm_layout {
|
||||
WasmLayout::StackMemory { .. } => {
|
||||
|
@ -486,39 +524,80 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
let return_layout = WasmLayout::new(layout);
|
||||
self.storage.load_symbols(&mut self.code_builder, arguments);
|
||||
|
||||
let build_result = build_call_low_level(
|
||||
&mut self.code_builder,
|
||||
&mut self.storage,
|
||||
lowlevel,
|
||||
arguments,
|
||||
&return_layout,
|
||||
);
|
||||
use LowlevelBuildResult::*;
|
||||
|
||||
match build_result {
|
||||
Done => Ok(()),
|
||||
BuiltinCall(name) => {
|
||||
self.call_imported_builtin(name, arguments, &return_layout);
|
||||
Ok(())
|
||||
}
|
||||
NotImplemented => Err(format!(
|
||||
"Low level operation {:?} is not yet implemented",
|
||||
lowlevel
|
||||
)),
|
||||
}
|
||||
self.build_low_level(*lowlevel, arguments, *sym, wasm_layout)
|
||||
}
|
||||
|
||||
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
|
||||
},
|
||||
|
||||
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
|
||||
|
||||
Expr::StructAtIndex {
|
||||
index,
|
||||
field_layouts,
|
||||
structure,
|
||||
} => {
|
||||
if let StoredValue::StackMemory { location, .. } = self.storage.get(structure) {
|
||||
let (local_id, mut offset) =
|
||||
location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
for field in field_layouts.iter().take(*index as usize) {
|
||||
offset += field.stack_size(PTR_SIZE);
|
||||
}
|
||||
self.storage.copy_value_from_memory(
|
||||
&mut self.code_builder,
|
||||
*sym,
|
||||
local_id,
|
||||
offset,
|
||||
);
|
||||
} else {
|
||||
unreachable!("Unexpected storage for {:?}", structure)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
x => Err(format!("Expression is not yet implemented {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_low_level(
|
||||
&mut self,
|
||||
lowlevel: LowLevel,
|
||||
arguments: &'a [Symbol],
|
||||
return_sym: Symbol,
|
||||
return_layout: WasmLayout,
|
||||
) -> Result<(), String> {
|
||||
// Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
|
||||
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
|
||||
// This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI.
|
||||
self.storage.load_symbols_fastcc(
|
||||
&mut self.code_builder,
|
||||
arguments,
|
||||
return_sym,
|
||||
&return_layout,
|
||||
);
|
||||
|
||||
let build_result = decode_low_level(
|
||||
&mut self.code_builder,
|
||||
&mut self.storage,
|
||||
lowlevel,
|
||||
arguments,
|
||||
&return_layout,
|
||||
);
|
||||
use LowlevelBuildResult::*;
|
||||
|
||||
match build_result {
|
||||
Done => Ok(()),
|
||||
BuiltinCall(name) => {
|
||||
self.call_zig_builtin(name, arguments, &return_layout);
|
||||
Ok(())
|
||||
}
|
||||
NotImplemented => Err(format!(
|
||||
"Low level operation {:?} is not yet implemented",
|
||||
lowlevel
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_literal(
|
||||
&mut self,
|
||||
lit: &Literal<'a>,
|
||||
|
@ -578,8 +657,8 @@ impl<'a> WasmBackend<'a> {
|
|||
self.lookup_string_constant(string, sym, layout);
|
||||
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.insert_memory_relocation(linker_sym_index);
|
||||
self.code_builder.i32_const(elements_addr as i32);
|
||||
self.code_builder
|
||||
.i32_const_mem_addr(elements_addr, linker_sym_index);
|
||||
self.code_builder.i32_store(Align::Bytes4, offset);
|
||||
|
||||
self.code_builder.get_local(local_id);
|
||||
|
@ -701,12 +780,10 @@ impl<'a> WasmBackend<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn call_imported_builtin(
|
||||
&mut self,
|
||||
name: &'a str,
|
||||
arguments: &[Symbol],
|
||||
ret_layout: &WasmLayout,
|
||||
) {
|
||||
/// Generate a call instruction to a Zig builtin function.
|
||||
/// And if we haven't seen it before, add an Import and linker data for it.
|
||||
/// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
|
||||
fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
|
||||
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
|
||||
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
|
||||
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
|
||||
|
@ -716,11 +793,29 @@ impl<'a> WasmBackend<'a> {
|
|||
},
|
||||
|
||||
None => {
|
||||
let mut param_types = Vec::with_capacity_in(arguments.len(), self.env.arena);
|
||||
param_types.extend(arguments.iter().map(|a| self.storage.get(a).value_type()));
|
||||
let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
|
||||
|
||||
let ret_type = if ret_layout.is_stack_memory() {
|
||||
param_types.push(ValueType::I32);
|
||||
None
|
||||
} else {
|
||||
Some(ret_layout.value_type())
|
||||
};
|
||||
|
||||
// Zig's "fast calling convention" packs structs into CPU registers (stack machine slots) if possible.
|
||||
// If they're small enough they can go into an I32 or I64. If they're big, they're pointers (I32).
|
||||
for arg in arguments {
|
||||
param_types.push(match self.storage.get(arg) {
|
||||
StoredValue::StackMemory { size, .. } if *size > 4 && *size <= 8 => {
|
||||
ValueType::I64
|
||||
}
|
||||
stored => stored.value_type(),
|
||||
});
|
||||
}
|
||||
|
||||
let signature_index = self.module.types.insert(Signature {
|
||||
param_types,
|
||||
ret_type: Some(ret_layout.value_type()), // TODO: handle builtins with no return value
|
||||
ret_type,
|
||||
});
|
||||
|
||||
let import_index = self.module.import.entries.len() as u32;
|
||||
|
@ -731,14 +826,15 @@ impl<'a> WasmBackend<'a> {
|
|||
};
|
||||
self.module.import.entries.push(import);
|
||||
|
||||
let sym_idx = self.linker_symbols.len() as u32;
|
||||
let sym_idx = self.linker_symbols.len();
|
||||
let sym_info = SymInfo::Function(WasmObjectSymbol::Imported {
|
||||
flags: WASM_SYM_UNDEFINED,
|
||||
index: import_index,
|
||||
});
|
||||
self.linker_symbols.push(sym_info);
|
||||
self.builtin_sym_index_map.insert(name, sym_idx);
|
||||
|
||||
(import_index, sym_idx)
|
||||
(import_index, sym_idx as u32)
|
||||
}
|
||||
};
|
||||
self.code_builder.call(
|
||||
|
|
|
@ -7,6 +7,7 @@ pub mod wasm_module;
|
|||
use bumpalo::{self, collections::Vec, Bump};
|
||||
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
|
@ -36,7 +37,7 @@ pub fn build_module<'a>(
|
|||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
let mut wasm_module = build_module_help(env, procedures)?;
|
||||
let (mut wasm_module, _) = build_module_help(env, procedures)?;
|
||||
let mut buffer = std::vec::Vec::with_capacity(4096);
|
||||
wasm_module.serialize_mut(&mut buffer);
|
||||
Ok(buffer)
|
||||
|
@ -45,36 +46,54 @@ pub fn build_module<'a>(
|
|||
pub fn build_module_help<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<WasmModule<'a>, String> {
|
||||
) -> Result<(WasmModule<'a>, u32), String> {
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
let mut proc_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||
let mut exports = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut exports = Vec::with_capacity_in(4, env.arena);
|
||||
let mut main_fn_index = None;
|
||||
|
||||
// Collect the symbols & names for the procedures
|
||||
for (i, (sym, layout)) in procedures.keys().enumerate() {
|
||||
proc_symbols.push(*sym);
|
||||
// Collect the symbols & names for the procedures,
|
||||
// and filter out procs we're going to inline
|
||||
let mut fn_index: u32 = 0;
|
||||
for ((sym, layout), proc) in procedures.into_iter() {
|
||||
if LowLevel::from_inlined_wrapper(sym).is_some() {
|
||||
continue;
|
||||
}
|
||||
generated_procs.push(proc);
|
||||
generated_symbols.push(sym);
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(*sym, layout)
|
||||
.to_symbol_string(*sym, &env.interns);
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
|
||||
if env.exposed_to_host.contains(sym) {
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
main_fn_index = Some(fn_index);
|
||||
exports.push(Export {
|
||||
name: fn_name.clone(),
|
||||
ty: ExportType::Func,
|
||||
index: i as u32,
|
||||
index: fn_index,
|
||||
});
|
||||
}
|
||||
|
||||
let linker_sym = SymInfo::for_function(i as u32, fn_name);
|
||||
let linker_sym = SymInfo::for_function(fn_index, fn_name);
|
||||
linker_symbols.push(linker_sym);
|
||||
|
||||
fn_index += 1;
|
||||
}
|
||||
|
||||
// Main loop: Build the Wasm module
|
||||
// Build the Wasm module
|
||||
let (mut module, linker_symbols) = {
|
||||
let mut backend = WasmBackend::new(env, layout_ids, proc_symbols, linker_symbols, exports);
|
||||
for ((sym, _), proc) in procedures.into_iter() {
|
||||
let mut backend = WasmBackend::new(
|
||||
env,
|
||||
layout_ids,
|
||||
generated_symbols.clone(),
|
||||
linker_symbols,
|
||||
exports,
|
||||
);
|
||||
|
||||
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
|
||||
backend.build_proc(proc, sym)?;
|
||||
}
|
||||
(backend.module, backend.linker_symbols)
|
||||
|
@ -83,7 +102,7 @@ pub fn build_module_help<'a>(
|
|||
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
|
||||
module.linking.subsections.push(symbol_table);
|
||||
|
||||
Ok(module)
|
||||
Ok((module, main_fn_index.unwrap()))
|
||||
}
|
||||
|
||||
pub struct CopyMemoryConfig {
|
||||
|
|
|
@ -15,10 +15,10 @@ pub enum LowlevelBuildResult {
|
|||
NotImplemented,
|
||||
}
|
||||
|
||||
pub fn build_call_low_level<'a>(
|
||||
pub fn decode_low_level<'a>(
|
||||
code_builder: &mut CodeBuilder<'a>,
|
||||
storage: &mut Storage<'a>,
|
||||
lowlevel: &LowLevel,
|
||||
lowlevel: LowLevel,
|
||||
args: &'a [Symbol],
|
||||
ret_layout: &WasmLayout,
|
||||
) -> LowlevelBuildResult {
|
||||
|
@ -27,16 +27,39 @@ pub fn build_call_low_level<'a>(
|
|||
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
|
||||
|
||||
match lowlevel {
|
||||
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
|
||||
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
|
||||
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
|
||||
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
|
||||
StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
|
||||
StrJoinWith => return NotImplemented, // needs Array
|
||||
StrIsEmpty => {
|
||||
code_builder.i64_const(i64::MIN);
|
||||
code_builder.i64_eq();
|
||||
}
|
||||
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
|
||||
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
|
||||
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
|
||||
StrSplit => return NotImplemented, // needs Array
|
||||
StrCountGraphemes => return NotImplemented, // test needs Array
|
||||
StrFromInt => return NotImplemented, // choose builtin based on storage size
|
||||
StrFromUtf8 => return NotImplemented, // needs Array
|
||||
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
|
||||
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
|
||||
StrFromUtf8Range => return NotImplemented, // needs Array
|
||||
StrToUtf8 => return NotImplemented, // needs Array
|
||||
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
|
||||
StrFromFloat => {
|
||||
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
|
||||
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
|
||||
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
|
||||
return NotImplemented;
|
||||
}
|
||||
StrTrim => return BuiltinCall(bitcode::STR_TRIM),
|
||||
|
||||
ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
|
||||
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
|
||||
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
|
||||
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListTakeFirst
|
||||
| ListTakeLast | ListDrop | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize
|
||||
| DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys
|
||||
| DictValues | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
|
||||
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
|
||||
| ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty
|
||||
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
|
||||
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
|
@ -129,6 +152,9 @@ pub fn build_call_low_level<'a>(
|
|||
NumIsMultipleOf => return NotImplemented,
|
||||
NumAbs => match ret_layout.value_type() {
|
||||
I32 => {
|
||||
let arg_storage = storage.get(&args[0]).to_owned();
|
||||
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i32_const(0);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i32_sub();
|
||||
|
@ -138,6 +164,9 @@ pub fn build_call_low_level<'a>(
|
|||
code_builder.select();
|
||||
}
|
||||
I64 => {
|
||||
let arg_storage = storage.get(&args[0]).to_owned();
|
||||
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i64_const(0);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i64_sub();
|
||||
|
|
|
@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
|
|||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState};
|
||||
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
|
||||
|
||||
pub enum StoredValueKind {
|
||||
|
@ -33,7 +33,7 @@ impl StackMemoryLocation {
|
|||
pub enum StoredValue {
|
||||
/// A value stored implicitly in the VM stack (primitives only)
|
||||
VirtualMachineStack {
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
vm_state: VmSymbolState,
|
||||
value_type: ValueType,
|
||||
size: u32,
|
||||
},
|
||||
|
@ -126,7 +126,7 @@ impl<'a> Storage<'a> {
|
|||
}
|
||||
}
|
||||
_ => StoredValue::VirtualMachineStack {
|
||||
vm_state: VirtualMachineSymbolState::NotYetPushed,
|
||||
vm_state: VmSymbolState::NotYetPushed,
|
||||
value_type: *value_type,
|
||||
size: *size,
|
||||
},
|
||||
|
@ -194,6 +194,63 @@ impl<'a> Storage<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Load a single symbol using the C Calling Convention
|
||||
/// *Private* because external code should always load symbols in bulk (see load_symbols)
|
||||
fn load_symbol_ccc(&mut self, code_builder: &mut CodeBuilder, sym: Symbol) {
|
||||
let storage = self.get(&sym).to_owned();
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
} => {
|
||||
let next_local_id = self.get_next_local_id();
|
||||
let maybe_next_vm_state = code_builder.load_symbol(sym, vm_state, next_local_id);
|
||||
match maybe_next_vm_state {
|
||||
// The act of loading the value changed the VM state, so update it
|
||||
Some(next_vm_state) => {
|
||||
self.symbol_storage_map.insert(
|
||||
sym,
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state: next_vm_state,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Loading the value required creating a new local, because
|
||||
// it was not in a convenient position in the VM stack.
|
||||
self.local_types.push(value_type);
|
||||
self.symbol_storage_map.insert(
|
||||
sym,
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StoredValue::Local { local_id, .. } => {
|
||||
code_builder.get_local(local_id);
|
||||
code_builder.set_top_symbol(sym);
|
||||
}
|
||||
|
||||
StoredValue::StackMemory { location, .. } => {
|
||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
code_builder.get_local(local_id);
|
||||
if offset != 0 {
|
||||
code_builder.i32_const(offset as i32);
|
||||
code_builder.i32_add();
|
||||
}
|
||||
code_builder.set_top_symbol(sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load symbols to the top of the VM stack
|
||||
/// Avoid calling this method in a loop with one symbol at a time! It will work,
|
||||
/// but it generates very inefficient Wasm code.
|
||||
|
@ -204,61 +261,60 @@ impl<'a> Storage<'a> {
|
|||
return;
|
||||
}
|
||||
for sym in symbols.iter() {
|
||||
let storage = self.get(sym).to_owned();
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
} => {
|
||||
let next_local_id = self.get_next_local_id();
|
||||
let maybe_next_vm_state =
|
||||
code_builder.load_symbol(*sym, vm_state, next_local_id);
|
||||
match maybe_next_vm_state {
|
||||
// The act of loading the value changed the VM state, so update it
|
||||
Some(next_vm_state) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state: next_vm_state,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Loading the value required creating a new local, because
|
||||
// it was not in a convenient position in the VM stack.
|
||||
self.local_types.push(value_type);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
StoredValue::Local { local_id, .. }
|
||||
| StoredValue::StackMemory {
|
||||
location: StackMemoryLocation::PointerArg(local_id),
|
||||
..
|
||||
} => {
|
||||
code_builder.get_local(local_id);
|
||||
code_builder.set_top_symbol(*sym);
|
||||
self.load_symbol_ccc(code_builder, *sym);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load symbols in a way compatible with LLVM's "fast calling convention"
|
||||
/// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
|
||||
/// It squashes small structs into primitive values where possible, avoiding stack memory
|
||||
/// in favour of CPU registers (or VM stack values, which eventually become CPU registers).
|
||||
/// We need to convert some of our structs from our internal C-like representation to work with Zig.
|
||||
/// We are sticking to C ABI for better compatibility on the platform side.
|
||||
pub fn load_symbols_fastcc(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
symbols: &[Symbol],
|
||||
return_symbol: Symbol,
|
||||
return_layout: &WasmLayout,
|
||||
) {
|
||||
// Note: we are not doing verify_stack_match in this case so we may generate more code.
|
||||
// We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
|
||||
|
||||
if return_layout.is_stack_memory() {
|
||||
// Load the address where the return value should be written
|
||||
// Apparently for return values we still use a pointer to stack memory
|
||||
self.load_symbol_ccc(code_builder, return_symbol);
|
||||
};
|
||||
|
||||
for sym in symbols {
|
||||
if let StoredValue::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
} = self.get(sym)
|
||||
{
|
||||
if *size == 0 {
|
||||
unimplemented!("Passing zero-sized values is not implemented yet");
|
||||
} else if *size > 8 {
|
||||
return self.load_symbol_ccc(code_builder, *sym);
|
||||
}
|
||||
|
||||
StoredValue::StackMemory {
|
||||
location: StackMemoryLocation::FrameOffset(offset),
|
||||
..
|
||||
} => {
|
||||
code_builder.get_local(self.stack_frame_pointer.unwrap());
|
||||
code_builder.i32_const(offset as i32);
|
||||
code_builder.i32_add();
|
||||
code_builder.set_top_symbol(*sym);
|
||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
code_builder.get_local(local_id);
|
||||
let align = Align::from(*alignment_bytes);
|
||||
|
||||
if *size == 1 {
|
||||
code_builder.i32_load8_u(align, offset);
|
||||
} else if *size == 2 {
|
||||
code_builder.i32_load16_u(align, offset);
|
||||
} else if *size <= 4 {
|
||||
code_builder.i32_load(align, offset);
|
||||
} else {
|
||||
code_builder.i64_load(align, offset);
|
||||
}
|
||||
} else {
|
||||
self.load_symbol_ccc(code_builder, *sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,6 +375,67 @@ impl<'a> Storage<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generate code to copy a StoredValue from an arbitrary memory location
|
||||
/// (defined by a pointer and offset).
|
||||
pub fn copy_value_from_memory(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
to_symbol: Symbol,
|
||||
from_ptr: LocalId,
|
||||
from_offset: u32,
|
||||
) -> u32 {
|
||||
let to_storage = self.get(&to_symbol).to_owned();
|
||||
match to_storage {
|
||||
StoredValue::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
copy_memory(
|
||||
code_builder,
|
||||
CopyMemoryConfig {
|
||||
from_ptr,
|
||||
from_offset,
|
||||
to_ptr,
|
||||
to_offset,
|
||||
size,
|
||||
alignment_bytes,
|
||||
},
|
||||
);
|
||||
size
|
||||
}
|
||||
|
||||
StoredValue::VirtualMachineStack {
|
||||
value_type, size, ..
|
||||
}
|
||||
| StoredValue::Local {
|
||||
value_type, size, ..
|
||||
} => {
|
||||
use crate::wasm_module::Align::*;
|
||||
|
||||
code_builder.get_local(from_ptr);
|
||||
match (value_type, size) {
|
||||
(ValueType::I64, 8) => code_builder.i64_load(Bytes8, from_offset),
|
||||
(ValueType::I32, 4) => code_builder.i32_load(Bytes4, from_offset),
|
||||
(ValueType::I32, 2) => code_builder.i32_load16_s(Bytes2, from_offset),
|
||||
(ValueType::I32, 1) => code_builder.i32_load8_s(Bytes1, from_offset),
|
||||
(ValueType::F32, 4) => code_builder.f32_load(Bytes4, from_offset),
|
||||
(ValueType::F64, 8) => code_builder.f64_load(Bytes8, from_offset),
|
||||
_ => {
|
||||
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
|
||||
}
|
||||
};
|
||||
|
||||
if let StoredValue::Local { local_id, .. } = to_storage {
|
||||
code_builder.set_local(local_id);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code to copy from one StoredValue to another
|
||||
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
|
||||
pub fn clone_value(
|
||||
|
@ -422,7 +539,7 @@ impl<'a> Storage<'a> {
|
|||
} = storage
|
||||
{
|
||||
let local_id = self.get_next_local_id();
|
||||
if vm_state != VirtualMachineSymbolState::NotYetPushed {
|
||||
if vm_state != VmSymbolState::NotYetPushed {
|
||||
code_builder.load_symbol(symbol, vm_state, local_id);
|
||||
code_builder.set_local(local_id);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use core::panic;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
|
@ -10,6 +9,13 @@ use super::opcodes::{OpCode, OpCode::*};
|
|||
use super::serialize::{SerialBuffer, Serialize};
|
||||
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
|
||||
|
||||
const ENABLE_DEBUG_LOG: bool = false;
|
||||
macro_rules! log_instruction {
|
||||
($($x: expr),+) => {
|
||||
if ENABLE_DEBUG_LOG { println!($($x,)*); }
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct LocalId(pub u32);
|
||||
|
||||
|
@ -29,6 +35,7 @@ impl Serialize for ValueType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum BlockType {
|
||||
NoResult,
|
||||
Value(ValueType),
|
||||
|
@ -43,6 +50,31 @@ impl BlockType {
|
|||
}
|
||||
}
|
||||
|
||||
/// A control block in our model of the VM
|
||||
/// Child blocks cannot "see" values from their parent block
|
||||
struct VmBlock<'a> {
|
||||
/// opcode indicating what kind of block this is
|
||||
opcode: OpCode,
|
||||
/// the stack of values for this block
|
||||
value_stack: Vec<'a, Symbol>,
|
||||
/// whether this block pushes a result value to its parent
|
||||
has_result: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VmBlock<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"{:?} {}",
|
||||
self.opcode,
|
||||
if self.has_result {
|
||||
"Result"
|
||||
} else {
|
||||
"NoResult"
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -73,7 +105,7 @@ impl From<u32> for Align {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum VirtualMachineSymbolState {
|
||||
pub enum VmSymbolState {
|
||||
/// Value doesn't exist yet
|
||||
NotYetPushed,
|
||||
|
||||
|
@ -113,6 +145,8 @@ macro_rules! instruction_memargs {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct CodeBuilder<'a> {
|
||||
arena: &'a Bump,
|
||||
|
||||
/// The main container for the instructions
|
||||
code: Vec<'a, u8>,
|
||||
|
||||
|
@ -135,8 +169,8 @@ pub struct CodeBuilder<'a> {
|
|||
inner_length: Vec<'a, u8>,
|
||||
|
||||
/// Our simulation model of the Wasm stack machine
|
||||
/// Keeps track of where Symbol values are in the VM stack
|
||||
vm_stack: Vec<'a, Symbol>,
|
||||
/// Nested blocks of instructions. A child block can't "see" the stack of its parent block
|
||||
vm_block_stack: Vec<'a, VmBlock<'a>>,
|
||||
|
||||
/// Linker info to help combine the Roc module with builtin & platform modules,
|
||||
/// e.g. to modify call instructions when function indices change
|
||||
|
@ -146,13 +180,22 @@ pub struct CodeBuilder<'a> {
|
|||
#[allow(clippy::new_without_default)]
|
||||
impl<'a> CodeBuilder<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
let mut vm_block_stack = Vec::with_capacity_in(8, arena);
|
||||
let function_block = VmBlock {
|
||||
opcode: BLOCK,
|
||||
has_result: true,
|
||||
value_stack: Vec::with_capacity_in(8, arena),
|
||||
};
|
||||
vm_block_stack.push(function_block);
|
||||
|
||||
CodeBuilder {
|
||||
arena,
|
||||
code: Vec::with_capacity_in(1024, arena),
|
||||
insertions: Vec::with_capacity_in(32, arena),
|
||||
insert_bytes: Vec::with_capacity_in(64, arena),
|
||||
preamble: Vec::with_capacity_in(32, arena),
|
||||
inner_length: Vec::with_capacity_in(5, arena),
|
||||
vm_stack: Vec::with_capacity_in(32, arena),
|
||||
vm_block_stack,
|
||||
relocations: Vec::with_capacity_in(32, arena),
|
||||
}
|
||||
}
|
||||
|
@ -167,45 +210,49 @@ impl<'a> CodeBuilder<'a> {
|
|||
|
||||
***********************************************************/
|
||||
|
||||
fn current_stack(&self) -> &Vec<'a, Symbol> {
|
||||
let block = self.vm_block_stack.last().unwrap();
|
||||
&block.value_stack
|
||||
}
|
||||
|
||||
fn current_stack_mut(&mut self) -> &mut Vec<'a, Symbol> {
|
||||
let block = self.vm_block_stack.last_mut().unwrap();
|
||||
&mut block.value_stack
|
||||
}
|
||||
|
||||
/// Set the Symbol that is at the top of the VM stack right now
|
||||
/// We will use this later when we need to load the Symbol
|
||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
|
||||
let len = self.vm_stack.len();
|
||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
|
||||
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
|
||||
let pushed_at = self.code.len();
|
||||
let top_symbol: &mut Symbol = current_stack.last_mut().unwrap();
|
||||
*top_symbol = sym;
|
||||
|
||||
if len == 0 {
|
||||
panic!(
|
||||
"trying to set symbol with nothing on stack, code = {:?}",
|
||||
self.code
|
||||
);
|
||||
}
|
||||
|
||||
self.vm_stack[len - 1] = sym;
|
||||
|
||||
VirtualMachineSymbolState::Pushed { pushed_at }
|
||||
VmSymbolState::Pushed { pushed_at }
|
||||
}
|
||||
|
||||
/// Verify if a sequence of symbols is at the top of the stack
|
||||
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
|
||||
let current_stack = self.current_stack();
|
||||
let n_symbols = symbols.len();
|
||||
let stack_depth = self.vm_stack.len();
|
||||
let stack_depth = current_stack.len();
|
||||
if n_symbols > stack_depth {
|
||||
return false;
|
||||
}
|
||||
let offset = stack_depth - n_symbols;
|
||||
|
||||
for (i, sym) in symbols.iter().enumerate() {
|
||||
if self.vm_stack[offset + i] != *sym {
|
||||
if current_stack[offset + i] != *sym {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) {
|
||||
fn add_insertion(&mut self, insert_at: usize, opcode: OpCode, immediate: u32) {
|
||||
let start = self.insert_bytes.len();
|
||||
|
||||
self.insert_bytes.push(opcode);
|
||||
self.insert_bytes.push(opcode as u8);
|
||||
self.insert_bytes.encode_u32(immediate);
|
||||
|
||||
self.insertions.push(Insertion {
|
||||
|
@ -213,6 +260,13 @@ impl<'a> CodeBuilder<'a> {
|
|||
start,
|
||||
end: self.insert_bytes.len(),
|
||||
});
|
||||
|
||||
log_instruction!(
|
||||
"**insert {:?} {} at byte offset {}**",
|
||||
opcode,
|
||||
immediate,
|
||||
insert_at
|
||||
);
|
||||
}
|
||||
|
||||
/// Load a Symbol that is stored in the VM stack
|
||||
|
@ -225,41 +279,56 @@ impl<'a> CodeBuilder<'a> {
|
|||
pub fn load_symbol(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
vm_state: VmSymbolState,
|
||||
next_local_id: LocalId,
|
||||
) -> Option<VirtualMachineSymbolState> {
|
||||
use VirtualMachineSymbolState::*;
|
||||
) -> Option<VmSymbolState> {
|
||||
use VmSymbolState::*;
|
||||
|
||||
match vm_state {
|
||||
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
|
||||
NotYetPushed => unreachable!("Symbol {:?} has no value yet. Nothing to load.", symbol),
|
||||
|
||||
Pushed { pushed_at } => {
|
||||
let &top = self.vm_stack.last().unwrap();
|
||||
if top == symbol {
|
||||
// We're lucky, the symbol is already on top of the VM stack
|
||||
// No code to generate! (This reduces code size by up to 25% in tests.)
|
||||
// Just let the caller know what happened
|
||||
Some(Popped { pushed_at })
|
||||
} else {
|
||||
// Symbol is not on top of the stack. Find it.
|
||||
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
|
||||
// Insert a local.set where the value was created
|
||||
self.add_insertion(pushed_at, SETLOCAL as u8, next_local_id.0);
|
||||
match self.current_stack().last() {
|
||||
Some(top_symbol) if *top_symbol == symbol => {
|
||||
// We're lucky, the symbol is already on top of the current block's stack.
|
||||
// No code to generate! (This reduces code size by up to 25% in tests.)
|
||||
// Just let the caller know what happened
|
||||
Some(Popped { pushed_at })
|
||||
}
|
||||
_ => {
|
||||
// Symbol is not on top of the stack.
|
||||
// We should have saved it to a local, so go back and do that now.
|
||||
|
||||
// Take the value out of the stack where local.set was inserted
|
||||
self.vm_stack.remove(found_index);
|
||||
// It should still be on the stack in the block where it was assigned. Remove it.
|
||||
let mut found = false;
|
||||
for block in self.vm_block_stack.iter_mut() {
|
||||
if let Some(found_index) =
|
||||
block.value_stack.iter().position(|&s| s == symbol)
|
||||
{
|
||||
block.value_stack.remove(found_index);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a local.get at the current position
|
||||
// Go back to the code position where it was pushed, and save it to a local
|
||||
if found {
|
||||
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
|
||||
} else {
|
||||
if ENABLE_DEBUG_LOG {
|
||||
println!(
|
||||
"{:?} has been popped implicitly. Leaving it on the stack.",
|
||||
symbol
|
||||
);
|
||||
}
|
||||
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
|
||||
}
|
||||
|
||||
// Recover the value again at the current position
|
||||
self.get_local(next_local_id);
|
||||
self.vm_stack.push(symbol);
|
||||
self.set_top_symbol(symbol);
|
||||
|
||||
// This Symbol is no longer stored in the VM stack, but in a local
|
||||
None
|
||||
} else {
|
||||
panic!(
|
||||
"{:?} has state {:?} but not found in VM stack",
|
||||
symbol, vm_state
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,11 +336,11 @@ impl<'a> CodeBuilder<'a> {
|
|||
Popped { pushed_at } => {
|
||||
// This Symbol is being used for a second time
|
||||
// Insert a local.tee where it was pushed, so we don't interfere with the first usage
|
||||
self.add_insertion(pushed_at, TEELOCAL as u8, next_local_id.0);
|
||||
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
|
||||
|
||||
// Insert a local.get at the current position
|
||||
self.get_local(next_local_id);
|
||||
self.vm_stack.push(symbol);
|
||||
self.set_top_symbol(symbol);
|
||||
|
||||
// This symbol has been promoted to a Local
|
||||
// Tell the caller it no longer has a VirtualMachineSymbolState
|
||||
|
@ -282,7 +351,7 @@ impl<'a> CodeBuilder<'a> {
|
|||
|
||||
/**********************************************************
|
||||
|
||||
FINALIZE AND SERIALIZE
|
||||
FUNCTION HEADER
|
||||
|
||||
***********************************************************/
|
||||
|
||||
|
@ -375,6 +444,12 @@ impl<'a> CodeBuilder<'a> {
|
|||
self.insertions.sort_by_key(|ins| ins.at);
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
SERIALIZE
|
||||
|
||||
***********************************************************/
|
||||
|
||||
/// Serialize all byte vectors in the right order
|
||||
/// Also update relocation offsets relative to the base offset (code section body start)
|
||||
pub fn serialize_with_relocs<T: SerialBuffer>(
|
||||
|
@ -433,38 +508,78 @@ impl<'a> CodeBuilder<'a> {
|
|||
|
||||
/// Base method for generating instructions
|
||||
/// Emits the opcode and simulates VM stack push/pop
|
||||
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
|
||||
let new_len = self.vm_stack.len() - pops as usize;
|
||||
self.vm_stack.truncate(new_len);
|
||||
fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
|
||||
let current_stack = self.current_stack_mut();
|
||||
let new_len = current_stack.len() - pops as usize;
|
||||
current_stack.truncate(new_len);
|
||||
if push {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
current_stack.push(Symbol::WASM_TMP);
|
||||
}
|
||||
|
||||
self.code.push(opcode as u8);
|
||||
}
|
||||
|
||||
fn inst_imm8(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u8) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.code.push(immediate);
|
||||
/// Plain instruction without any immediates
|
||||
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
|
||||
self.inst_base(opcode, pops, push);
|
||||
log_instruction!(
|
||||
"{:10}\t\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
// public for use in test code
|
||||
pub fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
|
||||
self.inst(opcode, pops, push);
|
||||
/// Block instruction
|
||||
fn inst_block(&mut self, opcode: OpCode, pops: usize, block_type: BlockType) {
|
||||
self.inst_base(opcode, pops, false);
|
||||
self.code.push(block_type.as_byte());
|
||||
|
||||
// Start a new block with a fresh value stack
|
||||
self.vm_block_stack.push(VmBlock {
|
||||
opcode,
|
||||
value_stack: Vec::with_capacity_in(8, self.arena),
|
||||
has_result: block_type != BlockType::NoResult,
|
||||
});
|
||||
|
||||
log_instruction!(
|
||||
"{:10} {:?}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
block_type,
|
||||
&self.vm_block_stack
|
||||
);
|
||||
}
|
||||
|
||||
fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
|
||||
self.inst_base(opcode, pops, push);
|
||||
self.code.encode_u32(immediate);
|
||||
log_instruction!(
|
||||
"{:10}\t{}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
immediate,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
fn inst_mem(&mut self, opcode: OpCode, pops: usize, push: bool, align: Align, offset: u32) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.inst_base(opcode, pops, push);
|
||||
self.code.push(align as u8);
|
||||
self.code.encode_u32(offset);
|
||||
log_instruction!(
|
||||
"{:10} {:?} {}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
align,
|
||||
offset,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
/// Insert a linker relocation for a memory address
|
||||
pub fn insert_memory_relocation(&mut self, symbol_index: u32) {
|
||||
/// Insert a const reference to a memory address
|
||||
pub fn i32_const_mem_addr(&mut self, addr: u32, symbol_index: u32) {
|
||||
self.inst_base(I32CONST, 0, true);
|
||||
let offset = self.code.len() as u32;
|
||||
self.code.encode_padded_u32(addr);
|
||||
self.relocations.push(RelocationEntry::Offset {
|
||||
type_id: OffsetRelocType::MemoryAddrLeb,
|
||||
offset: self.code.len() as u32,
|
||||
offset,
|
||||
symbol_index,
|
||||
addend: 0,
|
||||
});
|
||||
|
@ -484,22 +599,38 @@ impl<'a> CodeBuilder<'a> {
|
|||
instruction_no_args!(nop, NOP, 0, false);
|
||||
|
||||
pub fn block(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(BLOCK, 0, false, ty.as_byte());
|
||||
self.inst_block(BLOCK, 0, ty);
|
||||
}
|
||||
pub fn loop_(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(LOOP, 0, false, ty.as_byte());
|
||||
self.inst_block(LOOP, 0, ty);
|
||||
}
|
||||
pub fn if_(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(IF, 1, false, ty.as_byte());
|
||||
self.inst_block(IF, 1, ty);
|
||||
}
|
||||
pub fn else_(&mut self) {
|
||||
// Reuse the 'then' block but clear its value stack
|
||||
self.current_stack_mut().clear();
|
||||
self.inst(ELSE, 0, false);
|
||||
}
|
||||
|
||||
instruction_no_args!(else_, ELSE, 0, false);
|
||||
instruction_no_args!(end, END, 0, false);
|
||||
pub fn end(&mut self) {
|
||||
self.inst_base(END, 0, false);
|
||||
|
||||
let ended_block = self.vm_block_stack.pop().unwrap();
|
||||
if ended_block.has_result {
|
||||
let result = ended_block.value_stack.last().unwrap();
|
||||
self.current_stack_mut().push(*result)
|
||||
}
|
||||
|
||||
log_instruction!("END \t\t{:?}", &self.vm_block_stack);
|
||||
}
|
||||
pub fn br(&mut self, levels: u32) {
|
||||
self.inst_imm32(BR, 0, false, levels);
|
||||
}
|
||||
pub fn br_if(&mut self, levels: u32) {
|
||||
// In dynamic execution, br_if can pop 2 values if condition is true and the target block has a result.
|
||||
// But our stack model is for *static* analysis and we need it to be correct at the next instruction,
|
||||
// where the branch was not taken. So we only pop 1 value, the condition.
|
||||
self.inst_imm32(BRIF, 1, false, levels);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
|
@ -516,30 +647,26 @@ impl<'a> CodeBuilder<'a> {
|
|||
n_args: usize,
|
||||
has_return_val: bool,
|
||||
) {
|
||||
let stack_depth = self.vm_stack.len();
|
||||
if n_args > stack_depth {
|
||||
panic!(
|
||||
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}",
|
||||
function_index, n_args, stack_depth, self
|
||||
);
|
||||
}
|
||||
self.vm_stack.truncate(stack_depth - n_args);
|
||||
if has_return_val {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
}
|
||||
self.code.push(CALL as u8);
|
||||
self.inst_base(CALL, n_args, has_return_val);
|
||||
|
||||
// Write the index of the function to be called.
|
||||
// Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
|
||||
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
|
||||
// other factors to make it relative to the code section. (All insertions will be known then.)
|
||||
let offset = self.code.len() as u32;
|
||||
self.code.encode_padded_u32(function_index);
|
||||
|
||||
// Make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
|
||||
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
|
||||
// other factors to make it relative to the code section. (All insertions will be known then.)
|
||||
self.relocations.push(RelocationEntry::Index {
|
||||
type_id: IndexRelocType::FunctionIndexLeb,
|
||||
offset,
|
||||
symbol_index,
|
||||
});
|
||||
|
||||
log_instruction!(
|
||||
"{:10}\t{}\t{:?}",
|
||||
format!("{:?}", CALL),
|
||||
function_index,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -591,26 +718,44 @@ impl<'a> CodeBuilder<'a> {
|
|||
instruction_memargs!(i64_store32, I64STORE32, 2, false);
|
||||
|
||||
pub fn memory_size(&mut self) {
|
||||
self.inst_imm8(CURRENTMEMORY, 0, true, 0);
|
||||
self.inst(CURRENTMEMORY, 0, true);
|
||||
self.code.push(0);
|
||||
}
|
||||
pub fn memory_grow(&mut self) {
|
||||
self.inst_imm8(GROWMEMORY, 1, true, 0);
|
||||
self.inst(GROWMEMORY, 1, true);
|
||||
self.code.push(0);
|
||||
}
|
||||
|
||||
fn log_const<T>(&self, opcode: OpCode, x: T)
|
||||
where
|
||||
T: std::fmt::Debug + std::fmt::Display,
|
||||
{
|
||||
log_instruction!(
|
||||
"{:10}\t{}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
x,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
pub fn i32_const(&mut self, x: i32) {
|
||||
self.inst(I32CONST, 0, true);
|
||||
self.inst_base(I32CONST, 0, true);
|
||||
self.code.encode_i32(x);
|
||||
self.log_const(I32CONST, x);
|
||||
}
|
||||
pub fn i64_const(&mut self, x: i64) {
|
||||
self.inst(I64CONST, 0, true);
|
||||
self.inst_base(I64CONST, 0, true);
|
||||
self.code.encode_i64(x);
|
||||
self.log_const(I64CONST, x);
|
||||
}
|
||||
pub fn f32_const(&mut self, x: f32) {
|
||||
self.inst(F32CONST, 0, true);
|
||||
self.inst_base(F32CONST, 0, true);
|
||||
self.code.encode_f32(x);
|
||||
self.log_const(F32CONST, x);
|
||||
}
|
||||
pub fn f64_const(&mut self, x: f64) {
|
||||
self.inst(F64CONST, 0, true);
|
||||
self.inst_base(F64CONST, 0, true);
|
||||
self.code.encode_f64(x);
|
||||
self.log_const(F64CONST, x);
|
||||
}
|
||||
|
||||
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',
|
||||
|
|
|
@ -4,8 +4,6 @@ pub mod opcodes;
|
|||
pub mod sections;
|
||||
pub mod serialize;
|
||||
|
||||
pub use code_builder::{
|
||||
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
|
||||
};
|
||||
pub use code_builder::{Align, BlockType, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||
pub use linking::{LinkingSubSection, SymInfo};
|
||||
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#[repr(u8)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OpCode {
|
||||
UNREACHABLE = 0x00,
|
||||
NOP = 0x01,
|
||||
|
|
|
@ -29,6 +29,8 @@ pub enum SectionId {
|
|||
Element = 9,
|
||||
Code = 10,
|
||||
Data = 11,
|
||||
/// DataCount section is unused. Only needed for single-pass validation of
|
||||
/// memory.init and data.drop, which we don't use
|
||||
DataCount = 12,
|
||||
}
|
||||
|
||||
|
@ -239,8 +241,7 @@ impl<'a> Serialize for ImportSection<'a> {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionSection<'a> {
|
||||
/// Private. See WasmModule::add_function_signature
|
||||
signature_indices: Vec<'a, u32>,
|
||||
pub signature_indices: Vec<'a, u32>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionSection<'a> {
|
||||
|
@ -525,42 +526,6 @@ impl Serialize for DataSection<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Data Count section
|
||||
*
|
||||
* Pre-declares the number of segments in the Data section.
|
||||
* This helps the runtime to validate the module in a single pass.
|
||||
* The order of sections is DataCount -> Code -> Data
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DataCountSection {
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl DataCountSection {
|
||||
fn new(data_section: &DataSection<'_>) -> Self {
|
||||
let count = data_section
|
||||
.segments
|
||||
.iter()
|
||||
.filter(|seg| !seg.init.is_empty())
|
||||
.count() as u32;
|
||||
DataCountSection { count }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for DataCountSection {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
if self.count > 0 {
|
||||
let header_indices = write_section_header(buffer, SectionId::DataCount);
|
||||
buffer.encode_u32(self.count);
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Module
|
||||
|
@ -658,11 +623,6 @@ impl<'a> WasmModule<'a> {
|
|||
counter.serialize_and_count(buffer, &self.start);
|
||||
counter.serialize_and_count(buffer, &self.element);
|
||||
|
||||
// Data Count section forward-declares the size of the Data section
|
||||
// so that Code section can be validated in one pass
|
||||
let data_count_section = DataCountSection::new(&self.data);
|
||||
counter.serialize_and_count(buffer, &data_count_section);
|
||||
|
||||
// Code section is the only one with relocations so we can stop counting
|
||||
let code_section_index = counter.section_index;
|
||||
let code_section_body_index = self
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue