Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/8071-split-native-root-workers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Propagate the producer-selected root backend into split LLVM codegen workers so native-root units cannot skip statepoint rewriting and compact GC-map emission (#8070).
13 changes: 9 additions & 4 deletions crates/perry-codegen/src/inprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,14 @@ pub fn compile_ll_to_object_inprocess(
effective_target: &str,
clang_style_args: &[String],
module_name: &str,
native_roots: bool,
) -> Result<Vec<u8>> {
let (opt, mcpu_native, explicit_cpu, mllvm, emit_asm) = interpret_plan_args(clang_style_args)?;
// Same guard as the external `opt` path (`linker::rs4gc_funclet_refusal`):
// rewrite-statepoints-for-gc crashes on WinEH funclet pads, and here the
// pass runs inside THIS process — the crash would take the compiler down
// with it, not just a child.
if crate::codegen::helpers::rs4gc_enabled() {
if native_roots {
if let Some(refusal) = crate::linker::rs4gc_funclet_refusal(ll_text) {
return Err(anyhow!(refusal));
}
Expand All @@ -202,6 +203,7 @@ pub fn compile_ll_to_object_inprocess(
explicit_cpu.as_deref(),
&mllvm,
emit_asm,
native_roots,
)
}

Expand Down Expand Up @@ -310,6 +312,7 @@ pub(crate) fn optimize_and_emit_module(
module: &inkwell::module::Module<'_>,
effective_target: &str,
clang_style_args: &[String],
native_roots: bool,
) -> Result<Vec<u8>> {
let (opt, mcpu_native, explicit_cpu, mllvm, emit_asm) = interpret_plan_args(clang_style_args)?;
optimize_and_emit(
Expand All @@ -320,6 +323,7 @@ pub(crate) fn optimize_and_emit_module(
explicit_cpu.as_deref(),
&mllvm,
emit_asm,
native_roots,
)
}

Expand All @@ -331,6 +335,7 @@ fn optimize_and_emit(
explicit_cpu: Option<&str>,
mllvm: &[String],
emit_asm: bool,
native_roots: bool,
) -> Result<Vec<u8>> {
global_init(mllvm);
announce();
Expand Down Expand Up @@ -398,7 +403,7 @@ fn optimize_and_emit(
// backend that can root an `invoke`, and since #7302 every call inside a
// `try` is one — 26% of the gap suite (128 of 479 files) contains a `try`,
// which the explicit bridge refuses outright (#7327/#7330).
if crate::codegen::helpers::rs4gc_enabled() {
if native_roots {
module
.run_passes(STATEPOINT_REWRITE_PASSES, &tm, PassBuilderOptions::create())
.map_err(|e| {
Expand Down Expand Up @@ -511,7 +516,7 @@ mod tests {
let emit = |ir: &str, name: &str| {
let context = Context::create();
let module = parse_ir_text(&context, ir, name).expect("fixture parses");
optimize_and_emit_module(&module, &target, &["-O3".into(), "-S".into()])
optimize_and_emit_module(&module, &target, &["-O3".into(), "-S".into()], true)
.expect("fixture emits assembly")
};
let text = emit(&text_ir, "constant_fold_text");
Expand All @@ -536,7 +541,7 @@ mod tests {
let _shadow = crate::codegen::helpers::NativeRootsPin::shadow();
(
rewritten,
optimize_and_emit_module(&module, &target, &["-O3".into(), "-S".into()])
optimize_and_emit_module(&module, &target, &["-O3".into(), "-S".into()], false)
.expect("rewritten fixture emits assembly"),
)
};
Expand Down
45 changes: 38 additions & 7 deletions crates/perry-codegen/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ fn build_clang_compile_plan(
ll_fn_count: usize,
max_fn_bytes: Option<usize>,
debug_symbols: bool,
compact_gc_map: bool,
) -> ClangCompilePlan {
let effective_target = target_triple
.map(|s| s.to_string())
Expand Down Expand Up @@ -435,7 +436,6 @@ fn build_clang_compile_plan(
// the statepoint backends emit a stack map, so only they pay for it, and
// the cost is small: `-S` takes the same time as `-c` (codegen is the
// cost, printing text is free) and assembling is ~0.02s per module.
let compact_gc_map = crate::codegen::helpers::native_stack_roots_enabled();
let asm_path = compact_gc_map.then(|| PathBuf::from(format!("{}.s", obj_path.display())));

let mut clang_args = vec![
Expand Down Expand Up @@ -542,8 +542,8 @@ pub(crate) fn rs4gc_funclet_refusal(ll_text: &str) -> Option<String> {
})
}

fn maybe_rs4gc_preprocess(ll_text: &str) -> Result<Option<String>> {
if !crate::codegen::helpers::rs4gc_enabled() {
fn maybe_rs4gc_preprocess(ll_text: &str, native_roots: bool) -> Result<Option<String>> {
if !native_roots {
return Ok(None);
}
if let Some(refusal) = rs4gc_funclet_refusal(ll_text) {
Expand Down Expand Up @@ -629,13 +629,23 @@ fn which_in_path(name: &str) -> Option<PathBuf> {
}

pub fn compile_ll_to_object(ll_text: &str, target_triple: Option<&str>) -> Result<Vec<u8>> {
let rs4gc_ll = maybe_rs4gc_preprocess(ll_text)?;
let native_roots = crate::codegen::helpers::native_stack_roots_enabled();
compile_ll_to_object_with_native_roots(ll_text, target_triple, native_roots)
}

fn compile_ll_to_object_with_native_roots(
ll_text: &str,
target_triple: Option<&str>,
native_roots: bool,
) -> Result<Vec<u8>> {
let rs4gc_ll = maybe_rs4gc_preprocess(ll_text, native_roots)?;
let ll_text: &str = rs4gc_ll.as_deref().unwrap_or(ll_text);
compile_ll_to_object_in(
&env::temp_dir(),
ll_text,
target_triple,
TempFilePolicy::from_env(),
native_roots,
)
}

Expand Down Expand Up @@ -671,6 +681,7 @@ pub(crate) fn native_plan_args(
est_ll_bytes: usize,
ll_fn_count: usize,
max_fn_bytes: usize,
native_roots: bool,
) -> (String, Vec<String>) {
let plan = build_clang_compile_plan(
PathBuf::from("(in-process)"),
Expand All @@ -681,6 +692,7 @@ pub(crate) fn native_plan_args(
ll_fn_count,
Some(max_fn_bytes),
env::var_os("PERRY_DEBUG_SYMBOLS").is_some(),
native_roots,
);
(plan.effective_target, plan.clang_args)
}
Expand Down Expand Up @@ -784,6 +796,7 @@ fn compile_ll_inprocess_in(
ll_text: &str,
target_triple: Option<&str>,
policy: TempFilePolicy,
native_roots: bool,
) -> Result<Vec<u8>> {
let (paths, _pid, _nonce) = llvm_temp_paths(tmp_dir, ll_text);
// Same decision inputs as the clang path — opt level (#4880 fallback
Expand All @@ -798,6 +811,7 @@ fn compile_ll_inprocess_in(
count_ll_functions(ll_text),
None,
policy.debug_symbols,
native_roots,
);
// #7131 parity: the module identifier is the content-addressed basename,
// the only name that can reach the object bytes.
Expand All @@ -824,6 +838,7 @@ fn compile_ll_inprocess_in(
&plan.effective_target,
&plan.clang_args,
&module_name,
native_roots,
) {
// The statepoint backends ask for `-S`, because #7314's compact-map
// rewriter rewrites `.llvm_stackmaps` at ASSEMBLY time — that is where
Expand Down Expand Up @@ -910,6 +925,7 @@ fn compile_ll_inprocess_in(
_ll_text: &str,
_target_triple: Option<&str>,
_policy: TempFilePolicy,
_native_roots: bool,
) -> Result<Vec<u8>> {
// Fail loudly rather than silently falling back: an A/B arm that asked
// for the in-process backend must never be served the text path.
Expand All @@ -927,9 +943,10 @@ fn compile_ll_to_object_in(
ll_text: &str,
target_triple: Option<&str>,
policy: TempFilePolicy,
native_roots: bool,
) -> Result<Vec<u8>> {
if inprocess_requested() {
return compile_ll_inprocess_in(tmp_dir, ll_text, target_triple, policy);
return compile_ll_inprocess_in(tmp_dir, ll_text, target_triple, policy, native_roots);
}
// Validate the toolchain before creating the potentially large `.ll`
// scratch file. Unsupported clang releases should fail without leaving
Expand Down Expand Up @@ -979,6 +996,7 @@ fn compile_ll_to_object_in(
count_ll_functions(ll_text),
None,
policy.debug_symbols,
native_roots,
);

// Pre-flight probe: capture clang's default Target: line once per process,
Expand Down Expand Up @@ -1133,10 +1151,19 @@ pub fn compile_units_to_object(units: &[String], target_triple: Option<&str>) ->
})
.min(units.len());

// The root backend is a per-module decision stored on the producer thread.
// Unit workers must receive it explicitly: fresh threads start with the
// thread-local target/override cells unset and would otherwise silently
// compile precise-root IR without RS4GC or a compact map (#8070).
let native_roots = crate::codegen::helpers::native_stack_roots_enabled();
let mut compiled: Vec<Option<Result<Vec<u8>>>> = (0..units.len()).map(|_| None).collect();
if jobs <= 1 {
for (i, unit) in units.iter().enumerate() {
compiled[i] = Some(compile_ll_to_object(unit, target_triple));
compiled[i] = Some(compile_ll_to_object_with_native_roots(
unit,
target_triple,
native_roots,
));
}
} else {
let slots: Vec<std::sync::Mutex<Option<Result<Vec<u8>>>>> = (0..units.len())
Expand All @@ -1150,7 +1177,11 @@ pub fn compile_units_to_object(units: &[String], target_triple: Option<&str>) ->
if i >= units.len() {
break;
}
let out = compile_ll_to_object(&units[i], target_triple);
let out = compile_ll_to_object_with_native_roots(
&units[i],
target_triple,
native_roots,
);
*slots[i].lock().expect("codegen-unit slot poisoned") = Some(out);
});
}
Expand Down
10 changes: 5 additions & 5 deletions crates/perry-codegen/src/linker_temp_lifecycle_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ fn successful_compile_leaves_nothing_behind() {
};

for nth in 0..3 {
let bytes = compile_ll_to_object_in(&root, &test_ir(nth), None, CLEAN)
let bytes = compile_ll_to_object_in(&root, &test_ir(nth), None, CLEAN, false)
.unwrap_or_else(|e| panic!("compile {nth} failed: {e:#}"));
assert!(!bytes.is_empty(), "compile {nth} produced no object bytes");
assert_eq!(
Expand Down Expand Up @@ -162,7 +162,7 @@ fn concurrent_compiles_of_identical_ir_both_succeed_and_leave_nothing() {
.map(|_| {
let root = root.clone();
let ir = ir.clone();
s.spawn(move || compile_ll_to_object_in(&root, &ir, None, CLEAN))
s.spawn(move || compile_ll_to_object_in(&root, &ir, None, CLEAN, false))
})
.collect();
handles.into_iter().map(|h| h.join().unwrap()).collect()
Expand Down Expand Up @@ -196,7 +196,7 @@ fn failed_compile_keeps_the_ll_for_diagnosis() {
return;
};

let err = compile_ll_to_object_in(&root, "this is not LLVM IR\n", None, CLEAN)
let err = compile_ll_to_object_in(&root, "this is not LLVM IR\n", None, CLEAN, false)
.expect_err("clang must reject non-IR input");
let message = format!("{err:#}");
assert!(
Expand Down Expand Up @@ -251,7 +251,7 @@ fn keep_ir_retains_the_whole_scratch_dir() {
keep: true,
debug_symbols: false,
};
compile_ll_to_object_in(&root, &test_ir(7), None, policy).expect("compile failed");
compile_ll_to_object_in(&root, &test_ir(7), None, policy, false).expect("compile failed");

let left = entries(&root);
assert_eq!(left.len(), 1, "expected one kept scratch dir: {left:?}");
Expand Down Expand Up @@ -283,7 +283,7 @@ fn debug_symbols_do_not_change_the_temp_file_lifetime() {
debug_symbols: true,
};
for nth in 0..2 {
compile_ll_to_object_in(&root, &test_ir(9 + nth), None, policy)
compile_ll_to_object_in(&root, &test_ir(9 + nth), None, policy, false)
.unwrap_or_else(|e| panic!("-g compile {nth} failed: {e:#}"));
assert_eq!(
entries(&root),
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-codegen/src/linker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ fn compile_plan_records_effective_target_and_native_tuning() {
0,
None,
false,
false,
);
assert!(plan.clang_args.contains(&"-fno-math-errno".to_string()));
// Small module → optimized at -O3 (#4880).
Expand Down Expand Up @@ -202,6 +203,7 @@ fn compile_plan_size_optimizes_oversized_many_function_module() {
many_funcs,
None,
false,
false,
);
assert!(plan.clang_args.contains(&"-Os".to_string()));
assert!(!plan.clang_args.contains(&"-O3".to_string()));
Expand All @@ -223,6 +225,7 @@ fn compile_plan_keeps_o0_for_oversized_giant_function_monolith() {
2, // ~3 MB/fn — far above the density cap
None,
false,
false,
);
assert!(plan.clang_args.contains(&"-O0".to_string()));
assert!(!plan.clang_args.contains(&"-O3".to_string()));
Expand All @@ -240,6 +243,7 @@ fn compile_plan_skips_native_tuning_for_explicit_target() {
0,
None,
false,
false,
);
assert_eq!(plan.effective_target, "x86_64-unknown-linux-gnu");
assert_eq!(plan.native_tuning_arg, None);
Expand Down Expand Up @@ -334,6 +338,7 @@ fn compile_plan_metadata_json_contains_object_source() {
0,
None,
false,
false,
);
write_compile_plan_metadata(&plan, &temp).unwrap();
let text = fs::read_to_string(&temp).unwrap();
Expand Down
Loading
Loading