Skip to content

Commit 18cea8c

Browse files
committed
LibJS: Outline slow-path-only AsmInt handlers
Add a handler-level @Cold annotation restricted to handlers whose entire implementation is one call_slow_path operation. Emit their dispatch entry symbols in the shared cold region on both native backends. Mark all 95 matching handlers cold. This reduces the contiguous x86-64 hot handler span from about 24 KiB to about 18 KiB.
1 parent 6065491 commit 18cea8c

5 files changed

Lines changed: 168 additions & 108 deletions

File tree

Libraries/LibJS/AsmIntGen/src/codegen_aarch64.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,6 @@ fn generate_handler(
499499
program: &Program,
500500
pinned: &PinnedConstants,
501501
) -> String {
502-
emit_handler_alignment(out, program.object_format);
503-
w!(out, "asm_handler_{}:", handler.name);
504-
// x21 = pb + pc is set by the dispatch sequence that branches here.
505-
// It is callee-saved, so it survives C++ calls within the handler.
506-
507502
let mut state = HandlerState::new();
508503

509504
let mut counter = state.unique_counter;
@@ -516,6 +511,26 @@ fn generate_handler(
516511
});
517512
state.unique_counter = counter;
518513

514+
if handler.is_cold {
515+
assert!(
516+
matches!(instructions.as_slice(), [instruction] if instruction.mnemonic == "call_slow_path"),
517+
"cold handler '{}' must consist solely of call_slow_path",
518+
handler.name
519+
);
520+
let mut cold = String::new();
521+
emit_handler_alignment(&mut cold, program.object_format);
522+
w!(cold, "asm_handler_{}:", handler.name);
523+
emit_instruction(&mut cold, &instructions[0], handler, program, &mut state, pinned);
524+
cold.push_str(&state.cold_blocks);
525+
w!(cold);
526+
return cold;
527+
}
528+
529+
emit_handler_alignment(out, program.object_format);
530+
w!(out, "asm_handler_{}:", handler.name);
531+
// x21 = pb + pc is set by the dispatch sequence that branches here.
532+
// It is callee-saved, so it survives C++ calls within the handler.
533+
519534
let (hot_instructions, cold_instructions) = outline_cold_blocks(&instructions)
520535
.unwrap_or_else(|error| panic!("invalid cold block in handler '{}': {error}", handler.name));
521536

@@ -2978,6 +2993,7 @@ mod tests {
29782993
handlers: vec![Handler {
29792994
name: "Call".into(),
29802995
size: Some(1),
2996+
is_cold: false,
29812997
instructions,
29822998
}],
29832999
op_layouts: HashMap::from([(
@@ -3027,4 +3043,17 @@ mod tests {
30273043
assert!(output.contains(" ldr x1, [sp, #120]"));
30283044
assert!(output.contains(" ldr x0, [sp, #112]"));
30293045
}
3046+
3047+
#[test]
3048+
fn emits_cold_handler_after_hot_region() {
3049+
let mut program = coff_program(vec![AsmInstruction {
3050+
mnemonic: "call_slow_path".into(),
3051+
operands: vec![Operand::Register("slow_path".into())],
3052+
}]);
3053+
program.handlers[0].is_cold = true;
3054+
3055+
let output = generate(&program);
3056+
3057+
assert!(output.find("asm_cold_handler_paths:").unwrap() < output.find("asm_handler_Call:").unwrap());
3058+
}
30303059
}

Libraries/LibJS/AsmIntGen/src/codegen_x86_64.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,6 @@ fn handler_size(handler: &Handler, program: &Program) -> u32 {
435435
}
436436

437437
fn generate_handler(out: &mut String, handler: &Handler, program: &Program, abi: X86_64Abi) -> String {
438-
w!(out, ".p2align 4");
439-
w!(out, "asm_handler_{}:", handler.name);
440-
441438
let mut state = HandlerState::new();
442439

443440
let mut counter = state.unique_counter;
@@ -450,6 +447,24 @@ fn generate_handler(out: &mut String, handler: &Handler, program: &Program, abi:
450447
});
451448
state.unique_counter = counter;
452449

450+
if handler.is_cold {
451+
assert!(
452+
matches!(instructions.as_slice(), [instruction] if instruction.mnemonic == "call_slow_path"),
453+
"cold handler '{}' must consist solely of call_slow_path",
454+
handler.name
455+
);
456+
let mut cold = String::new();
457+
w!(cold, ".p2align 4");
458+
w!(cold, "asm_handler_{}:", handler.name);
459+
emit_instruction(&mut cold, &instructions[0], handler, program, &mut state, abi);
460+
cold.push_str(&state.cold_blocks);
461+
w!(cold);
462+
return cold;
463+
}
464+
465+
w!(out, ".p2align 4");
466+
w!(out, "asm_handler_{}:", handler.name);
467+
453468
let (hot_instructions, cold_instructions) = outline_cold_blocks(&instructions)
454469
.unwrap_or_else(|error| panic!("invalid cold block in handler '{}': {error}", handler.name));
455470

@@ -1831,6 +1846,7 @@ mod tests {
18311846
Handler {
18321847
name: "Call".into(),
18331848
size: None,
1849+
is_cold: false,
18341850
instructions: Vec::new(),
18351851
}
18361852
}

Libraries/LibJS/AsmIntGen/src/parser.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub struct AsmInstruction {
3838
pub struct Handler {
3939
pub name: String,
4040
pub size: Option<u32>,
41+
pub is_cold: bool,
4142
pub instructions: Vec<AsmInstruction>,
4243
}
4344

@@ -116,8 +117,8 @@ pub fn parse(input: &str) -> Program {
116117
macros.insert(name, Macro { params, body });
117118
i += 1;
118119
} else if let Some(rest) = line.strip_prefix("handler ") {
119-
// handler Name, size=N
120-
let (name, size) = parse_handler_header(rest);
120+
// handler Name [@cold], size=N
121+
let (name, size, is_cold) = parse_handler_header(rest);
121122
i += 1;
122123
let mut instructions = Vec::new();
123124
while i < lines.len() {
@@ -133,6 +134,7 @@ pub fn parse(input: &str) -> Program {
133134
handlers.push(Handler {
134135
name,
135136
size,
137+
is_cold,
136138
instructions,
137139
});
138140
i += 1;
@@ -185,17 +187,20 @@ fn parse_macro_signature(s: &str) -> (String, Vec<String>) {
185187
}
186188
}
187189

188-
fn parse_handler_header(s: &str) -> (String, Option<u32>) {
190+
fn parse_handler_header(s: &str) -> (String, Option<u32>, bool) {
189191
let parts: Vec<&str> = s.split(',').collect();
190-
let name = parts[0].trim().to_string();
192+
let header = parts[0].trim();
193+
let (name, is_cold) = header
194+
.strip_suffix(" @cold")
195+
.map_or((header, false), |name| (name, true));
191196
let mut size = None;
192197
for part in &parts[1..] {
193198
let part = part.trim();
194199
if let Some(val) = part.strip_prefix("size=") {
195200
size = Some(val.trim().parse().unwrap());
196201
}
197202
}
198-
(name, size)
203+
(name.to_string(), size, is_cold)
199204
}
200205

201206
fn parse_asm_instructions(line: &str) -> Vec<AsmInstruction> {
@@ -377,4 +382,13 @@ mod tests {
377382
assert_eq!(instructions[1].mnemonic, "label");
378383
assert!(matches!(instructions[1].operands.as_slice(), [Operand::Label(label)] if label == ".slow"));
379384
}
385+
386+
#[test]
387+
fn parses_cold_handler_annotation() {
388+
let program = parse("handler Throw @cold\n call_slow_path slow\nend\n");
389+
let handler = &program.handlers[0];
390+
391+
assert_eq!(handler.name, "Throw");
392+
assert!(handler.is_cold);
393+
}
380394
}

Libraries/LibJS/AsmIntGen/src/shared.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ mod tests {
457457
Handler {
458458
name: "Call".into(),
459459
size: None,
460+
is_cold: false,
460461
instructions: Vec::new(),
461462
}
462463
}

0 commit comments

Comments
 (0)