Skip to content

Commit 10b5cc5

Browse files
authored
Further compress the in-memory representation of address maps (#2324)
This commit reduces the size of `InstructionAddressMap` from 24 bytes to 8 bytes by dropping the `code_len` field and reducing `code_offset` to `u32` instead of `usize`. The intention is to primarily make the in-memory version take up less space, and the hunch is that the `code_len` is largely not necessary since most entries in this map are always adjacent to one another. The `code_len` field is now implied by the `code_offset` field of the next entry in the map. This isn't as big of an improvement to serialized module size as #2321 or #2322, primarily because of the switch to variable-length encoding. Despite this though it shaves about 10MB off the encoded size of the module from #2318
1 parent 372ae2a commit 10b5cc5

5 files changed

Lines changed: 115 additions & 72 deletions

File tree

crates/cranelift/src/lib.rs

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,28 @@ impl StackMapSink {
229229
fn get_function_address_map<'data>(
230230
context: &Context,
231231
data: &FunctionBodyData<'data>,
232-
body_len: usize,
232+
body_len: u32,
233233
isa: &dyn isa::TargetIsa,
234234
) -> FunctionAddressMap {
235+
// Generate artificial srcloc for function start/end to identify boundary
236+
// within module.
237+
let data = data.body.get_binary_reader();
238+
let offset = data.original_position();
239+
let len = data.bytes_remaining();
240+
assert!((offset + len) <= u32::max_value() as usize);
241+
let start_srcloc = ir::SourceLoc::new(offset as u32);
242+
let end_srcloc = ir::SourceLoc::new((offset + len) as u32);
243+
235244
let instructions = if let Some(ref mcr) = &context.mach_compile_result {
236245
// New-style backend: we have a `MachCompileResult` that will give us `MachSrcLoc` mapping
237246
// tuples.
238-
collect_address_maps(mcr.buffer.get_srclocs_sorted().into_iter().map(
239-
|&MachSrcLoc { start, end, loc }| InstructionAddressMap {
240-
srcloc: loc,
241-
code_offset: start as usize,
242-
code_len: (end - start) as usize,
243-
},
244-
))
247+
collect_address_maps(
248+
body_len,
249+
mcr.buffer
250+
.get_srclocs_sorted()
251+
.into_iter()
252+
.map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))),
253+
)
245254
} else {
246255
// Old-style backend: we need to traverse the instruction/encoding info in the function.
247256
let func = &context.func;
@@ -250,28 +259,16 @@ fn get_function_address_map<'data>(
250259

251260
let encinfo = isa.encoding_info();
252261
collect_address_maps(
262+
body_len,
253263
blocks
254264
.into_iter()
255265
.flat_map(|block| func.inst_offsets(block, &encinfo))
256-
.map(|(offset, inst, size)| InstructionAddressMap {
257-
srcloc: func.srclocs[inst],
258-
code_offset: offset as usize,
259-
code_len: size as usize,
260-
}),
266+
.map(|(offset, inst, size)| (func.srclocs[inst], offset, size)),
261267
)
262268
};
263269

264-
// Generate artificial srcloc for function start/end to identify boundary
265-
// within module. Similar to FuncTranslator::cur_srcloc(): it will wrap around
266-
// if byte code is larger than 4 GB.
267-
let data = data.body.get_binary_reader();
268-
let offset = data.original_position();
269-
let len = data.bytes_remaining();
270-
let start_srcloc = ir::SourceLoc::new(offset as u32);
271-
let end_srcloc = ir::SourceLoc::new((offset + len) as u32);
272-
273270
FunctionAddressMap {
274-
instructions,
271+
instructions: instructions.into(),
275272
start_srcloc,
276273
end_srcloc,
277274
body_offset: 0,
@@ -283,23 +280,54 @@ fn get_function_address_map<'data>(
283280
// into a `FunctionAddressMap`. This will automatically coalesce adjacent
284281
// instructions which map to the same original source position.
285282
fn collect_address_maps(
286-
iter: impl IntoIterator<Item = InstructionAddressMap>,
283+
code_size: u32,
284+
iter: impl IntoIterator<Item = (ir::SourceLoc, u32, u32)>,
287285
) -> Vec<InstructionAddressMap> {
288286
let mut iter = iter.into_iter();
289-
let mut cur = match iter.next() {
287+
let (mut cur_loc, mut cur_offset, mut cur_len) = match iter.next() {
290288
Some(i) => i,
291289
None => return Vec::new(),
292290
};
293291
let mut ret = Vec::new();
294-
for item in iter {
295-
if cur.code_offset + cur.code_len == item.code_offset && item.srcloc == cur.srcloc {
296-
cur.code_len += item.code_len;
297-
} else {
298-
ret.push(cur);
299-
cur = item;
292+
for (loc, offset, len) in iter {
293+
// If this instruction is adjacent to the previous and has the same
294+
// source location then we can "coalesce" it with the current
295+
// instruction.
296+
if cur_offset + cur_len == offset && loc == cur_loc {
297+
cur_len += len;
298+
continue;
300299
}
300+
301+
// Push an entry for the previous source item.
302+
ret.push(InstructionAddressMap {
303+
srcloc: cur_loc,
304+
code_offset: cur_offset,
305+
});
306+
// And push a "dummy" entry if necessary to cover the span of ranges,
307+
// if any, between the previous source offset and this one.
308+
if cur_offset + cur_len != offset {
309+
ret.push(InstructionAddressMap {
310+
srcloc: ir::SourceLoc::default(),
311+
code_offset: cur_offset + cur_len,
312+
});
313+
}
314+
// Update our current location to get extended later or pushed on at
315+
// the end.
316+
cur_loc = loc;
317+
cur_offset = offset;
318+
cur_len = len;
301319
}
302-
ret.push(cur);
320+
ret.push(InstructionAddressMap {
321+
srcloc: cur_loc,
322+
code_offset: cur_offset,
323+
});
324+
if cur_offset + cur_len != code_size {
325+
ret.push(InstructionAddressMap {
326+
srcloc: ir::SourceLoc::default(),
327+
code_offset: cur_offset + cur_len,
328+
});
329+
}
330+
303331
return ret;
304332
}
305333

@@ -406,7 +434,8 @@ impl Compiler for Cranelift {
406434
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
407435
})?;
408436

409-
let address_transform = get_function_address_map(&context, &input, code_buf.len(), isa);
437+
let address_transform =
438+
get_function_address_map(&context, &input, code_buf.len() as u32, isa);
410439

411440
let ranges = if tunables.debug_info {
412441
let ranges = context.build_value_labels_ranges(isa).map_err(|error| {

crates/debug/src/transform/address_transform.rs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ fn build_function_lookup(
102102
let mut ranges_index = BTreeMap::new();
103103
let mut current_range = Vec::new();
104104
let mut last_gen_inst_empty = false;
105-
for t in &ft.instructions {
105+
for (i, t) in ft.instructions.iter().enumerate() {
106106
if t.srcloc.is_default() {
107107
continue;
108108
}
@@ -111,8 +111,11 @@ fn build_function_lookup(
111111
assert_le!(fn_start, offset);
112112
assert_le!(offset, fn_end);
113113

114-
let inst_gen_start = t.code_offset;
115-
let inst_gen_end = t.code_offset + t.code_len;
114+
let inst_gen_start = t.code_offset as usize;
115+
let inst_gen_end = match ft.instructions.get(i + 1) {
116+
Some(i) => i.code_offset as usize,
117+
None => ft.body_len as usize,
118+
};
116119

117120
if last_wasm_pos > offset {
118121
// Start new range.
@@ -149,7 +152,7 @@ fn build_function_lookup(
149152
}
150153
last_wasm_pos = offset;
151154
}
152-
let last_gen_addr = ft.body_offset + ft.body_len;
155+
let last_gen_addr = ft.body_offset + ft.body_len as usize;
153156
ranges_index.insert(range_wasm_start, ranges.len());
154157
ranges.push(Range {
155158
wasm_start: range_wasm_start,
@@ -193,13 +196,13 @@ fn build_function_addr_map(
193196
for (_, f) in funcs {
194197
let ft = &f.address_map;
195198
let mut fn_map = Vec::new();
196-
for t in &ft.instructions {
199+
for t in ft.instructions.iter() {
197200
if t.srcloc.is_default() {
198201
continue;
199202
}
200203
let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
201204
fn_map.push(AddressMap {
202-
generated: t.code_offset,
205+
generated: t.code_offset as usize,
203206
wasm: offset,
204207
});
205208
}
@@ -213,7 +216,7 @@ fn build_function_addr_map(
213216

214217
map.push(FunctionMap {
215218
offset: ft.body_offset,
216-
len: ft.body_len,
219+
len: ft.body_len as usize,
217220
wasm_start: get_wasm_code_offset(ft.start_srcloc, code_section_offset),
218221
wasm_end: get_wasm_code_offset(ft.end_srcloc, code_section_offset),
219222
addresses: fn_map.into_boxed_slice(),
@@ -605,6 +608,7 @@ mod tests {
605608
use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
606609
use gimli::write::Address;
607610
use std::iter::FromIterator;
611+
use std::mem;
608612
use wasmtime_environ::entity::PrimaryMap;
609613
use wasmtime_environ::ir::SourceLoc;
610614
use wasmtime_environ::{CompiledFunction, WasmFileInfo};
@@ -626,14 +630,21 @@ mod tests {
626630
InstructionAddressMap {
627631
srcloc: SourceLoc::new(wasm_offset + 2),
628632
code_offset: 5,
629-
code_len: 3,
633+
},
634+
InstructionAddressMap {
635+
srcloc: SourceLoc::default(),
636+
code_offset: 8,
630637
},
631638
InstructionAddressMap {
632639
srcloc: SourceLoc::new(wasm_offset + 7),
633640
code_offset: 15,
634-
code_len: 8,
635641
},
636-
],
642+
InstructionAddressMap {
643+
srcloc: SourceLoc::default(),
644+
code_offset: 23,
645+
},
646+
]
647+
.into(),
637648
start_srcloc: SourceLoc::new(wasm_offset),
638649
end_srcloc: SourceLoc::new(wasm_offset + 10),
639650
body_offset: 0,
@@ -678,11 +689,16 @@ mod tests {
678689
fn test_build_function_lookup_two_ranges() {
679690
let mut input = create_simple_func(11);
680691
// append instruction with same srcloc as input.instructions[0]
681-
input.instructions.push(InstructionAddressMap {
692+
let mut list = Vec::from(mem::take(&mut input.instructions));
693+
list.push(InstructionAddressMap {
682694
srcloc: SourceLoc::new(11 + 2),
683695
code_offset: 23,
684-
code_len: 3,
685696
});
697+
list.push(InstructionAddressMap {
698+
srcloc: SourceLoc::default(),
699+
code_offset: 26,
700+
});
701+
input.instructions = list.into();
686702
let (start, end, lookup) = build_function_lookup(&input, 1);
687703
assert_eq!(10, start);
688704
assert_eq!(20, end);

crates/debug/src/transform/expression.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,14 +1145,21 @@ mod tests {
11451145
InstructionAddressMap {
11461146
srcloc: SourceLoc::new(code_section_offset + 12),
11471147
code_offset: 5,
1148-
code_len: 3,
1148+
},
1149+
InstructionAddressMap {
1150+
srcloc: SourceLoc::default(),
1151+
code_offset: 8,
11491152
},
11501153
InstructionAddressMap {
11511154
srcloc: SourceLoc::new(code_section_offset + 17),
11521155
code_offset: 15,
1153-
code_len: 8,
11541156
},
1155-
],
1157+
InstructionAddressMap {
1158+
srcloc: SourceLoc::default(),
1159+
code_offset: 23,
1160+
},
1161+
]
1162+
.into(),
11561163
start_srcloc: SourceLoc::new(code_section_offset + 10),
11571164
end_srcloc: SourceLoc::new(code_section_offset + 20),
11581165
body_offset: 0,

crates/environ/src/address_map.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,24 @@ use serde::{Deserialize, Serialize};
77
/// Single source location to generated address mapping.
88
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
99
pub struct InstructionAddressMap {
10-
/// Original source location.
10+
/// Where in the source this instruction comes from.
1111
pub srcloc: ir::SourceLoc,
1212

13-
/// Generated instructions offset.
14-
pub code_offset: usize,
15-
16-
/// Generated instructions length.
17-
pub code_len: usize,
13+
/// Offset from the start of the function's compiled code to where this
14+
/// instruction is located, or the region where it starts.
15+
pub code_offset: u32,
1816
}
1917

2018
/// Function and its instructions addresses mappings.
2119
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
2220
pub struct FunctionAddressMap {
23-
/// Instructions maps.
24-
/// The array is sorted by the InstructionAddressMap::code_offset field.
25-
pub instructions: Vec<InstructionAddressMap>,
21+
/// An array of data for the instructions in this function, indicating where
22+
/// each instruction maps back to in the original function.
23+
///
24+
/// This array is sorted least-to-greatest by the `code_offset` field.
25+
/// Additionally the span of each `InstructionAddressMap` is implicitly the
26+
/// gap between it and the next item in the array.
27+
pub instructions: Box<[InstructionAddressMap]>,
2628

2729
/// Function start source location (normally declaration).
2830
pub start_srcloc: ir::SourceLoc,
@@ -34,7 +36,7 @@ pub struct FunctionAddressMap {
3436
pub body_offset: usize,
3537

3638
/// Generated function body length.
37-
pub body_len: usize,
39+
pub body_len: u32,
3840
}
3941

4042
/// Memory definition offset in the VMContext structure.

crates/wasmtime/src/frame_info.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl GlobalFrameInfo {
6464
// Use our relative position from the start of the function to find the
6565
// machine instruction that corresponds to `pc`, which then allows us to
6666
// map that to a wasm original source location.
67-
let rel_pos = pc - func.start;
67+
let rel_pos = (pc - func.start) as u32;
6868
let pos = match func
6969
.instr_map
7070
.instructions
@@ -77,19 +77,8 @@ impl GlobalFrameInfo {
7777
// instructions cover `pc`.
7878
Err(0) => None,
7979

80-
// This would be at the `nth` slot, so check `n-1` to see if we're
81-
// part of that instruction. This happens due to the minus one when
82-
// this function is called form trap symbolication, where we don't
83-
// always get called with a `pc` that's an exact instruction
84-
// boundary.
85-
Err(n) => {
86-
let instr = &func.instr_map.instructions[n - 1];
87-
if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
88-
Some(n - 1)
89-
} else {
90-
None
91-
}
92-
}
80+
// This would be at the `nth` slot, so we're at the `n-1`th slot.
81+
Err(n) => Some(n - 1),
9382
};
9483

9584
// In debug mode for now assert that we found a mapping for `pc` within

0 commit comments

Comments
 (0)