mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Move code_builder from wasm_module to gen_wasm
This commit is contained in:
parent
c8f949d546
commit
4dea82b2f5
8 changed files with 169 additions and 192 deletions
|
@ -1,7 +1,6 @@
|
||||||
use bitvec::vec::BitVec;
|
use bitvec::vec::BitVec;
|
||||||
use bumpalo::collections::{String, Vec};
|
use bumpalo::collections::{String, Vec};
|
||||||
|
|
||||||
use code_builder::Align;
|
|
||||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_error_macros::internal_error;
|
use roc_error_macros::internal_error;
|
||||||
|
@ -15,21 +14,22 @@ use roc_mono::ir::{
|
||||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
|
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
|
||||||
use roc_std::RocDec;
|
use roc_std::RocDec;
|
||||||
|
|
||||||
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
|
||||||
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
|
||||||
use crate::storage::{AddressValue, Storage, StoredValue, StoredVarKind};
|
|
||||||
use crate::{
|
|
||||||
copy_memory, CopyMemoryConfig, Env, DEBUG_SETTINGS, MEMORY_NAME, PTR_SIZE, PTR_TYPE,
|
|
||||||
TARGET_INFO,
|
|
||||||
};
|
|
||||||
use roc_wasm_module::linking::{DataSymbol, WasmObjectSymbol};
|
use roc_wasm_module::linking::{DataSymbol, WasmObjectSymbol};
|
||||||
use roc_wasm_module::sections::{
|
use roc_wasm_module::sections::{
|
||||||
ConstExpr, DataMode, DataSegment, Export, Global, GlobalType, Import, ImportDesc, Limits,
|
ConstExpr, DataMode, DataSegment, Export, Global, GlobalType, Import, ImportDesc, Limits,
|
||||||
MemorySection, NameSection,
|
MemorySection, NameSection,
|
||||||
};
|
};
|
||||||
use roc_wasm_module::{
|
use roc_wasm_module::{
|
||||||
code_builder, round_up_to_alignment, CodeBuilder, ExportType, LocalId, Signature, SymInfo,
|
round_up_to_alignment, Align, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule,
|
||||||
ValueType, WasmModule,
|
};
|
||||||
|
|
||||||
|
use crate::code_builder::CodeBuilder;
|
||||||
|
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
||||||
|
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
||||||
|
use crate::storage::{AddressValue, Storage, StoredValue, StoredVarKind};
|
||||||
|
use crate::{
|
||||||
|
copy_memory, CopyMemoryConfig, Env, DEBUG_SETTINGS, MEMORY_NAME, PTR_SIZE, PTR_TYPE,
|
||||||
|
TARGET_INFO,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -104,10 +104,9 @@ impl<'a> WasmBackend<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.link_host_to_app_calls(env.arena, host_to_app_map);
|
module.link_host_to_app_calls(env.arena, host_to_app_map);
|
||||||
module.code.code_builders.reserve(proc_lookup.len());
|
|
||||||
|
|
||||||
let host_function_count = module.import.imports.len()
|
let host_function_count = module.import.imports.len()
|
||||||
+ (module.code.dead_import_dummy_count + module.code.preloaded_count) as usize;
|
+ (module.code.dead_import_dummy_count + module.code.function_count) as usize;
|
||||||
|
|
||||||
WasmBackend {
|
WasmBackend {
|
||||||
env,
|
env,
|
||||||
|
@ -212,7 +211,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
self.module.data.end_addr += PTR_SIZE;
|
self.module.data.end_addr += PTR_SIZE;
|
||||||
|
|
||||||
self.module.reloc_code.apply_relocs_u32(
|
self.module.reloc_code.apply_relocs_u32(
|
||||||
&mut self.module.code.preloaded_bytes,
|
&mut self.module.code.bytes,
|
||||||
sym_index as u32,
|
sym_index as u32,
|
||||||
global_value_addr,
|
global_value_addr,
|
||||||
);
|
);
|
||||||
|
@ -320,7 +319,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
self.module.export.append(Export {
|
self.module.export.append(Export {
|
||||||
name: START,
|
name: START,
|
||||||
ty: ExportType::Func,
|
ty: ExportType::Func,
|
||||||
index: self.fn_index_offset + self.module.code.code_builders.len() as u32,
|
index: self.module.code.function_count,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.code_builder.i32_const(0); // argc=0
|
self.code_builder.i32_const(0); // argc=0
|
||||||
|
@ -367,7 +366,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
// Push the completed CodeBuilder into the module and swap it for a new empty one
|
// Push the completed CodeBuilder into the module and swap it for a new empty one
|
||||||
let mut swap_code_builder = CodeBuilder::new(self.env.arena);
|
let mut swap_code_builder = CodeBuilder::new(self.env.arena);
|
||||||
std::mem::swap(&mut swap_code_builder, &mut self.code_builder);
|
std::mem::swap(&mut swap_code_builder, &mut self.code_builder);
|
||||||
self.module.code.code_builders.push(swap_code_builder);
|
|
||||||
|
swap_code_builder.serialize_without_relocs(&mut self.module.code.bytes);
|
||||||
|
|
||||||
self.storage.clear();
|
self.storage.clear();
|
||||||
self.joinpoint_label_map.clear();
|
self.joinpoint_label_map.clear();
|
||||||
|
@ -1343,7 +1343,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
self.called_preload_fns.set(*fn_index as usize, true);
|
self.called_preload_fns.set(*fn_index as usize, true);
|
||||||
|
|
||||||
let host_import_count = self.fn_index_offset - self.module.code.preloaded_count;
|
let host_import_count = self.fn_index_offset - self.module.code.function_count;
|
||||||
if *fn_index < host_import_count {
|
if *fn_index < host_import_count {
|
||||||
self.code_builder
|
self.code_builder
|
||||||
.call_import(*fn_index, num_wasm_args, has_return_val);
|
.call_import(*fn_index, num_wasm_args, has_return_val);
|
||||||
|
|
|
@ -1,53 +1,24 @@
|
||||||
use bumpalo::collections::vec::Vec;
|
use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use core::panic;
|
use core::panic;
|
||||||
|
|
||||||
use roc_error_macros::internal_error;
|
use roc_error_macros::internal_error;
|
||||||
|
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
|
use roc_wasm_module::opcodes::{OpCode, OpCode::*};
|
||||||
use super::opcodes::{OpCode, OpCode::*};
|
use roc_wasm_module::serialize::{SerialBuffer, Serialize};
|
||||||
use super::serialize::{SerialBuffer, Serialize};
|
use roc_wasm_module::{
|
||||||
use crate::{
|
round_up_to_alignment, Align, LocalId, ValueType, FRAME_ALIGNMENT_BYTES,
|
||||||
round_up_to_alignment, DEBUG_SETTINGS, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID,
|
STACK_POINTER_GLOBAL_ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::DEBUG_SETTINGS;
|
||||||
|
|
||||||
macro_rules! log_instruction {
|
macro_rules! log_instruction {
|
||||||
($($x: expr),+) => {
|
($($x: expr),+) => {
|
||||||
if DEBUG_SETTINGS.instructions { println!($($x,)*); }
|
if DEBUG_SETTINGS.instructions { println!($($x,)*); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct LocalId(pub u32);
|
|
||||||
|
|
||||||
/// Wasm value type. (Rust representation matches Wasm encoding)
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
||||||
pub enum ValueType {
|
|
||||||
I32 = 0x7f,
|
|
||||||
I64 = 0x7e,
|
|
||||||
F32 = 0x7d,
|
|
||||||
F64 = 0x7c,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for ValueType {
|
|
||||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
|
||||||
buffer.append_u8(*self as u8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for ValueType {
|
|
||||||
fn from(x: u8) -> Self {
|
|
||||||
match x {
|
|
||||||
0x7f => Self::I32,
|
|
||||||
0x7e => Self::I64,
|
|
||||||
0x7d => Self::F32,
|
|
||||||
0x7c => Self::F64,
|
|
||||||
_ => internal_error!("Invalid ValueType 0x{:02x}", x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const BLOCK_NO_RESULT: u8 = 0x40;
|
const BLOCK_NO_RESULT: u8 = 0x40;
|
||||||
|
|
||||||
/// A control block in our model of the VM
|
/// A control block in our model of the VM
|
||||||
|
@ -65,52 +36,6 @@ impl std::fmt::Debug for VmBlock<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wasm memory alignment for load/store instructions.
|
|
||||||
/// 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, PartialEq, Eq, PartialOrd)]
|
|
||||||
pub enum Align {
|
|
||||||
Bytes1 = 0,
|
|
||||||
Bytes2 = 1,
|
|
||||||
Bytes4 = 2,
|
|
||||||
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 {
|
|
||||||
1 => Align::Bytes1,
|
|
||||||
2 => Align::Bytes2,
|
|
||||||
4 => Align::Bytes4,
|
|
||||||
_ => {
|
|
||||||
if x.count_ones() == 1 {
|
|
||||||
Align::Bytes8 // Max value supported by any Wasm instruction
|
|
||||||
} else {
|
|
||||||
internal_error!("Cannot align to {} bytes", x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||||
pub enum VmSymbolState {
|
pub enum VmSymbolState {
|
||||||
/// Value doesn't exist yet
|
/// Value doesn't exist yet
|
|
@ -1,5 +1,6 @@
|
||||||
//! Provides the WASM backend to generate Roc binaries.
|
//! Provides the WASM backend to generate Roc binaries.
|
||||||
mod backend;
|
mod backend;
|
||||||
|
mod code_builder;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod low_level;
|
mod low_level;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
@ -19,9 +20,10 @@ use roc_mono::ir::{Proc, ProcLayout};
|
||||||
use roc_mono::layout::{LayoutIds, STLayoutInterner};
|
use roc_mono::layout::{LayoutIds, STLayoutInterner};
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
use roc_wasm_module::parse::ParseError;
|
use roc_wasm_module::parse::ParseError;
|
||||||
|
use roc_wasm_module::{Align, LocalId, ValueType, WasmModule};
|
||||||
|
|
||||||
use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
||||||
use roc_wasm_module::{Align, CodeBuilder, LocalId, ValueType, WasmModule};
|
use crate::code_builder::CodeBuilder;
|
||||||
|
|
||||||
const TARGET_INFO: TargetInfo = TargetInfo::default_wasm32();
|
const TARGET_INFO: TargetInfo = TargetInfo::default_wasm32();
|
||||||
const PTR_SIZE: u32 = {
|
const PTR_SIZE: u32 = {
|
||||||
|
@ -96,7 +98,7 @@ pub fn build_app_module<'a>(
|
||||||
|
|
||||||
// Adjust Wasm function indices to account for functions from the object file
|
// Adjust Wasm function indices to account for functions from the object file
|
||||||
let fn_index_offset: u32 =
|
let fn_index_offset: u32 =
|
||||||
host_module.import.function_count() as u32 + host_module.code.preloaded_count;
|
host_module.import.function_count() as u32 + host_module.code.function_count;
|
||||||
|
|
||||||
// Pre-pass over the procedure names & layouts
|
// Pre-pass over the procedure names & layouts
|
||||||
// Create a lookup to tell us the final index of each proc in the output file
|
// Create a lookup to tell us the final index of each proc in the output file
|
||||||
|
|
|
@ -6,11 +6,10 @@ use roc_error_macros::internal_error;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::layout::{Layout, STLayoutInterner};
|
use roc_mono::layout::{Layout, STLayoutInterner};
|
||||||
|
|
||||||
|
use crate::code_builder::{CodeBuilder, VmSymbolState};
|
||||||
use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout};
|
use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout};
|
||||||
use crate::{copy_memory, CopyMemoryConfig, PTR_TYPE};
|
use crate::{copy_memory, CopyMemoryConfig, PTR_TYPE};
|
||||||
use roc_wasm_module::{
|
use roc_wasm_module::{round_up_to_alignment, Align, LocalId, ValueType};
|
||||||
round_up_to_alignment, Align, CodeBuilder, LocalId, ValueType, VmSymbolState,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum StoredVarKind {
|
pub enum StoredVarKind {
|
||||||
Variable,
|
Variable,
|
||||||
|
|
|
@ -5,18 +5,20 @@ The user needs to analyse the Wasm module's memory to decode the result.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use bumpalo::{collections::Vec, Bump};
|
use bumpalo::{collections::Vec, Bump};
|
||||||
|
|
||||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||||
use roc_intern::Interner;
|
use roc_intern::Interner;
|
||||||
use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
||||||
use roc_target::TargetInfo;
|
|
||||||
|
|
||||||
use crate::wasm32_sized::Wasm32Sized;
|
|
||||||
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
|
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
|
||||||
|
use roc_target::TargetInfo;
|
||||||
use roc_wasm_module::{
|
use roc_wasm_module::{
|
||||||
linking::SymInfo, linking::WasmObjectSymbol, Align, CodeBuilder, Export, ExportType, LocalId,
|
linking::SymInfo, linking::WasmObjectSymbol, Align, Export, ExportType, LocalId, Serialize,
|
||||||
Signature, ValueType, WasmModule,
|
Signature, ValueType, WasmModule,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::code_builder::CodeBuilder;
|
||||||
|
use crate::wasm32_sized::Wasm32Sized;
|
||||||
|
|
||||||
/// Type-driven wrapper generation
|
/// Type-driven wrapper generation
|
||||||
pub trait Wasm32Result {
|
pub trait Wasm32Result {
|
||||||
fn insert_wrapper<'a>(
|
fn insert_wrapper<'a>(
|
||||||
|
@ -28,7 +30,7 @@ pub trait Wasm32Result {
|
||||||
insert_wrapper_metadata(arena, module, wrapper_name);
|
insert_wrapper_metadata(arena, module, wrapper_name);
|
||||||
let mut code_builder = CodeBuilder::new(arena);
|
let mut code_builder = CodeBuilder::new(arena);
|
||||||
Self::build_wrapper_body(&mut code_builder, main_function_index);
|
Self::build_wrapper_body(&mut code_builder, main_function_index);
|
||||||
module.code.code_builders.push(code_builder);
|
code_builder.serialize(&mut module.code.bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32);
|
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32);
|
||||||
|
@ -51,7 +53,7 @@ pub fn insert_wrapper_for_layout<'a>(
|
||||||
insert_wrapper_metadata(arena, module, wrapper_name);
|
insert_wrapper_metadata(arena, module, wrapper_name);
|
||||||
let mut code_builder = CodeBuilder::new(arena);
|
let mut code_builder = CodeBuilder::new(arena);
|
||||||
build_wrapper_body_stack_memory(&mut code_builder, main_fn_index, size as usize);
|
build_wrapper_body_stack_memory(&mut code_builder, main_fn_index, size as usize);
|
||||||
module.code.code_builders.push(code_builder);
|
code_builder.serialize(&mut module.code.bytes);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,8 +94,7 @@ fn insert_wrapper_metadata<'a>(
|
||||||
) {
|
) {
|
||||||
let index = (module.import.function_count() as u32)
|
let index = (module.import.function_count() as u32)
|
||||||
+ module.code.dead_import_dummy_count
|
+ module.code.dead_import_dummy_count
|
||||||
+ module.code.preloaded_count
|
+ module.code.function_count;
|
||||||
+ module.code.code_builders.len() as u32;
|
|
||||||
|
|
||||||
module.add_function_signature(Signature {
|
module.add_function_signature(Signature {
|
||||||
param_types: Vec::with_capacity_in(0, arena),
|
param_types: Vec::with_capacity_in(0, arena),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod code_builder;
|
|
||||||
pub mod linking;
|
pub mod linking;
|
||||||
pub mod opcodes;
|
pub mod opcodes;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
@ -7,8 +6,9 @@ pub mod serialize;
|
||||||
|
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
|
|
||||||
pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
|
||||||
pub use linking::{OffsetRelocType, RelocationEntry, SymInfo};
|
pub use linking::{OffsetRelocType, RelocationEntry, SymInfo};
|
||||||
|
use opcodes::OpCode;
|
||||||
|
use roc_error_macros::internal_error;
|
||||||
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature};
|
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature};
|
||||||
|
|
||||||
use bitvec::vec::BitVec;
|
use bitvec::vec::BitVec;
|
||||||
|
@ -21,7 +21,7 @@ use self::sections::{
|
||||||
ImportDesc, ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId,
|
ImportDesc, ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId,
|
||||||
TableSection, TypeSection,
|
TableSection, TypeSection,
|
||||||
};
|
};
|
||||||
use self::serialize::{SerialBuffer, Serialize};
|
pub use self::serialize::{SerialBuffer, Serialize};
|
||||||
|
|
||||||
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
|
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
|
||||||
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
|
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
|
||||||
|
@ -132,7 +132,7 @@ impl<'a> WasmModule<'a> {
|
||||||
if function.signatures.is_empty() {
|
if function.signatures.is_empty() {
|
||||||
module_errors.push_str("Missing Function section\n");
|
module_errors.push_str("Missing Function section\n");
|
||||||
}
|
}
|
||||||
if code.preloaded_bytes.is_empty() {
|
if code.bytes.is_empty() {
|
||||||
module_errors.push_str("Missing Code section\n");
|
module_errors.push_str("Missing Code section\n");
|
||||||
}
|
}
|
||||||
if linking.symbol_table.is_empty() {
|
if linking.symbol_table.is_empty() {
|
||||||
|
@ -187,7 +187,7 @@ impl<'a> WasmModule<'a> {
|
||||||
|
|
||||||
let import_count = self.import.imports.len();
|
let import_count = self.import.imports.len();
|
||||||
let host_fn_min = import_count as u32 + self.code.dead_import_dummy_count;
|
let host_fn_min = import_count as u32 + self.code.dead_import_dummy_count;
|
||||||
let host_fn_max = host_fn_min + self.code.preloaded_count;
|
let host_fn_max = host_fn_min + self.code.function_count;
|
||||||
|
|
||||||
// All functions exported to JS must be kept alive
|
// All functions exported to JS must be kept alive
|
||||||
let exported_fns = self
|
let exported_fns = self
|
||||||
|
@ -278,39 +278,27 @@ impl<'a> WasmModule<'a> {
|
||||||
.linking
|
.linking
|
||||||
.find_and_reindex_imported_fn(old_index as u32, new_index as u32)
|
.find_and_reindex_imported_fn(old_index as u32, new_index as u32)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.reloc_code.apply_relocs_u32(
|
self.reloc_code
|
||||||
&mut self.code.preloaded_bytes,
|
.apply_relocs_u32(&mut self.code.bytes, sym_index, new_index as u32);
|
||||||
sym_index,
|
|
||||||
new_index as u32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relocate calls from Roc app to JS imports
|
|
||||||
for code_builder in self.code.code_builders.iter_mut() {
|
|
||||||
code_builder.apply_import_relocs(&live_import_fns);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Dead code elimination. Replace dead functions with tiny dummies.
|
// Dead code elimination. Replace dead functions with tiny dummies.
|
||||||
// Live function indices are unchanged, so no relocations are needed.
|
// Live function indices are unchanged, so no relocations are needed.
|
||||||
//
|
//
|
||||||
let dummy = CodeBuilder::dummy(arena);
|
let mut buffer = Vec::with_capacity_in(self.code.bytes.len(), arena);
|
||||||
let mut dummy_bytes = Vec::with_capacity_in(dummy.size(), arena);
|
self.code.function_count.serialize(&mut buffer);
|
||||||
dummy.serialize(&mut dummy_bytes);
|
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity_in(self.code.preloaded_bytes.len(), arena);
|
|
||||||
self.code.preloaded_count.serialize(&mut buffer);
|
|
||||||
for (i, fn_index) in (host_fn_min..host_fn_max).enumerate() {
|
for (i, fn_index) in (host_fn_min..host_fn_max).enumerate() {
|
||||||
if live_flags[fn_index as usize] {
|
if live_flags[fn_index as usize] {
|
||||||
let code_start = self.code.preloaded_offsets[i] as usize;
|
let code_start = self.code.function_offsets[i] as usize;
|
||||||
let code_end = self.code.preloaded_offsets[i + 1] as usize;
|
let code_end = self.code.function_offsets[i + 1] as usize;
|
||||||
buffer.extend_from_slice(&self.code.preloaded_bytes[code_start..code_end]);
|
buffer.extend_from_slice(&self.code.bytes[code_start..code_end]);
|
||||||
} else {
|
} else {
|
||||||
buffer.extend_from_slice(&dummy_bytes);
|
buffer.extend_from_slice(&DUMMY_FUNCTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.code.preloaded_bytes = buffer;
|
self.code.bytes = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trace_live_host_functions<I: Iterator<Item = u32>>(
|
fn trace_live_host_functions<I: Iterator<Item = u32>>(
|
||||||
|
@ -378,8 +366,8 @@ impl<'a> WasmModule<'a> {
|
||||||
|
|
||||||
// Find where the function body is
|
// Find where the function body is
|
||||||
let offset_index = fn_index - host_fn_min as usize;
|
let offset_index = fn_index - host_fn_min as usize;
|
||||||
let code_start = self.code.preloaded_offsets[offset_index];
|
let code_start = self.code.function_offsets[offset_index];
|
||||||
let code_end = self.code.preloaded_offsets[offset_index + 1];
|
let code_end = self.code.function_offsets[offset_index + 1];
|
||||||
|
|
||||||
// For each call in the body
|
// For each call in the body
|
||||||
for (offset, symbol) in call_offsets_and_symbols.iter() {
|
for (offset, symbol) in call_offsets_and_symbols.iter() {
|
||||||
|
@ -423,11 +411,8 @@ impl<'a> WasmModule<'a> {
|
||||||
self.linking
|
self.linking
|
||||||
.find_internal_symbol(sym_name)
|
.find_internal_symbol(sym_name)
|
||||||
.map(|sym_index| {
|
.map(|sym_index| {
|
||||||
self.reloc_code.apply_relocs_u32(
|
self.reloc_code
|
||||||
&mut self.code.preloaded_bytes,
|
.apply_relocs_u32(&mut self.code.bytes, sym_index as u32, value);
|
||||||
sym_index as u32,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
|
|
||||||
sym_index as u32
|
sym_index as u32
|
||||||
})
|
})
|
||||||
|
@ -494,11 +479,8 @@ impl<'a> WasmModule<'a> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Update calls to use the app function instead of the host import
|
// Update calls to use the app function instead of the host import
|
||||||
self.reloc_code.apply_relocs_u32(
|
self.reloc_code
|
||||||
&mut self.code.preloaded_bytes,
|
.apply_relocs_u32(&mut self.code.bytes, host_sym_index, app_fn_index);
|
||||||
host_sym_index,
|
|
||||||
app_fn_index,
|
|
||||||
);
|
|
||||||
|
|
||||||
if swap_import_index != host_import_index {
|
if swap_import_index != host_import_index {
|
||||||
// get the name using the old host import index because we already swapped it!
|
// get the name using the old host import index because we already swapped it!
|
||||||
|
@ -512,7 +494,7 @@ impl<'a> WasmModule<'a> {
|
||||||
|
|
||||||
// Update calls to the swapped JS import
|
// Update calls to the swapped JS import
|
||||||
self.reloc_code.apply_relocs_u32(
|
self.reloc_code.apply_relocs_u32(
|
||||||
&mut self.code.preloaded_bytes,
|
&mut self.code.bytes,
|
||||||
swap_sym_index,
|
swap_sym_index,
|
||||||
host_fn_index as u32,
|
host_fn_index as u32,
|
||||||
);
|
);
|
||||||
|
@ -595,6 +577,89 @@ impl<'a> WasmModule<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************
|
||||||
|
*
|
||||||
|
* Common types & utility functions
|
||||||
|
*
|
||||||
|
*******************************************************************/
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct LocalId(pub u32);
|
||||||
|
|
||||||
|
/// Wasm value type. (Rust representation matches Wasm encoding)
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub enum ValueType {
|
||||||
|
I32 = 0x7f,
|
||||||
|
I64 = 0x7e,
|
||||||
|
F32 = 0x7d,
|
||||||
|
F64 = 0x7c,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for ValueType {
|
||||||
|
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||||
|
buffer.append_u8(*self as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for ValueType {
|
||||||
|
fn from(x: u8) -> Self {
|
||||||
|
match x {
|
||||||
|
0x7f => Self::I32,
|
||||||
|
0x7e => Self::I64,
|
||||||
|
0x7d => Self::F32,
|
||||||
|
0x7c => Self::F64,
|
||||||
|
_ => internal_error!("Invalid ValueType 0x{:02x}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wasm memory alignment for load/store instructions.
|
||||||
|
/// 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, PartialEq, Eq, PartialOrd)]
|
||||||
|
pub enum Align {
|
||||||
|
Bytes1 = 0,
|
||||||
|
Bytes2 = 1,
|
||||||
|
Bytes4 = 2,
|
||||||
|
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 {
|
||||||
|
1 => Align::Bytes1,
|
||||||
|
2 => Align::Bytes2,
|
||||||
|
4 => Align::Bytes4,
|
||||||
|
_ => {
|
||||||
|
if x.count_ones() == 1 {
|
||||||
|
Align::Bytes8 // Max value supported by any Wasm instruction
|
||||||
|
} else {
|
||||||
|
internal_error!("Cannot align to {} bytes", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Round up to alignment_bytes (which must be a power of 2)
|
/// Round up to alignment_bytes (which must be a power of 2)
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! round_up_to_alignment {
|
macro_rules! round_up_to_alignment {
|
||||||
|
@ -615,24 +680,20 @@ macro_rules! round_up_to_alignment {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bytes for a dummy function with just a single `unreachable` instruction.
|
||||||
|
/// Used in dead code elimination to replace unused functions.
|
||||||
|
const DUMMY_FUNCTION: [u8; 4] = [
|
||||||
|
3, // inner byte length
|
||||||
|
0, // number of local variable declarations
|
||||||
|
OpCode::UNREACHABLE as u8, // panic if we were wrong to eliminate!
|
||||||
|
OpCode::END as u8, // end of function (required for validation)
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: make this an environment variable
|
||||||
pub struct WasmDebugSettings {
|
pub struct WasmDebugSettings {
|
||||||
proc_start_end: bool,
|
|
||||||
user_procs_ir: bool,
|
|
||||||
helper_procs_ir: bool,
|
|
||||||
let_stmt_ir: bool,
|
|
||||||
instructions: bool,
|
|
||||||
storage_map: bool,
|
|
||||||
pub keep_test_binary: bool,
|
|
||||||
pub skip_dead_code_elim: bool,
|
pub skip_dead_code_elim: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEBUG_SETTINGS: WasmDebugSettings = WasmDebugSettings {
|
pub const DEBUG_SETTINGS: WasmDebugSettings = WasmDebugSettings {
|
||||||
proc_start_end: false && cfg!(debug_assertions),
|
|
||||||
user_procs_ir: false && cfg!(debug_assertions), // Note: we also have `ROC_PRINT_IR_AFTER_REFCOUNT=1 cargo test-gen-wasm`
|
|
||||||
helper_procs_ir: false && cfg!(debug_assertions),
|
|
||||||
let_stmt_ir: false && cfg!(debug_assertions),
|
|
||||||
instructions: false && cfg!(debug_assertions),
|
|
||||||
storage_map: false && cfg!(debug_assertions),
|
|
||||||
keep_test_binary: false && cfg!(debug_assertions),
|
|
||||||
skip_dead_code_elim: false && cfg!(debug_assertions),
|
skip_dead_code_elim: false && cfg!(debug_assertions),
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,11 +4,13 @@ use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_error_macros::internal_error;
|
use roc_error_macros::internal_error;
|
||||||
|
|
||||||
|
use crate::DUMMY_FUNCTION;
|
||||||
|
|
||||||
use super::linking::{LinkingSection, SymInfo, WasmObjectSymbol};
|
use super::linking::{LinkingSection, SymInfo, WasmObjectSymbol};
|
||||||
use super::opcodes::OpCode;
|
use super::opcodes::OpCode;
|
||||||
use super::parse::{Parse, ParseError, SkipBytes};
|
use super::parse::{Parse, ParseError, SkipBytes};
|
||||||
use super::serialize::{SerialBuffer, Serialize, MAX_SIZE_ENCODED_U32};
|
use super::serialize::{SerialBuffer, Serialize, MAX_SIZE_ENCODED_U32};
|
||||||
use super::{CodeBuilder, ValueType};
|
use super::ValueType;
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
*
|
*
|
||||||
|
@ -1168,20 +1170,17 @@ impl<'a> Serialize for ElementSection<'a> {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CodeSection<'a> {
|
pub struct CodeSection<'a> {
|
||||||
pub preloaded_count: u32,
|
pub function_count: u32,
|
||||||
pub preloaded_bytes: Vec<'a, u8>,
|
pub bytes: Vec<'a, u8>,
|
||||||
/// The start of each preloaded function
|
/// The start of each preloaded function
|
||||||
pub preloaded_offsets: Vec<'a, u32>,
|
pub function_offsets: Vec<'a, u32>,
|
||||||
/// Dead imports are replaced with dummy functions in CodeSection
|
/// Dead imports are replaced with dummy functions in CodeSection
|
||||||
pub dead_import_dummy_count: u32,
|
pub dead_import_dummy_count: u32,
|
||||||
pub code_builders: Vec<'a, CodeBuilder<'a>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CodeSection<'a> {
|
impl<'a> CodeSection<'a> {
|
||||||
pub fn size(&self) -> usize {
|
pub fn size(&self) -> usize {
|
||||||
let builders_size: usize = self.code_builders.iter().map(|cb| cb.size()).sum();
|
MAX_SIZE_SECTION_HEADER + self.bytes.len()
|
||||||
|
|
||||||
MAX_SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(
|
pub fn parse(
|
||||||
|
@ -1224,11 +1223,10 @@ impl<'a> CodeSection<'a> {
|
||||||
debug_assert_eq!(preloaded_offsets.len(), 1 + count as usize);
|
debug_assert_eq!(preloaded_offsets.len(), 1 + count as usize);
|
||||||
|
|
||||||
Ok(CodeSection {
|
Ok(CodeSection {
|
||||||
preloaded_count: count,
|
function_count: count,
|
||||||
preloaded_bytes,
|
bytes: preloaded_bytes,
|
||||||
preloaded_offsets,
|
function_offsets: preloaded_offsets,
|
||||||
dead_import_dummy_count: 0,
|
dead_import_dummy_count: 0,
|
||||||
code_builders: Vec::with_capacity_in(0, arena),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1236,26 +1234,17 @@ impl<'a> CodeSection<'a> {
|
||||||
impl<'a> Serialize for CodeSection<'a> {
|
impl<'a> Serialize for CodeSection<'a> {
|
||||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||||
let header_indices = write_section_header(buffer, SectionId::Code);
|
let header_indices = write_section_header(buffer, SectionId::Code);
|
||||||
buffer.encode_u32(
|
buffer.encode_u32(self.dead_import_dummy_count + self.function_count);
|
||||||
self.dead_import_dummy_count + self.preloaded_count + self.code_builders.len() as u32,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert dummy functions, requested by our linking logic.
|
// Insert dummy functions, requested by our linking logic.
|
||||||
// This helps to minimise the number of functions we need to move around during linking.
|
// This helps to minimise the number of functions we need to move around during linking.
|
||||||
let arena = self.code_builders[0].arena;
|
|
||||||
let dummy = CodeBuilder::dummy(arena);
|
|
||||||
for _ in 0..self.dead_import_dummy_count {
|
for _ in 0..self.dead_import_dummy_count {
|
||||||
dummy.serialize(buffer);
|
DUMMY_FUNCTION.serialize(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// host + builtin functions
|
// real functions
|
||||||
let first_fn_start = self.preloaded_offsets[0] as usize;
|
let first_fn_start = self.function_offsets[0] as usize;
|
||||||
buffer.append_slice(&self.preloaded_bytes[first_fn_start..]);
|
buffer.append_slice(&self.bytes[first_fn_start..]);
|
||||||
|
|
||||||
// Roc functions
|
|
||||||
for code_builder in self.code_builders.iter() {
|
|
||||||
code_builder.serialize(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
update_section_size(buffer, header_indices);
|
update_section_size(buffer, header_indices);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::fmt::Debug;
|
||||||
/// Of course there is a price for this - an encoded U32 can be up to 5 bytes wide.
|
/// Of course there is a price for this - an encoded U32 can be up to 5 bytes wide.
|
||||||
pub const MAX_SIZE_ENCODED_U32: usize = 5;
|
pub const MAX_SIZE_ENCODED_U32: usize = 5;
|
||||||
|
|
||||||
pub(super) trait Serialize {
|
pub trait Serialize {
|
||||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
|
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue