diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 3b22bddfc1..33950ee42c 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -13,14 +13,14 @@ Or, more specifically, "dead function replacement" - On pre-loading the object file: - Analyse its call graph by finding all `call` instructions in the Code section, - and checking which function index they refer to. Store this in a `DeadCodeMetadata` + and checking which function index they refer to. Store this in a `PreloadsCallGraph` - While compiling Roc code: - Run the backend as usual, adding more data into various sections of the Wasm module - Whenever a call to a builtin or platform function is made, record its index in a Set. These are the "live" preloaded functions that we are not allowed to eliminate. - Call graph analysis: - Starting with the set of live preloaded functions, trace their call graphs using the info we - collected earlier in `DeadCodeMetadata`. Mark all function indices in the call graph as "live". + collected earlier in `PreloadsCallGraph`. Mark all function indices in the call graph as "live". - Dead function replacement: - We actually don't want to just *delete* dead functions, because that would change the indices of the live functions, invalidating all references to them, such as `call` instructions. @@ -28,7 +28,7 @@ Or, more specifically, "dead function replacement" */ #[derive(Debug)] -pub struct DeadCodeMetadata<'a> { +pub struct PreloadsCallGraph<'a> { num_preloads: usize, /// Byte offset where each function body can be found code_offsets: Vec<'a, u32>, @@ -38,7 +38,7 @@ pub struct DeadCodeMetadata<'a> { calls_offsets: Vec<'a, u32>, } -impl<'a> DeadCodeMetadata<'a> { +impl<'a> PreloadsCallGraph<'a> { pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self { let num_preloads = (import_fn_count + fn_count) as usize; @@ -50,7 +50,7 @@ impl<'a> DeadCodeMetadata<'a> { code_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); - DeadCodeMetadata { + PreloadsCallGraph { num_preloads, code_offsets, calls, @@ -63,36 +63,38 @@ impl<'a> DeadCodeMetadata<'a> { /// which functions are actually called, and which are not. /// This would normally be done in a linker optimisation, but we want to be able to /// use this backend without a linker. -pub fn parse_dead_code_metadata<'a>( +pub fn parse_preloads_call_graph<'a>( arena: &'a Bump, fn_count: u32, code_section_body: &[u8], import_fn_count: u32, -) -> DeadCodeMetadata<'a> { - let mut metadata = DeadCodeMetadata::new(arena, import_fn_count, fn_count); +) -> PreloadsCallGraph<'a> { + let mut call_graph = PreloadsCallGraph::new(arena, import_fn_count, fn_count); + // Iterate over the bytes of the Code section let mut cursor: usize = 0; while cursor < code_section_body.len() { - metadata.code_offsets.push(cursor as u32); - metadata.calls_offsets.push(metadata.calls.len() as u32); + // Record the start of a function + call_graph.code_offsets.push(cursor as u32); + call_graph.calls_offsets.push(call_graph.calls.len() as u32); let func_size = parse_u32_or_panic(code_section_body, &mut cursor); let func_end = cursor + func_size as usize; - // Local variable declarations + // Skip over local variable declarations let local_groups_count = parse_u32_or_panic(code_section_body, &mut cursor); for _ in 0..local_groups_count { parse_u32_or_panic(code_section_body, &mut cursor); cursor += 1; // ValueType } - // Instructions + // Parse `call` instructions and skip over all other instructions while cursor < func_end { let opcode_byte: u8 = code_section_body[cursor]; if opcode_byte == OpCode::CALL as u8 { cursor += 1; let call_index = parse_u32_or_panic(code_section_body, &mut cursor); - metadata.calls.push(call_index as u32); + call_graph.calls.push(call_index as u32); } else { OpCode::skip_bytes(code_section_body, &mut cursor); } @@ -100,29 +102,29 @@ pub fn parse_dead_code_metadata<'a>( } // Extra entries to mark the end of the last function - metadata.code_offsets.push(cursor as u32); - metadata.calls_offsets.push(metadata.calls.len() as u32); + call_graph.code_offsets.push(cursor as u32); + call_graph.calls_offsets.push(call_graph.calls.len() as u32); - metadata + call_graph } /// Trace the dependencies of a list of functions -/// We've already collected metadata saying which functions call each other +/// We've already collected call_graph saying which functions call each other /// Now we need to trace the dependency graphs of a specific subset of them /// Result is the full set of builtins and platform functions used in the app. /// The rest are "dead code" and can be eliminated. -pub fn trace_function_deps<'a, Indices: IntoIterator>( +pub fn trace_call_graph<'a, Indices: IntoIterator>( arena: &'a Bump, - metadata: &DeadCodeMetadata<'a>, + call_graph: &PreloadsCallGraph<'a>, exported_fns: &[u32], called_from_app: Indices, ) -> Vec<'a, u32> { - let num_preloads = metadata.num_preloads; + let num_preloads = call_graph.num_preloads; // All functions that get called from the app, directly or indirectly let mut live_fn_indices = Vec::with_capacity_in(num_preloads, arena); - // Current & next batch of functions whose call graphs we want to trace through the metadata + // Current & next batch of functions whose call graphs we want to trace through the call_graph // (2 separate vectors so that we're not iterating over the same one we're changing) // If the max call depth is N then we will do N traces or less let mut current_trace = Vec::with_capacity_in(num_preloads, arena); @@ -147,9 +149,9 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( for func_idx in current_trace.iter() { let i = *func_idx as usize; already_traced[i] = true; - let calls_start = metadata.calls_offsets[i] as usize; - let calls_end = metadata.calls_offsets[i + 1] as usize; - let called_indices: &[u32] = &metadata.calls[calls_start..calls_end]; + let calls_start = call_graph.calls_offsets[i] as usize; + let calls_end = call_graph.calls_offsets[i + 1] as usize; + let called_indices: &[u32] = &call_graph.calls[calls_start..calls_end]; for called_idx in called_indices { if !already_traced[*called_idx as usize] { next_trace.push(*called_idx); @@ -171,10 +173,10 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( /// Copy used functions from preloaded object file into our Code section /// Replace unused functions with very small dummies, to avoid changing any indices -pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( +pub fn copy_preloads_shrinking_dead_fns<'a, T: SerialBuffer>( arena: &'a Bump, buffer: &mut T, - metadata: &DeadCodeMetadata<'a>, + call_graph: &PreloadsCallGraph<'a>, external_code: &[u8], import_fn_count: u32, mut live_preload_indices: Vec<'a, u32>, @@ -193,12 +195,12 @@ pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( let mut live_iter = live_preload_indices.iter(); let mut next_live_idx = live_iter.next(); - for i in preload_idx_start..metadata.num_preloads { + for i in preload_idx_start..call_graph.num_preloads { match next_live_idx { Some(live) if *live as usize == i => { next_live_idx = live_iter.next(); - let live_body_start = metadata.code_offsets[i] as usize; - let live_body_end = metadata.code_offsets[i + 1] 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; buffer.append_slice(&external_code[live_body_start..live_body_end]); } _ => { diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 0bbec1a3a9..213892f5d8 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -4,8 +4,8 @@ use roc_collections::all::MutMap; use roc_reporting::internal_error; use super::dead_code::{ - copy_live_and_replace_dead_preloads, parse_dead_code_metadata, trace_function_deps, - DeadCodeMetadata, + copy_preloads_shrinking_dead_fns, parse_preloads_call_graph, trace_call_graph, + PreloadsCallGraph, }; use super::linking::RelocationEntry; use super::opcodes::OpCode; @@ -705,7 +705,7 @@ pub struct CodeSection<'a> { pub preloaded_count: u32, pub preloaded_bytes: &'a [u8], pub code_builders: Vec<'a, CodeBuilder<'a>>, - dead_code_metadata: DeadCodeMetadata<'a>, + dead_code_metadata: PreloadsCallGraph<'a>, } impl<'a> CodeSection<'a> { @@ -742,9 +742,9 @@ impl<'a> CodeSection<'a> { let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); - // TODO: Try to move this metadata preparation to platform build time + // TODO: Try to move this call_graph preparation to platform build time let dead_code_metadata = - parse_dead_code_metadata(arena, preloaded_count, initial_bytes, import_fn_count); + parse_preloads_call_graph(arena, preloaded_count, initial_bytes, import_fn_count); CodeSection { preloaded_count, @@ -761,7 +761,7 @@ impl<'a> CodeSection<'a> { exported_fns: &[u32], called_preload_fns: T, ) { - let live_ext_fn_indices = trace_function_deps( + let live_ext_fn_indices = trace_call_graph( arena, &self.dead_code_metadata, exported_fns, @@ -770,7 +770,7 @@ impl<'a> CodeSection<'a> { let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); - copy_live_and_replace_dead_preloads( + copy_preloads_shrinking_dead_fns( arena, &mut buffer, &self.dead_code_metadata,