LibJS: Deduplicate adjacent source map entries during codegen

Avoid emitting consecutive source map entries when they carry the
same source range. The bytecode offset for the previous entry remains
valid for later PCs because source lookup now uses the largest source
map entry whose offset is not greater than the program counter.

This keeps stack traces stable while allowing statement-sized runs of
bytecode to share one source map entry.
This commit is contained in:
Andreas Kling 2026-05-13 18:58:57 +02:00 committed by Andreas Kling
parent e926e86f8d
commit b6ac36c200
3 changed files with 80 additions and 48 deletions

View file

@ -405,19 +405,24 @@ Optional<SourceRange> Executable::source_range_at(size_t offset) const
{
if (offset >= bytecode.size())
return {};
auto* entry = binary_search(source_map, offset, nullptr, [](size_t needle, SourceMapEntry const& entry) -> int {
if (needle < entry.bytecode_offset)
return -1;
if (needle > entry.bytecode_offset)
return 1;
return 0;
});
if (!entry)
if (source_map.is_empty())
return {};
size_t low = 0;
size_t high = source_map.size();
while (low < high) {
auto middle = low + (high - low) / 2;
if (source_map[middle].bytecode_offset <= offset)
low = middle + 1;
else
high = middle;
}
if (low == 0)
return {};
auto& entry = source_map[low - 1];
return SourceRange {
.code = source_code,
.start = entry->source_record.start,
.end = entry->source_record.end,
.start = entry.source_record.start,
.end = entry.source_record.end,
};
}

View file

@ -759,7 +759,7 @@ impl Utf16String {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Position {
pub line: u32,
pub column: u32,

View file

@ -1645,6 +1645,15 @@ impl Generator {
let mut bytecode: Vec<u8> = Vec::with_capacity(offset);
let mut source_map: Vec<SourceMapEntry> = Vec::new();
let mut exception_handlers: Vec<ExceptionHandler> = Vec::new();
fn push_source_map_entry(source_map: &mut Vec<SourceMapEntry>, entry: SourceMapEntry) {
let should_push = source_map.last().is_none_or(|previous| {
previous.source_start != entry.source_start || previous.source_end != entry.source_end
});
if should_push {
source_map.push(entry);
}
}
// Track which blocks actually produced instructions.
let mut basic_block_start_offsets: Vec<usize> = Vec::with_capacity(num_blocks);
@ -1666,30 +1675,39 @@ impl Generator {
}
InstAction::Emit => {
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
});
push_source_map_entry(
&mut source_map,
SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
},
);
instruction.encode(self.strict, &mut bytecode);
}
InstAction::JumpToReturn(value) => {
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
});
push_source_map_entry(
&mut source_map,
SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
},
);
let replacement = Instruction::Return { value };
replacement.encode(self.strict, &mut bytecode);
}
InstAction::JumpToEnd(value) => {
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
});
push_source_map_entry(
&mut source_map,
SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
},
);
let replacement = Instruction::End { value };
replacement.encode(self.strict, &mut bytecode);
}
@ -1698,11 +1716,14 @@ impl Generator {
let target_block = target.0 as usize;
target.0 = u32_from_usize(block_offsets[target_block]);
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
});
push_source_map_entry(
&mut source_map,
SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
},
);
let replacement = Instruction::JumpFalse { condition, target };
replacement.encode(self.strict, &mut bytecode);
}
@ -1710,11 +1731,14 @@ impl Generator {
let target_block = target.0 as usize;
target.0 = u32_from_usize(block_offsets[target_block]);
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
});
push_source_map_entry(
&mut source_map,
SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: sm.source_start,
source_end: sm.source_end,
},
);
let replacement = Instruction::JumpTrue { condition, target };
replacement.encode(self.strict, &mut bytecode);
}
@ -1727,19 +1751,22 @@ impl Generator {
undef_rewritten.offset_index_by(number_of_registers + number_of_locals);
let end_instruction = Instruction::End { value: undef_rewritten };
let instruction_offset = bytecode.len();
source_map.push(SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: Position {
line: 0,
column: 0,
offset: 0,
push_source_map_entry(
&mut source_map,
SourceMapEntry {
bytecode_offset: u32_from_usize(instruction_offset),
source_start: Position {
line: 0,
column: 0,
offset: 0,
},
source_end: Position {
line: 0,
column: 0,
offset: 0,
},
},
source_end: Position {
line: 0,
column: 0,
offset: 0,
},
});
);
end_instruction.encode(self.strict, &mut bytecode);
}