Merge pull request #2621 from rtfeldman/wasm-runtime-error

Wasm RuntimeError
This commit is contained in:
Brian Carroll 2022-03-08 11:25:32 +00:00 committed by GitHub
commit c79fc18ba8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 595 additions and 217 deletions

View file

@ -29,11 +29,6 @@ use crate::{
PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, TARGET_INFO, PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, TARGET_INFO,
}; };
/// The memory address where the constants data will be loaded during module instantiation.
/// We avoid address zero and anywhere near it. They're valid addresses but maybe bug-prone.
/// Follow Emscripten's example by leaving 1kB unused (though 4 bytes would probably do!)
const CONST_SEGMENT_BASE_ADDR: u32 = 1024;
pub struct WasmBackend<'a> { pub struct WasmBackend<'a> {
pub env: &'a Env<'a>, pub env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
@ -57,7 +52,6 @@ pub struct WasmBackend<'a> {
} }
impl<'a> WasmBackend<'a> { impl<'a> WasmBackend<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
@ -78,6 +72,11 @@ impl<'a> WasmBackend<'a> {
index: STACK_POINTER_GLOBAL_ID, index: STACK_POINTER_GLOBAL_ID,
}); });
// The preloaded binary has a global to tell us where its data section ends
// Note: We need this to account for zero data (.bss), which doesn't have an explicit DataSegment!
let data_end_idx = module.export.globals_lookup["__data_end".as_bytes()];
let next_constant_addr = module.global.parse_u32_at_index(data_end_idx);
WasmBackend { WasmBackend {
env, env,
interns, interns,
@ -86,7 +85,7 @@ impl<'a> WasmBackend<'a> {
module, module,
layout_ids, layout_ids,
next_constant_addr: CONST_SEGMENT_BASE_ADDR, next_constant_addr,
fn_index_offset, fn_index_offset,
called_preload_fns: Vec::with_capacity_in(2, env.arena), called_preload_fns: Vec::with_capacity_in(2, env.arena),
proc_lookup, proc_lookup,
@ -530,7 +529,23 @@ impl<'a> WasmBackend<'a> {
} }
fn stmt_runtime_error(&mut self, msg: &'a str) { fn stmt_runtime_error(&mut self, msg: &'a str) {
todo!("RuntimeError {:?}", msg) // Create a zero-terminated version of the message string
let mut bytes = Vec::with_capacity_in(msg.len() + 1, self.env.arena);
bytes.extend_from_slice(msg.as_bytes());
bytes.push(0);
// Store it in the app's data section
let sym = self.create_symbol(msg);
let (linker_sym_index, elements_addr) = self.store_bytes_in_data_section(&bytes, sym);
// Pass its address to roc_panic
let tag_id = 0;
self.code_builder
.i32_const_mem_addr(elements_addr, linker_sym_index);
self.code_builder.i32_const(tag_id);
self.call_zig_builtin_after_loading_args("roc_panic", 2, false);
self.code_builder.unreachable_();
} }
/********************************************************** /**********************************************************
@ -541,7 +556,7 @@ impl<'a> WasmBackend<'a> {
fn expr(&mut self, sym: Symbol, expr: &Expr<'a>, layout: &Layout<'a>, storage: &StoredValue) { fn expr(&mut self, sym: Symbol, expr: &Expr<'a>, layout: &Layout<'a>, storage: &StoredValue) {
match expr { match expr {
Expr::Literal(lit) => self.expr_literal(lit, storage, sym, layout), Expr::Literal(lit) => self.expr_literal(lit, storage, sym),
Expr::Call(roc_mono::ir::Call { Expr::Call(roc_mono::ir::Call {
call_type, call_type,
@ -579,7 +594,9 @@ impl<'a> WasmBackend<'a> {
index, index,
} => self.expr_union_at_index(*structure, *tag_id, union_layout, *index, sym), } => self.expr_union_at_index(*structure, *tag_id, union_layout, *index, sym),
_ => todo!("Expression `{}`", expr.to_pretty(100)), Expr::Reuse { .. } | Expr::Reset { .. } | Expr::RuntimeErrorFunction(_) => {
todo!("Expression `{}`", expr.to_pretty(100))
}
} }
} }
@ -587,13 +604,7 @@ impl<'a> WasmBackend<'a> {
* Literals * Literals
*******************************************************************/ *******************************************************************/
fn expr_literal( fn expr_literal(&mut self, lit: &Literal<'a>, storage: &StoredValue, sym: Symbol) {
&mut self,
lit: &Literal<'a>,
storage: &StoredValue,
sym: Symbol,
layout: &Layout<'a>,
) {
let invalid_error = let invalid_error =
|| internal_error!("Literal value {:?} has invalid storage {:?}", lit, storage); || internal_error!("Literal value {:?} has invalid storage {:?}", lit, storage);
@ -655,8 +666,9 @@ impl<'a> WasmBackend<'a> {
self.code_builder.i64_const(str_as_int); self.code_builder.i64_const(str_as_int);
self.code_builder.i64_store(Align::Bytes4, offset); self.code_builder.i64_store(Align::Bytes4, offset);
} else { } else {
let bytes = string.as_bytes();
let (linker_sym_index, elements_addr) = let (linker_sym_index, elements_addr) =
self.expr_literal_big_str(string, sym, layout); self.store_bytes_in_data_section(bytes, sym);
self.code_builder.get_local(local_id); self.code_builder.get_local(local_id);
self.code_builder self.code_builder
@ -678,16 +690,11 @@ impl<'a> WasmBackend<'a> {
/// Create a string constant in the module data section /// Create a string constant in the module data section
/// Return the data we need for code gen: linker symbol index and memory address /// Return the data we need for code gen: linker symbol index and memory address
fn expr_literal_big_str( fn store_bytes_in_data_section(&mut self, bytes: &[u8], sym: Symbol) -> (u32, u32) {
&mut self,
string: &'a str,
sym: Symbol,
layout: &Layout<'a>,
) -> (u32, u32) {
// Place the segment at a 4-byte aligned offset // Place the segment at a 4-byte aligned offset
let segment_addr = round_up_to_alignment!(self.next_constant_addr, PTR_SIZE); let segment_addr = round_up_to_alignment!(self.next_constant_addr, PTR_SIZE);
let elements_addr = segment_addr + PTR_SIZE; let elements_addr = segment_addr + PTR_SIZE;
let length_with_refcount = 4 + string.len(); let length_with_refcount = 4 + bytes.len();
self.next_constant_addr = segment_addr + length_with_refcount as u32; self.next_constant_addr = segment_addr + length_with_refcount as u32;
let mut segment = DataSegment { let mut segment = DataSegment {
@ -698,14 +705,14 @@ impl<'a> WasmBackend<'a> {
// Prefix the string bytes with "infinite" refcount // Prefix the string bytes with "infinite" refcount
let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes();
segment.init.extend_from_slice(&refcount_max_bytes); segment.init.extend_from_slice(&refcount_max_bytes);
segment.init.extend_from_slice(string.as_bytes()); segment.init.extend_from_slice(bytes);
let segment_index = self.module.data.append_segment(segment); let segment_index = self.module.data.append_segment(segment);
// Generate linker symbol // Generate linker symbol
let name = self let name = self
.layout_ids .layout_ids
.get(sym, layout) .get(sym, &Layout::Builtin(Builtin::Str))
.to_symbol_string(sym, self.interns); .to_symbol_string(sym, self.interns);
let linker_symbol = SymInfo::Data(DataSymbol::Defined { let linker_symbol = SymInfo::Data(DataSymbol::Defined {
@ -713,7 +720,7 @@ impl<'a> WasmBackend<'a> {
name: name.clone(), name: name.clone(),
segment_index, segment_index,
segment_offset: 4, segment_offset: 4,
size: string.len() as u32, size: bytes.len() as u32,
}); });
// Ensure the linker keeps the segment aligned when relocating it // Ensure the linker keeps the segment aligned when relocating it

View file

@ -216,7 +216,7 @@ macro_rules! round_up_to_alignment {
if $alignment_bytes <= 1 { if $alignment_bytes <= 1 {
$unaligned $unaligned
} else if $alignment_bytes.count_ones() != 1 { } else if $alignment_bytes.count_ones() != 1 {
panic!( internal_error!(
"Cannot align to {} bytes. Not a power of 2.", "Cannot align to {} bytes. Not a power of 2.",
$alignment_bytes $alignment_bytes
); );

View file

@ -39,16 +39,16 @@ pub struct PreloadsCallGraph<'a> {
} }
impl<'a> PreloadsCallGraph<'a> { impl<'a> PreloadsCallGraph<'a> {
pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self { pub fn new(arena: &'a Bump, import_fn_count: usize, fn_count: usize) -> Self {
let num_preloads = (import_fn_count + fn_count) as usize; let num_preloads = import_fn_count + fn_count;
let mut code_offsets = Vec::with_capacity_in(num_preloads, arena); let mut code_offsets = Vec::with_capacity_in(num_preloads, arena);
let calls = Vec::with_capacity_in(2 * num_preloads, arena); let calls = Vec::with_capacity_in(2 * num_preloads, arena);
let mut calls_offsets = Vec::with_capacity_in(1 + num_preloads, arena); let mut calls_offsets = Vec::with_capacity_in(1 + num_preloads, arena);
// Imported functions have zero code length and no calls // Imported functions have zero code length and no calls
code_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); code_offsets.extend(std::iter::repeat(0).take(import_fn_count));
calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); calls_offsets.extend(std::iter::repeat(0).take(import_fn_count));
PreloadsCallGraph { PreloadsCallGraph {
num_preloads, num_preloads,
@ -65,11 +65,24 @@ impl<'a> PreloadsCallGraph<'a> {
/// use this backend without a linker. /// use this backend without a linker.
pub fn parse_preloads_call_graph<'a>( pub fn parse_preloads_call_graph<'a>(
arena: &'a Bump, arena: &'a Bump,
fn_count: u32,
code_section_body: &[u8], code_section_body: &[u8],
import_fn_count: u32, imported_fn_signatures: &[u32],
defined_fn_signatures: &[u32],
indirect_callees: &[u32],
) -> PreloadsCallGraph<'a> { ) -> PreloadsCallGraph<'a> {
let mut call_graph = PreloadsCallGraph::new(arena, import_fn_count, fn_count); let mut call_graph = PreloadsCallGraph::new(
arena,
imported_fn_signatures.len(),
defined_fn_signatures.len(),
);
// Function type signatures, used for indirect calls
let mut signatures = Vec::with_capacity_in(
imported_fn_signatures.len() + defined_fn_signatures.len(),
arena,
);
signatures.extend_from_slice(imported_fn_signatures);
signatures.extend_from_slice(defined_fn_signatures);
// Iterate over the bytes of the Code section // Iterate over the bytes of the Code section
let mut cursor: usize = 0; let mut cursor: usize = 0;
@ -88,13 +101,23 @@ pub fn parse_preloads_call_graph<'a>(
cursor += 1; // ValueType cursor += 1; // ValueType
} }
// Parse `call` instructions and skip over all other instructions // Parse `call` and `call_indirect` instructions, skip over everything else
while cursor < func_end { while cursor < func_end {
let opcode_byte: u8 = code_section_body[cursor]; let opcode_byte: u8 = code_section_body[cursor];
if opcode_byte == OpCode::CALL as u8 { if opcode_byte == OpCode::CALL as u8 {
cursor += 1; cursor += 1;
let call_index = parse_u32_or_panic(code_section_body, &mut cursor); let call_index = parse_u32_or_panic(code_section_body, &mut cursor);
call_graph.calls.push(call_index as u32); call_graph.calls.push(call_index as u32);
} else if opcode_byte == OpCode::CALLINDIRECT as u8 {
cursor += 1;
// Insert all indirect callees with a matching type signature
let sig = parse_u32_or_panic(code_section_body, &mut cursor);
call_graph.calls.extend(
indirect_callees
.iter()
.filter(|f| signatures[**f as usize] == sig),
);
u32::skip_bytes(code_section_body, &mut cursor); // table_idx
} else { } else {
OpCode::skip_bytes(code_section_body, &mut cursor); OpCode::skip_bytes(code_section_body, &mut cursor);
} }
@ -193,11 +216,13 @@ pub fn copy_preloads_shrinking_dead_fns<'a, T: SerialBuffer>(
live_preload_indices.sort_unstable(); live_preload_indices.sort_unstable();
live_preload_indices.dedup(); live_preload_indices.dedup();
let mut live_iter = live_preload_indices.iter(); let mut live_iter = live_preload_indices
.into_iter()
.skip_while(|f| (*f as usize) < preload_idx_start);
let mut next_live_idx = live_iter.next(); let mut next_live_idx = live_iter.next();
for i in preload_idx_start..call_graph.num_preloads { for i in preload_idx_start..call_graph.num_preloads {
match next_live_idx { match next_live_idx {
Some(live) if *live as usize == i => { Some(live) if live as usize == i => {
next_live_idx = live_iter.next(); next_live_idx = live_iter.next();
let live_body_start = call_graph.code_offsets[i] as usize; let live_body_start = call_graph.code_offsets[i] as usize;
let live_body_end = call_graph.code_offsets[i + 1] as usize; let live_body_end = call_graph.code_offsets[i + 1] as usize;

View file

@ -11,12 +11,10 @@ pub use linking::SymInfo;
use roc_error_macros::internal_error; 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 crate::wasm_module::serialize::SkipBytes;
use self::linking::{LinkingSection, RelocationSection}; use self::linking::{LinkingSection, RelocationSection};
use self::sections::{ use self::sections::{
CodeSection, DataSection, ExportSection, FunctionSection, GlobalSection, ImportSection, CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection,
MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection, ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection,
}; };
use self::serialize::{SerialBuffer, Serialize}; use self::serialize::{SerialBuffer, Serialize};
@ -32,7 +30,7 @@ pub struct WasmModule<'a> {
pub global: GlobalSection<'a>, pub global: GlobalSection<'a>,
pub export: ExportSection<'a>, pub export: ExportSection<'a>,
pub start: OpaqueSection<'a>, pub start: OpaqueSection<'a>,
pub element: OpaqueSection<'a>, pub element: ElementSection<'a>,
pub code: CodeSection<'a>, pub code: CodeSection<'a>,
pub data: DataSection<'a>, pub data: DataSection<'a>,
pub names: NameSection<'a>, pub names: NameSection<'a>,
@ -66,6 +64,7 @@ impl<'a> WasmModule<'a> {
self.element.serialize(buffer); self.element.serialize(buffer);
self.code.serialize(buffer); self.code.serialize(buffer);
self.data.serialize(buffer); self.data.serialize(buffer);
self.names.serialize(buffer);
} }
/// Serialize the module to bytes /// Serialize the module to bytes
@ -118,6 +117,7 @@ impl<'a> WasmModule<'a> {
+ self.element.size() + self.element.size()
+ self.code.size() + self.code.size()
+ self.data.size() + self.data.size()
+ self.names.size()
} }
pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Self { pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Self {
@ -132,18 +132,33 @@ impl<'a> WasmModule<'a> {
let mut types = TypeSection::preload(arena, bytes, &mut cursor); let mut types = TypeSection::preload(arena, bytes, &mut cursor);
types.parse_offsets(); types.parse_offsets();
let import = ImportSection::preload(arena, bytes, &mut cursor); let mut import = ImportSection::preload(arena, bytes, &mut cursor);
let imported_fn_signatures = import.parse(arena);
let function = FunctionSection::preload(arena, bytes, &mut cursor); 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 = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor);
let memory = MemorySection::preload(arena, bytes, &mut cursor); let memory = MemorySection::preload(arena, bytes, &mut cursor);
let global = GlobalSection::preload(arena, bytes, &mut cursor); let global = GlobalSection::preload(arena, bytes, &mut cursor);
ExportSection::skip_bytes(bytes, &mut cursor); let export = ExportSection::preload_globals(arena, bytes, &mut cursor);
let export = ExportSection::empty(arena);
let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor);
let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor);
let code = CodeSection::preload(arena, bytes, &mut cursor, import.function_count); let element = ElementSection::preload(arena, bytes, &mut cursor);
let indirect_callees = element.indirect_callees(arena);
let code = CodeSection::preload(
arena,
bytes,
&mut cursor,
&imported_fn_signatures,
&defined_fn_signatures,
&indirect_callees,
);
let data = DataSection::preload(arena, bytes, &mut cursor); let data = DataSection::preload(arena, bytes, &mut cursor);

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -10,7 +12,8 @@ use super::dead_code::{
use super::linking::RelocationEntry; use super::linking::RelocationEntry;
use super::opcodes::OpCode; use super::opcodes::OpCode;
use super::serialize::{ use super::serialize::{
parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes, MAX_SIZE_ENCODED_U32, parse_string_bytes, parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes,
MAX_SIZE_ENCODED_U32,
}; };
use super::{CodeBuilder, ValueType}; use super::{CodeBuilder, ValueType};
@ -377,20 +380,19 @@ impl<'a> ImportSection<'a> {
self.count += 1; self.count += 1;
} }
fn update_function_count(&mut self) { pub fn parse(&mut self, arena: &'a Bump) -> Vec<'a, u32> {
let mut f_count = 0; let mut fn_signatures = bumpalo::vec![in arena];
let mut cursor = 0; let mut cursor = 0;
while cursor < self.bytes.len() { while cursor < self.bytes.len() {
String::skip_bytes(&self.bytes, &mut cursor); String::skip_bytes(&self.bytes, &mut cursor); // import namespace
String::skip_bytes(&self.bytes, &mut cursor); String::skip_bytes(&self.bytes, &mut cursor); // import name
let type_id = ImportTypeId::from(self.bytes[cursor]); let type_id = ImportTypeId::from(self.bytes[cursor]);
cursor += 1; cursor += 1;
match type_id { match type_id {
ImportTypeId::Func => { ImportTypeId::Func => {
f_count += 1; fn_signatures.push(parse_u32_or_panic(&self.bytes, &mut cursor));
u32::skip_bytes(&self.bytes, &mut cursor);
} }
ImportTypeId::Table => { ImportTypeId::Table => {
TableType::skip_bytes(&self.bytes, &mut cursor); TableType::skip_bytes(&self.bytes, &mut cursor);
@ -404,17 +406,16 @@ impl<'a> ImportSection<'a> {
} }
} }
self.function_count = f_count; self.function_count = fn_signatures.len() as u32;
fn_signatures
} }
pub fn from_count_and_bytes(count: u32, bytes: Vec<'a, u8>) -> Self { pub fn from_count_and_bytes(count: u32, bytes: Vec<'a, u8>) -> Self {
let mut created = ImportSection { ImportSection {
bytes, bytes,
count, count,
function_count: 0, function_count: 0,
}; }
created.update_function_count();
created
} }
} }
@ -442,6 +443,16 @@ impl<'a> FunctionSection<'a> {
self.bytes.encode_u32(sig_id); self.bytes.encode_u32(sig_id);
self.count += 1; self.count += 1;
} }
pub fn parse(&self, arena: &'a Bump) -> Vec<'a, u32> {
let count = self.count as usize;
let mut signatures = Vec::with_capacity_in(count, arena);
let mut cursor = 0;
for _ in 0..count {
signatures.push(parse_u32_or_panic(&self.bytes, &mut cursor));
}
signatures
}
} }
section_impl!(FunctionSection, SectionId::Function); section_impl!(FunctionSection, SectionId::Function);
@ -555,6 +566,26 @@ pub enum ConstExpr {
F64(f64), F64(f64),
} }
impl ConstExpr {
fn parse_u32(bytes: &[u8], cursor: &mut usize) -> u32 {
let err = || internal_error!("Invalid ConstExpr. Expected i32.");
if bytes[*cursor] != OpCode::I32CONST as u8 {
err();
}
*cursor += 1;
let value = parse_u32_or_panic(bytes, cursor);
if bytes[*cursor] != OpCode::END as u8 {
err();
}
*cursor += 1;
value
}
}
impl Serialize for ConstExpr { impl Serialize for ConstExpr {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) { fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self { match self {
@ -579,6 +610,15 @@ impl Serialize for ConstExpr {
} }
} }
impl SkipBytes for ConstExpr {
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
while bytes[*cursor] != OpCode::END as u8 {
OpCode::skip_bytes(bytes, cursor);
}
*cursor += 1;
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Global { pub struct Global {
/// Type and mutability of the global /// Type and mutability of the global
@ -601,16 +641,14 @@ pub struct GlobalSection<'a> {
} }
impl<'a> GlobalSection<'a> { impl<'a> GlobalSection<'a> {
pub fn new(arena: &'a Bump, globals: &[Global]) -> Self { pub fn parse_u32_at_index(&self, index: u32) -> u32 {
let capacity = 13 * globals.len(); let mut cursor = 0;
let mut bytes = Vec::with_capacity_in(capacity, arena); for _ in 0..index {
for global in globals { GlobalType::skip_bytes(&self.bytes, &mut cursor);
global.serialize(&mut bytes); ConstExpr::skip_bytes(&self.bytes, &mut cursor);
}
GlobalSection {
count: globals.len() as u32,
bytes,
} }
GlobalType::skip_bytes(&self.bytes, &mut cursor);
ConstExpr::parse_u32(&self.bytes, &mut cursor)
} }
pub fn append(&mut self, global: Global) { pub fn append(&mut self, global: Global) {
@ -636,6 +674,18 @@ pub enum ExportType {
Global = 3, Global = 3,
} }
impl From<u8> for ExportType {
fn from(x: u8) -> Self {
match x {
0 => Self::Func,
1 => Self::Table,
2 => Self::Mem,
3 => Self::Global,
_ => internal_error!("invalid ExportType {:2x?}", x),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Export<'a> { pub struct Export<'a> {
pub name: &'a [u8], pub name: &'a [u8],
@ -643,6 +693,19 @@ pub struct Export<'a> {
pub index: u32, pub index: u32,
} }
impl<'a> Export<'a> {
fn parse(arena: &'a Bump, bytes: &[u8], cursor: &mut usize) -> Self {
let name = parse_string_bytes(arena, bytes, cursor);
let ty = ExportType::from(bytes[*cursor]);
*cursor += 1;
let index = parse_u32_or_panic(bytes, cursor);
Export { name, ty, index }
}
}
impl Serialize for Export<'_> { impl Serialize for Export<'_> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) { fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.name.serialize(buffer); self.name.serialize(buffer);
@ -655,7 +718,10 @@ impl Serialize for Export<'_> {
pub struct ExportSection<'a> { pub struct ExportSection<'a> {
pub count: u32, pub count: u32,
pub bytes: Vec<'a, u8>, pub bytes: Vec<'a, u8>,
/// List of exported functions to keep during dead-code-elimination
pub function_indices: Vec<'a, u32>, pub function_indices: Vec<'a, u32>,
/// name -> index
pub globals_lookup: MutMap<&'a [u8], u32>,
} }
impl<'a> ExportSection<'a> { impl<'a> ExportSection<'a> {
@ -673,18 +739,36 @@ impl<'a> ExportSection<'a> {
section_size(&self.bytes) section_size(&self.bytes)
} }
pub fn empty(arena: &'a Bump) -> Self { fn empty(arena: &'a Bump) -> Self {
ExportSection { ExportSection {
count: 0, count: 0,
bytes: Vec::with_capacity_in(256, arena), bytes: Vec::with_capacity_in(256, arena),
function_indices: Vec::with_capacity_in(4, arena), function_indices: Vec::with_capacity_in(4, arena),
globals_lookup: MutMap::default(),
} }
} }
}
impl SkipBytes for ExportSection<'_> { /// Preload from object file. Keep only the Global exports, ignore the rest.
fn skip_bytes(bytes: &[u8], cursor: &mut usize) { pub fn preload_globals(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self {
parse_section(Self::ID, bytes, cursor); let (num_exports, body_bytes) = parse_section(Self::ID, module_bytes, cursor);
let mut export_section = ExportSection::empty(arena);
let mut body_cursor = 0;
for _ in 0..num_exports {
let export_start = body_cursor;
let export = Export::parse(arena, body_bytes, &mut body_cursor);
if matches!(export.ty, ExportType::Global) {
let global_bytes = &body_bytes[export_start..body_cursor];
export_section.bytes.extend_from_slice(global_bytes);
export_section.count += 1;
export_section
.globals_lookup
.insert(export.name, export.index);
}
}
export_section
} }
} }
@ -699,6 +783,122 @@ impl<'a> Serialize for ExportSection<'a> {
} }
} }
/*******************************************************************
*
* Element section
*
* Elements are entries in tables (see Table section)
* For example, Wasm uses a function table instead of function pointers,
* and each entry in that function table is an element.
* The call_indirect instruction uses element indices to refer to functions.
* This section therefore enumerates all indirectly-called functions.
*
*******************************************************************/
#[repr(u8)]
enum ElementSegmentFormatId {
/// Currently only supporting the original Wasm MVP format since it's the only one in wide use.
/// There are newer formats for other table types, with complex encodings to preserve backward compatibility
/// (Already going down the same path as x86!)
ActiveImplicitTableIndex = 0x00,
}
#[derive(Debug)]
struct ElementSegment<'a> {
offset: ConstExpr,
fn_indices: Vec<'a, u32>,
}
impl<'a> ElementSegment<'a> {
fn parse(arena: &'a Bump, bytes: &[u8], cursor: &mut usize) -> Self {
// In practice we only need the original MVP format
let format_id = bytes[*cursor];
debug_assert!(format_id == ElementSegmentFormatId::ActiveImplicitTableIndex as u8);
*cursor += 1;
// The table index offset is encoded as a ConstExpr, but only I32 makes sense
let const_expr_opcode = bytes[*cursor];
debug_assert!(const_expr_opcode == OpCode::I32CONST as u8);
*cursor += 1;
let offset = parse_u32_or_panic(bytes, cursor);
debug_assert!(bytes[*cursor] == OpCode::END as u8);
*cursor += 1;
let num_elems = parse_u32_or_panic(bytes, cursor);
let mut fn_indices = Vec::with_capacity_in(num_elems as usize, arena);
for _ in 0..num_elems {
let fn_idx = parse_u32_or_panic(bytes, cursor);
fn_indices.push(fn_idx);
}
ElementSegment {
offset: ConstExpr::I32(offset as i32),
fn_indices,
}
}
fn size(&self) -> usize {
let variant_id = 1;
let constexpr_opcode = 1;
let constexpr_value = MAX_SIZE_ENCODED_U32;
let vec_len = MAX_SIZE_ENCODED_U32;
let vec_contents = MAX_SIZE_ENCODED_U32 * self.fn_indices.len();
variant_id + constexpr_opcode + constexpr_value + vec_len + vec_contents
}
}
impl<'a> Serialize for ElementSegment<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(ElementSegmentFormatId::ActiveImplicitTableIndex as u8);
self.offset.serialize(buffer);
self.fn_indices.serialize(buffer);
}
}
#[derive(Debug)]
pub struct ElementSection<'a> {
segments: Vec<'a, ElementSegment<'a>>,
}
impl<'a> ElementSection<'a> {
const ID: SectionId = SectionId::Element;
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);
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 }
}
pub fn size(&self) -> usize {
self.segments.iter().map(|seg| seg.size()).sum()
}
pub fn indirect_callees(&self, arena: &'a Bump) -> Vec<'a, u32> {
let mut result = bumpalo::vec![in arena];
for segment in self.segments.iter() {
result.extend_from_slice(&segment.fn_indices);
}
result
}
}
impl<'a> Serialize for ElementSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
let header_indices = write_section_header(buffer, Self::ID);
self.segments.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
/******************************************************************* /*******************************************************************
* *
* Code section (see also code_builder.rs) * Code section (see also code_builder.rs)
@ -742,14 +942,21 @@ impl<'a> CodeSection<'a> {
arena: &'a Bump, arena: &'a Bump,
module_bytes: &[u8], module_bytes: &[u8],
cursor: &mut usize, cursor: &mut usize,
import_fn_count: u32, import_signatures: &[u32],
function_signatures: &[u32],
indirect_callees: &[u32],
) -> Self { ) -> Self {
let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor);
let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); let preloaded_bytes = arena.alloc_slice_copy(initial_bytes);
// TODO: Try to move this call_graph preparation to platform build time // TODO: Try to move this call_graph preparation to platform build time
let dead_code_metadata = let dead_code_metadata = parse_preloads_call_graph(
parse_preloads_call_graph(arena, preloaded_count, initial_bytes, import_fn_count); arena,
initial_bytes,
import_signatures,
function_signatures,
indirect_callees,
);
CodeSection { CodeSection {
preloaded_count, preloaded_count,
@ -850,7 +1057,7 @@ impl Serialize for DataSegment<'_> {
#[derive(Debug)] #[derive(Debug)]
pub struct DataSection<'a> { pub struct DataSection<'a> {
count: u32, count: u32,
bytes: Vec<'a, u8>, pub bytes: Vec<'a, u8>, // public so backend.rs can calculate addr of first string
} }
impl<'a> DataSection<'a> { impl<'a> DataSection<'a> {
@ -928,8 +1135,8 @@ enum NameSubSections {
LocalNames = 2, LocalNames = 2,
} }
#[derive(Debug, Default)]
pub struct NameSection<'a> { pub struct NameSection<'a> {
pub bytes: Vec<'a, u8>,
pub functions: MutMap<&'a [u8], u32>, pub functions: MutMap<&'a [u8], u32>,
} }
@ -938,13 +1145,6 @@ impl<'a> NameSection<'a> {
const NAME: &'static str = "name"; const NAME: &'static str = "name";
pub fn parse(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { pub fn parse(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self {
let functions = MutMap::default();
let mut section = NameSection { functions };
section.parse_help(arena, module_bytes, cursor);
section
}
fn parse_help(&mut self, arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) {
// Custom section ID // Custom section ID
let section_id_byte = module_bytes[*cursor]; let section_id_byte = module_bytes[*cursor];
if section_id_byte != Self::ID as u8 { if section_id_byte != Self::ID as u8 {
@ -958,21 +1158,37 @@ impl<'a> NameSection<'a> {
*cursor += 1; *cursor += 1;
// Section size // Section size
let section_size = parse_u32_or_panic(module_bytes, cursor); let section_size = parse_u32_or_panic(module_bytes, cursor) as usize;
let section_end = *cursor + section_size as usize; let section_end = *cursor + section_size;
// Custom section name let mut bytes = Vec::with_capacity_in(section_size, arena);
let section_name_len = parse_u32_or_panic(module_bytes, cursor); bytes.extend_from_slice(&module_bytes[*cursor..section_end]);
let section_name_end = *cursor + section_name_len as usize; let functions = MutMap::default();
let section_name = &module_bytes[*cursor..section_name_end]; let mut section = NameSection { bytes, functions };
section.parse_body(arena, module_bytes, cursor, section_end);
section
}
pub fn size(&self) -> usize {
self.bytes.len()
}
fn parse_body(
&mut self,
arena: &'a Bump,
module_bytes: &[u8],
cursor: &mut usize,
section_end: usize,
) {
let section_name = parse_string_bytes(arena, module_bytes, cursor);
if section_name != Self::NAME.as_bytes() { if section_name != Self::NAME.as_bytes() {
internal_error!( internal_error!(
"Expected Custon section {:?}, found {:?}", "Expected Custom section {:?}, found {:?}",
Self::NAME, Self::NAME,
std::str::from_utf8(section_name) std::str::from_utf8(section_name)
); );
} }
*cursor = section_name_end;
// Find function names subsection // Find function names subsection
let mut found_function_names = false; let mut found_function_names = false;
@ -997,10 +1213,7 @@ impl<'a> NameSection<'a> {
let num_entries = parse_u32_or_panic(module_bytes, cursor) as usize; let num_entries = parse_u32_or_panic(module_bytes, cursor) as usize;
for _ in 0..num_entries { for _ in 0..num_entries {
let fn_index = parse_u32_or_panic(module_bytes, cursor); let fn_index = parse_u32_or_panic(module_bytes, cursor);
let name_len = parse_u32_or_panic(module_bytes, cursor); let name_bytes = parse_string_bytes(arena, module_bytes, cursor);
let name_end = *cursor + name_len as usize;
let name_bytes: &[u8] = &module_bytes[*cursor..name_end];
*cursor = name_end;
self.functions self.functions
.insert(arena.alloc_slice_copy(name_bytes), fn_index); .insert(arena.alloc_slice_copy(name_bytes), fn_index);
@ -1008,6 +1221,37 @@ impl<'a> NameSection<'a> {
} }
} }
impl<'a> Serialize for NameSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if !self.bytes.is_empty() {
let header_indices = write_section_header(buffer, Self::ID);
buffer.append_slice(&self.bytes);
update_section_size(buffer, header_indices);
}
}
}
impl<'a> Debug for NameSection<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "NameSection")?;
// We want to display index->name because it matches the binary format and looks nicer.
// But our hashmap is name->index because that's what code gen wants to look up.
let mut by_index = std::vec::Vec::with_capacity(self.functions.len());
for (name, index) in self.functions.iter() {
by_index.push((*index, name));
}
by_index.sort_unstable();
for (index, name) in by_index.iter() {
let name_str = unsafe { std::str::from_utf8_unchecked(name) };
writeln!(f, " {:4}: {}", index, name_str)?;
}
Ok(())
}
}
/******************************************************************* /*******************************************************************
* *
* Unit tests * Unit tests

View file

@ -1,6 +1,6 @@
use std::{fmt::Debug, iter::FromIterator}; use std::{fmt::Debug, iter::FromIterator};
use bumpalo::collections::vec::Vec; use bumpalo::{collections::vec::Vec, Bump};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
/// In the WebAssembly binary format, all integers are variable-length encoded (using LEB-128) /// In the WebAssembly binary format, all integers are variable-length encoded (using LEB-128)
@ -262,6 +262,15 @@ pub fn parse_u32_or_panic(bytes: &[u8], cursor: &mut usize) -> u32 {
value value
} }
pub fn parse_string_bytes<'a>(arena: &'a Bump, bytes: &[u8], cursor: &mut usize) -> &'a [u8] {
let len = parse_u32_or_panic(bytes, cursor);
let end = *cursor + len as usize;
let bytes: &[u8] = &bytes[*cursor..end];
let copy = arena.alloc_slice_copy(bytes);
*cursor = end;
copy
}
/// Skip over serialized bytes for a type /// Skip over serialized bytes for a type
/// This may, or may not, require looking at the byte values /// This may, or may not, require looking at the byte values
pub trait SkipBytes { pub trait SkipBytes {

View file

@ -3,8 +3,6 @@ use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_expect_failed; use crate::helpers::llvm::assert_expect_failed;
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_llvm_evals_to;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_non_opt_evals_to; use crate::helpers::llvm::assert_non_opt_evals_to;
#[cfg(feature = "gen-dev")] #[cfg(feature = "gen-dev")]
@ -2470,10 +2468,10 @@ fn function_malformed_pattern() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Hit an erroneous type when creating a layout for")] #[should_panic(expected = "Hit an erroneous type when creating a layout for")]
fn call_invalid_layout() { fn call_invalid_layout() {
assert_llvm_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
f : I64 -> I64 f : I64 -> I64

View file

@ -1,14 +1,11 @@
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to; use crate::helpers::llvm::{assert_evals_to, expect_runtime_error_panic};
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::expect_runtime_error_panic;
#[cfg(feature = "gen-dev")] #[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to; use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")] #[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to; use crate::helpers::wasm::{assert_evals_to, expect_runtime_error_panic};
// use crate::assert_wasm_evals_to as assert_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
@ -1044,6 +1041,7 @@ fn different_proc_types_specialized_to_same_layout() {
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
#[should_panic( #[should_panic(
// TODO: something upstream is escaping the ' // TODO: something upstream is escaping the '
// NOTE: Are we sure it's upstream? It's not escaped in gen-wasm version below!
expected = r#"Roc failed with message: "Can\'t create record with improper layout""# expected = r#"Roc failed with message: "Can\'t create record with improper layout""#
)] )]
fn call_with_bad_record_runtime_error() { fn call_with_bad_record_runtime_error() {
@ -1059,6 +1057,22 @@ fn call_with_bad_record_runtime_error() {
)) ))
} }
#[test]
#[cfg(any(feature = "gen-wasm"))]
#[should_panic(expected = r#"Can't create record with improper layout"#)]
fn call_with_bad_record_runtime_error() {
expect_runtime_error_panic!(indoc!(
r#"
app "test" provides [ main ] to "./platform"
main =
get : {a: Bool} -> Bool
get = \{a} -> a
get {b: ""}
"#
))
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn generalized_accessor() { fn generalized_accessor() {

View file

@ -1,5 +1,5 @@
#[cfg(feature = "gen-wasm")] #[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_refcounts; use crate::helpers::{wasm::assert_refcounts, RefCount::*};
#[allow(unused_imports)] #[allow(unused_imports)]
use indoc::indoc; use indoc::indoc;
@ -25,8 +25,8 @@ fn str_inc() {
), ),
RocList<RocStr>, RocList<RocStr>,
&[ &[
3, // s Live(3), // s
1 // result Live(1) // result
] ]
); );
} }
@ -43,7 +43,7 @@ fn str_dealloc() {
"# "#
), ),
bool, bool,
&[0] &[Deallocated]
); );
} }
@ -60,10 +60,10 @@ fn list_int_inc() {
RocList<RocList<i64>>, RocList<RocList<i64>>,
&[ &[
// TODO be smarter about coalescing polymorphic list values // TODO be smarter about coalescing polymorphic list values
1, // list0 Live(1), // list0
1, // list1 Live(1), // list1
1, // list2 Live(1), // list2
1 // result Live(1) // result
] ]
); );
} }
@ -81,10 +81,10 @@ fn list_int_dealloc() {
usize, usize,
&[ &[
// TODO be smarter about coalescing polymorphic list values // TODO be smarter about coalescing polymorphic list values
0, // list0 Deallocated, // list0
0, // list1 Deallocated, // list1
0, // list2 Deallocated, // list2
0 // result Deallocated // result
] ]
); );
} }
@ -102,9 +102,9 @@ fn list_str_inc() {
), ),
RocList<RocList<RocStr>>, RocList<RocList<RocStr>>,
&[ &[
6, // s Live(6), // s
2, // list Live(2), // list
1 // result Live(1) // result
] ]
); );
} }
@ -122,9 +122,9 @@ fn list_str_dealloc() {
), ),
usize, usize,
&[ &[
0, // s Deallocated, // s
0, // list Deallocated, // list
0 // result Deallocated // result
] ]
); );
} }
@ -142,7 +142,7 @@ fn struct_inc() {
"# "#
), ),
[(i64, RocStr, RocStr); 2], [(i64, RocStr, RocStr); 2],
&[4] // s &[Live(4)] // s
); );
} }
@ -160,7 +160,7 @@ fn struct_dealloc() {
"# "#
), ),
i64, i64,
&[0] // s &[Deallocated] // s
); );
} }
@ -186,7 +186,7 @@ fn union_nonrecursive_inc() {
"# "#
), ),
(TwoStr, TwoStr, i64), (TwoStr, TwoStr, i64),
&[4] &[Live(4)]
); );
} }
@ -209,7 +209,7 @@ fn union_nonrecursive_dec() {
"# "#
), ),
RocStr, RocStr,
&[1] // s &[Live(1)] // s
); );
} }
@ -234,9 +234,9 @@ fn union_recursive_inc() {
), ),
(Pointer, Pointer), (Pointer, Pointer),
&[ &[
4, // s Live(4), // s
4, // sym Live(4), // sym
2, // e Live(2), // e
] ]
); );
} }
@ -264,9 +264,9 @@ fn union_recursive_dec() {
), ),
Pointer, Pointer,
&[ &[
1, // s Live(1), // s
1, // sym Live(1), // sym
0 // e Deallocated // e
] ]
); );
} }
@ -300,13 +300,13 @@ fn refcount_different_rosetrees_inc() {
), ),
(Pointer, Pointer), (Pointer, Pointer),
&[ &[
2, // s Live(2), // s
3, // i1 Live(3), // i1
2, // s1 Live(2), // s1
1, // [i1, i1] Live(1), // [i1, i1]
1, // i2 Live(1), // i2
1, // [s1, s1] Live(1), // [s1, s1]
1 // s2 Live(1) // s2
] ]
); );
} }
@ -341,13 +341,13 @@ fn refcount_different_rosetrees_dec() {
), ),
i64, i64,
&[ &[
0, // s Deallocated, // s
0, // i1 Deallocated, // i1
0, // s1 Deallocated, // s1
0, // [i1, i1] Deallocated, // [i1, i1]
0, // i2 Deallocated, // i2
0, // [s1, s1] Deallocated, // [s1, s1]
0, // s2 Deallocated, // s2
] ]
); );
} }
@ -370,10 +370,10 @@ fn union_linked_list_inc() {
), ),
(Pointer, Pointer), (Pointer, Pointer),
&[ &[
6, // s Live(6), // s
2, // Cons Live(2), // Cons
2, // Cons Live(2), // Cons
2, // Cons Live(2), // Cons
] ]
); );
} }
@ -398,10 +398,10 @@ fn union_linked_list_dec() {
), ),
RocStr, RocStr,
&[ &[
1, // s Live(1), // s
0, // Cons Deallocated, // Cons
0, // Cons Deallocated, // Cons
0, // Cons Deallocated, // Cons
] ]
); );
} }
@ -434,6 +434,6 @@ fn union_linked_list_long_dec() {
"# "#
), ),
i64, i64,
&[0; 1_000] &[Deallocated; 1_000]
); );
} }

View file

@ -1222,7 +1222,7 @@ fn applied_tag_function_linked_list() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "")] #[should_panic(expected = "")] // TODO: this only panics because it returns 0 instead of 1!
fn tag_must_be_its_own_type() { fn tag_must_be_its_own_type() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(

View file

@ -258,6 +258,7 @@
function fd_write(fd, iovs_ptr, iovs_len, nwritten_mut_ptr) { function fd_write(fd, iovs_ptr, iovs_len, nwritten_mut_ptr) {
let string_buffer = ""; let string_buffer = "";
let nwritten = 0; let nwritten = 0;
const STDOUT = 1;
for (let i = 0; i < iovs_len; i++) { for (let i = 0; i < iovs_len; i++) {
const index32 = iovs_ptr >> 2; const index32 = iovs_ptr >> 2;
@ -282,16 +283,18 @@
} }
wasiLinkObject.memory32[nwritten_mut_ptr >> 2] = nwritten; wasiLinkObject.memory32[nwritten_mut_ptr >> 2] = nwritten;
if (string_buffer) { if (string_buffer) {
console.log(string_buffer); if (fd === STDOUT) {
console.log(string_buffer);
} else {
console.error(string_buffer);
}
} }
return 0; return 0;
} }
// proc_exit : (i32) -> nil // proc_exit : (i32) -> nil
function proc_exit(exit_code) { function proc_exit(exit_code) {
if (exit_code) { throw new Error(`Wasm exited with code ${exit_code}`);
throw new Error(`Wasm exited with code ${exit_code}`);
}
} }
// Signatures from wasm_test_platform.o // Signatures from wasm_test_platform.o

View file

@ -579,19 +579,31 @@ macro_rules! assert_llvm_evals_to {
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! assert_evals_to { macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{ ($src:expr, $expected:expr, $ty:ty) => {{
assert_evals_to!($src, $expected, $ty, $crate::helpers::llvm::identity); assert_evals_to!($src, $expected, $ty, $crate::helpers::llvm::identity, false);
}}; }};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{
// same as above, except with an additional transformation argument. // same as above, except with an additional transformation argument.
{ assert_evals_to!($src, $expected, $ty, $transform, false);
#[cfg(feature = "wasm-cli-run")] }};
$crate::helpers::llvm::assert_wasm_evals_to!( ($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems: expr) => {{
$src, $expected, $ty, $transform, false, false // same as above, except with ignore_problems.
); #[cfg(feature = "wasm-cli-run")]
$crate::helpers::llvm::assert_wasm_evals_to!(
$src,
$expected,
$ty,
$transform,
$ignore_problems
);
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false); $crate::helpers::llvm::assert_llvm_evals_to!(
} $src,
}; $expected,
$ty,
$transform,
$ignore_problems
);
}};
} }
#[allow(unused_macros)] #[allow(unused_macros)]

View file

@ -55,3 +55,10 @@ where
{ {
run_test() run_test()
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RefCount {
Live(u32),
Deallocated,
Constant,
}

View file

@ -6,6 +6,7 @@ use std::marker::PhantomData;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use wasmer::{Memory, WasmPtr}; use wasmer::{Memory, WasmPtr};
use super::RefCount;
use crate::helpers::from_wasmer_memory::FromWasmerMemory; use crate::helpers::from_wasmer_memory::FromWasmerMemory;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_gen_wasm::wasm32_result::Wasm32Result; use roc_gen_wasm::wasm32_result::Wasm32Result;
@ -17,6 +18,7 @@ const OUT_DIR_VAR: &str = "TEST_GEN_OUT";
const TEST_WRAPPER_NAME: &str = "test_wrapper"; const TEST_WRAPPER_NAME: &str = "test_wrapper";
const INIT_REFCOUNT_NAME: &str = "init_refcount_test"; const INIT_REFCOUNT_NAME: &str = "init_refcount_test";
const PANIC_MSG_NAME: &str = "panic_msg";
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
@ -176,7 +178,7 @@ fn load_bytes_into_runtime(bytes: Vec<u8>) -> wasmer::Instance {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn assert_wasm_evals_to_help<T>(src: &str, phantom: PhantomData<T>) -> Result<T, String> pub fn assert_evals_to_help<T>(src: &str, phantom: PhantomData<T>) -> Result<T, String>
where where
T: FromWasmerMemory + Wasm32Result, T: FromWasmerMemory + Wasm32Result,
{ {
@ -192,7 +194,13 @@ where
let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap(); let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap();
match test_wrapper.call(&[]) { match test_wrapper.call(&[]) {
Err(e) => Err(format!("{:?}", e)), Err(e) => {
if let Some(msg) = get_roc_panic_msg(&instance, memory) {
Err(msg)
} else {
Err(e.to_string())
}
}
Ok(result) => { Ok(result) => {
let address = result[0].unwrap_i32(); let address = result[0].unwrap_i32();
@ -213,12 +221,37 @@ where
} }
} }
/// Our test roc_panic stores a pointer to its message in a global variable so we can find it.
fn get_roc_panic_msg(instance: &wasmer::Instance, memory: &Memory) -> Option<String> {
let memory_bytes = unsafe { memory.data_unchecked() };
// We need to dereference twice!
// The Wasm Global only points at the memory location of the C global value
let panic_msg_global = instance.exports.get_global(PANIC_MSG_NAME).unwrap();
let global_addr = panic_msg_global.get().unwrap_i32() as usize;
let global_ptr = memory_bytes[global_addr..].as_ptr() as *const u32;
// Dereference again to find the bytes of the message string
let msg_addr = unsafe { *global_ptr };
if msg_addr == 0 {
return None;
}
let msg_index = msg_addr as usize;
let msg_len = memory_bytes[msg_index..]
.iter()
.position(|c| *c == 0)
.unwrap();
let msg_bytes = memory_bytes[msg_index..][..msg_len].to_vec();
let msg = unsafe { String::from_utf8_unchecked(msg_bytes) };
Some(msg)
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn assert_wasm_refcounts_help<T>( pub fn assert_wasm_refcounts_help<T>(
src: &str, src: &str,
phantom: PhantomData<T>, phantom: PhantomData<T>,
num_refcounts: usize, num_refcounts: usize,
) -> Result<Vec<u32>, String> ) -> Result<Vec<RefCount>, String>
where where
T: FromWasmerMemory + Wasm32Result, T: FromWasmerMemory + Wasm32Result,
{ {
@ -264,12 +297,15 @@ where
for i in 0..num_refcounts { for i in 0..num_refcounts {
let rc_ptr = refcount_ptrs[i].get(); let rc_ptr = refcount_ptrs[i].get();
let rc = if rc_ptr.offset() == 0 { let rc = if rc_ptr.offset() == 0 {
// RC pointer has been set to null, which means the value has been freed. RefCount::Deallocated
// In tests, we simply represent this as zero refcount.
0
} else { } else {
let rc_encoded = rc_ptr.deref(memory).unwrap().get(); let rc_encoded: i32 = rc_ptr.deref(memory).unwrap().get();
(rc_encoded - i32::MIN + 1) as u32 if rc_encoded == 0 {
RefCount::Constant
} else {
let rc = rc_encoded - i32::MIN + 1;
RefCount::Live(rc as u32)
}
}; };
refcounts.push(rc); refcounts.push(rc);
} }
@ -307,42 +343,44 @@ pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) {
} }
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! assert_wasm_evals_to { macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => {
$crate::helpers::wasm::assert_evals_to!(
$src,
$expected,
$ty,
$crate::helpers::wasm::identity,
false
)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
$crate::helpers::wasm::assert_evals_to!($src, $expected, $ty, $transform, false);
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems: expr) => {{
let phantom = std::marker::PhantomData; let phantom = std::marker::PhantomData;
match $crate::helpers::wasm::assert_wasm_evals_to_help::<$ty>($src, phantom) { let _ = $ignore_problems; // Always ignore "problems"! One backend (LLVM) is enough to cover them.
Err(msg) => panic!("{:?}", msg), match $crate::helpers::wasm::assert_evals_to_help::<$ty>($src, phantom) {
Err(msg) => panic!("{}", msg),
Ok(actual) => { Ok(actual) => {
assert_eq!($transform(actual), $expected) assert_eq!($transform(actual), $expected)
} }
} }
}; }};
($src:expr, $expected:expr, $ty:ty) => {
$crate::helpers::wasm::assert_wasm_evals_to!(
$src,
$expected,
$ty,
$crate::helpers::wasm::identity
);
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
$crate::helpers::wasm::assert_wasm_evals_to!($src, $expected, $ty, $transform);
};
} }
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! assert_evals_to { macro_rules! expect_runtime_error_panic {
($src:expr, $expected:expr, $ty:ty) => {{ ($src:expr) => {{
assert_evals_to!($src, $expected, $ty, $crate::helpers::wasm::identity); $crate::helpers::wasm::assert_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::wasm::identity,
true // ignore problems
);
}}; }};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
$crate::helpers::wasm::assert_wasm_evals_to!($src, $expected, $ty, $transform);
}
};
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -371,8 +409,9 @@ macro_rules! assert_refcounts {
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use assert_evals_to; pub(crate) use assert_evals_to;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use assert_wasm_evals_to; pub(crate) use expect_runtime_error_panic;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use assert_refcounts; pub(crate) use assert_refcounts;

View file

@ -1,4 +1,5 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
// Makes test runs take 50% longer, due to linking // Makes test runs take 50% longer, due to linking
#define ENABLE_PRINTF 0 #define ENABLE_PRINTF 0
@ -121,14 +122,18 @@ void roc_dealloc(void *ptr, unsigned int alignment)
//-------------------------- //--------------------------
void roc_panic(void *ptr, unsigned int alignment) // Allow the test to probe the panic message
char* panic_msg;
void roc_panic(char *msg, unsigned int tag_id)
{ {
#if ENABLE_PRINTF panic_msg = msg;
char *msg = (char *)ptr;
fprintf(stderr, // Note: no dynamic string formatting
"Application crashed with message\n\n %s\n\nShutting down\n", msg); fputs("Application crashed with message\n\n ", stderr);
#endif fputs(msg, stderr);
abort(); fputs("\n\nShutting down\n", stderr);
exit(101);
} }
//-------------------------- //--------------------------