mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Wasm: rename DeadCodeMetadata to PreloadsCallGraph
This commit is contained in:
parent
4311b5a410
commit
c5134fa015
2 changed files with 38 additions and 36 deletions
|
@ -13,14 +13,14 @@ Or, more specifically, "dead function replacement"
|
||||||
|
|
||||||
- On pre-loading the object file:
|
- On pre-loading the object file:
|
||||||
- Analyse its call graph by finding all `call` instructions in the Code section,
|
- 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:
|
- While compiling Roc code:
|
||||||
- Run the backend as usual, adding more data into various sections of the Wasm module
|
- 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.
|
- 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.
|
These are the "live" preloaded functions that we are not allowed to eliminate.
|
||||||
- Call graph analysis:
|
- Call graph analysis:
|
||||||
- Starting with the set of live preloaded functions, trace their call graphs using the info we
|
- 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:
|
- Dead function replacement:
|
||||||
- We actually don't want to just *delete* dead functions, because that would change the indices
|
- 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.
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct DeadCodeMetadata<'a> {
|
pub struct PreloadsCallGraph<'a> {
|
||||||
num_preloads: usize,
|
num_preloads: usize,
|
||||||
/// Byte offset where each function body can be found
|
/// Byte offset where each function body can be found
|
||||||
code_offsets: Vec<'a, u32>,
|
code_offsets: Vec<'a, u32>,
|
||||||
|
@ -38,7 +38,7 @@ pub struct DeadCodeMetadata<'a> {
|
||||||
calls_offsets: Vec<'a, u32>,
|
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 {
|
pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self {
|
||||||
let num_preloads = (import_fn_count + fn_count) as usize;
|
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));
|
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));
|
calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize));
|
||||||
|
|
||||||
DeadCodeMetadata {
|
PreloadsCallGraph {
|
||||||
num_preloads,
|
num_preloads,
|
||||||
code_offsets,
|
code_offsets,
|
||||||
calls,
|
calls,
|
||||||
|
@ -63,36 +63,38 @@ impl<'a> DeadCodeMetadata<'a> {
|
||||||
/// which functions are actually called, and which are not.
|
/// 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
|
/// This would normally be done in a linker optimisation, but we want to be able to
|
||||||
/// use this backend without a linker.
|
/// use this backend without a linker.
|
||||||
pub fn parse_dead_code_metadata<'a>(
|
pub fn parse_preloads_call_graph<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
fn_count: u32,
|
fn_count: u32,
|
||||||
code_section_body: &[u8],
|
code_section_body: &[u8],
|
||||||
import_fn_count: u32,
|
import_fn_count: u32,
|
||||||
) -> DeadCodeMetadata<'a> {
|
) -> PreloadsCallGraph<'a> {
|
||||||
let mut metadata = DeadCodeMetadata::new(arena, import_fn_count, fn_count);
|
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;
|
let mut cursor: usize = 0;
|
||||||
while cursor < code_section_body.len() {
|
while cursor < code_section_body.len() {
|
||||||
metadata.code_offsets.push(cursor as u32);
|
// Record the start of a function
|
||||||
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);
|
||||||
|
|
||||||
let func_size = parse_u32_or_panic(code_section_body, &mut cursor);
|
let func_size = parse_u32_or_panic(code_section_body, &mut cursor);
|
||||||
let func_end = cursor + func_size as usize;
|
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);
|
let local_groups_count = parse_u32_or_panic(code_section_body, &mut cursor);
|
||||||
for _ in 0..local_groups_count {
|
for _ in 0..local_groups_count {
|
||||||
parse_u32_or_panic(code_section_body, &mut cursor);
|
parse_u32_or_panic(code_section_body, &mut cursor);
|
||||||
cursor += 1; // ValueType
|
cursor += 1; // ValueType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instructions
|
// Parse `call` instructions and skip over all other instructions
|
||||||
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);
|
||||||
metadata.calls.push(call_index as u32);
|
call_graph.calls.push(call_index as u32);
|
||||||
} else {
|
} else {
|
||||||
OpCode::skip_bytes(code_section_body, &mut cursor);
|
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
|
// Extra entries to mark the end of the last function
|
||||||
metadata.code_offsets.push(cursor as u32);
|
call_graph.code_offsets.push(cursor as u32);
|
||||||
metadata.calls_offsets.push(metadata.calls.len() as u32);
|
call_graph.calls_offsets.push(call_graph.calls.len() as u32);
|
||||||
|
|
||||||
metadata
|
call_graph
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trace the dependencies of a list of functions
|
/// 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
|
/// 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.
|
/// Result is the full set of builtins and platform functions used in the app.
|
||||||
/// The rest are "dead code" and can be eliminated.
|
/// The rest are "dead code" and can be eliminated.
|
||||||
pub fn trace_function_deps<'a, Indices: IntoIterator<Item = u32>>(
|
pub fn trace_call_graph<'a, Indices: IntoIterator<Item = u32>>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
metadata: &DeadCodeMetadata<'a>,
|
call_graph: &PreloadsCallGraph<'a>,
|
||||||
exported_fns: &[u32],
|
exported_fns: &[u32],
|
||||||
called_from_app: Indices,
|
called_from_app: Indices,
|
||||||
) -> Vec<'a, u32> {
|
) -> 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
|
// All functions that get called from the app, directly or indirectly
|
||||||
let mut live_fn_indices = Vec::with_capacity_in(num_preloads, arena);
|
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)
|
// (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
|
// 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);
|
let mut current_trace = Vec::with_capacity_in(num_preloads, arena);
|
||||||
|
@ -147,9 +149,9 @@ pub fn trace_function_deps<'a, Indices: IntoIterator<Item = u32>>(
|
||||||
for func_idx in current_trace.iter() {
|
for func_idx in current_trace.iter() {
|
||||||
let i = *func_idx as usize;
|
let i = *func_idx as usize;
|
||||||
already_traced[i] = true;
|
already_traced[i] = true;
|
||||||
let calls_start = metadata.calls_offsets[i] as usize;
|
let calls_start = call_graph.calls_offsets[i] as usize;
|
||||||
let calls_end = metadata.calls_offsets[i + 1] as usize;
|
let calls_end = call_graph.calls_offsets[i + 1] as usize;
|
||||||
let called_indices: &[u32] = &metadata.calls[calls_start..calls_end];
|
let called_indices: &[u32] = &call_graph.calls[calls_start..calls_end];
|
||||||
for called_idx in called_indices {
|
for called_idx in called_indices {
|
||||||
if !already_traced[*called_idx as usize] {
|
if !already_traced[*called_idx as usize] {
|
||||||
next_trace.push(*called_idx);
|
next_trace.push(*called_idx);
|
||||||
|
@ -171,10 +173,10 @@ pub fn trace_function_deps<'a, Indices: IntoIterator<Item = u32>>(
|
||||||
|
|
||||||
/// Copy used functions from preloaded object file into our Code section
|
/// Copy used functions from preloaded object file into our Code section
|
||||||
/// Replace unused functions with very small dummies, to avoid changing any indices
|
/// 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,
|
arena: &'a Bump,
|
||||||
buffer: &mut T,
|
buffer: &mut T,
|
||||||
metadata: &DeadCodeMetadata<'a>,
|
call_graph: &PreloadsCallGraph<'a>,
|
||||||
external_code: &[u8],
|
external_code: &[u8],
|
||||||
import_fn_count: u32,
|
import_fn_count: u32,
|
||||||
mut live_preload_indices: Vec<'a, 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 live_iter = live_preload_indices.iter();
|
||||||
let mut next_live_idx = live_iter.next();
|
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 {
|
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 = metadata.code_offsets[i] as usize;
|
let live_body_start = call_graph.code_offsets[i] as usize;
|
||||||
let live_body_end = metadata.code_offsets[i + 1] 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]);
|
buffer.append_slice(&external_code[live_body_start..live_body_end]);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
@ -4,8 +4,8 @@ use roc_collections::all::MutMap;
|
||||||
use roc_reporting::internal_error;
|
use roc_reporting::internal_error;
|
||||||
|
|
||||||
use super::dead_code::{
|
use super::dead_code::{
|
||||||
copy_live_and_replace_dead_preloads, parse_dead_code_metadata, trace_function_deps,
|
copy_preloads_shrinking_dead_fns, parse_preloads_call_graph, trace_call_graph,
|
||||||
DeadCodeMetadata,
|
PreloadsCallGraph,
|
||||||
};
|
};
|
||||||
use super::linking::RelocationEntry;
|
use super::linking::RelocationEntry;
|
||||||
use super::opcodes::OpCode;
|
use super::opcodes::OpCode;
|
||||||
|
@ -705,7 +705,7 @@ pub struct CodeSection<'a> {
|
||||||
pub preloaded_count: u32,
|
pub preloaded_count: u32,
|
||||||
pub preloaded_bytes: &'a [u8],
|
pub preloaded_bytes: &'a [u8],
|
||||||
pub code_builders: Vec<'a, CodeBuilder<'a>>,
|
pub code_builders: Vec<'a, CodeBuilder<'a>>,
|
||||||
dead_code_metadata: DeadCodeMetadata<'a>,
|
dead_code_metadata: PreloadsCallGraph<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CodeSection<'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_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 metadata 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_dead_code_metadata(arena, preloaded_count, initial_bytes, import_fn_count);
|
parse_preloads_call_graph(arena, preloaded_count, initial_bytes, import_fn_count);
|
||||||
|
|
||||||
CodeSection {
|
CodeSection {
|
||||||
preloaded_count,
|
preloaded_count,
|
||||||
|
@ -761,7 +761,7 @@ impl<'a> CodeSection<'a> {
|
||||||
exported_fns: &[u32],
|
exported_fns: &[u32],
|
||||||
called_preload_fns: T,
|
called_preload_fns: T,
|
||||||
) {
|
) {
|
||||||
let live_ext_fn_indices = trace_function_deps(
|
let live_ext_fn_indices = trace_call_graph(
|
||||||
arena,
|
arena,
|
||||||
&self.dead_code_metadata,
|
&self.dead_code_metadata,
|
||||||
exported_fns,
|
exported_fns,
|
||||||
|
@ -770,7 +770,7 @@ impl<'a> CodeSection<'a> {
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena);
|
let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena);
|
||||||
|
|
||||||
copy_live_and_replace_dead_preloads(
|
copy_preloads_shrinking_dead_fns(
|
||||||
arena,
|
arena,
|
||||||
&mut buffer,
|
&mut buffer,
|
||||||
&self.dead_code_metadata,
|
&self.dead_code_metadata,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue