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
7 changes: 7 additions & 0 deletions changelog.d/8058-split-native-ptrtoint-constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Fix relocatable constants in split native codegen

Production-sized modules using split native codegen now materialize LLVM
`ptrtoint` constant operands for function and global references instead of
misparsing them as integer literals. Module-init wrappers can therefore pass
their `__init_body` function pointer through the runtime ABI while retaining a
real relocation in each independently emitted and partially linked object.
42 changes: 42 additions & 0 deletions crates/perry-codegen/src/dialect/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,48 @@ pub(super) fn constant<'ctx>(
}
bail!("reference to unknown global @{name}");
}
// LLVM constant expressions can appear anywhere an ordinary constant is
// accepted. Perry emits this exact form when a non-entry module passes its
// `__init_body` function pointer through the integer-valued runtime ABI:
//
// call void @js_run_module_init_catching(
// i64 ptrtoint (ptr @module__init_body to i64))
//
// The text backend has always delegated this to LLVM's assembler. The
// in-process reader used to feed the whole expression to `i128::parse`, so
// only split native codegen units failed, late in a large build, with
// `bad integer ptrtoint (...)`. Materialize the relocatable constant with
// LLVM's constant API; using a runtime instruction would be invalid here
// because this is an operand constant, not an SSA definition.
if let Some(body) = tok
.strip_prefix("ptrtoint (")
.and_then(|body| body.strip_suffix(')'))
{
let (source, destination) = body
.rsplit_once(" to ")
.ok_or_else(|| anyhow!("bad ptrtoint constant `{tok}`"))?;
let (source_ty, source_value) = ty_and_val(source)?;
let source_ty = basic_type(ctx, source_ty)?;
if !source_ty.is_pointer_type() {
bail!("ptrtoint source is not a pointer in `{tok}`");
}
let destination_ty = basic_type(ctx, destination.trim())?;
let BasicTypeEnum::IntType(destination_ty) = destination_ty else {
bail!("ptrtoint destination is not an integer in `{tok}`");
};
let BasicTypeEnum::IntType(expected_ty) = ty else {
bail!("ptrtoint constant used as non-integer {ty:?}");
};
if destination_ty != expected_ty {
bail!(
"ptrtoint destination type {} disagrees with operand type {}",
destination_ty.print_to_string(),
expected_ty.print_to_string()
);
}
let source = constant(ctx, module, source_ty, source_value)?.into_pointer_value();
return Ok(source.const_to_int(destination_ty).into());
}
Ok(match tok {
// The null of the OPERAND's type, not of address space 0: RS4GC emits
// `store ptr addrspace(1) null, ptr %slot`, and an addrspace(0) null
Expand Down
39 changes: 39 additions & 0 deletions crates/perry-codegen/src/native_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,45 @@ fn debug_dump(module: &Module<'_>, module_prefix: &str) {
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::module::LlModule;
use crate::types::{I64, VOID};

#[test]
fn split_units_emit_and_merge_init_body_pointer_constant() {
// Production webpack modules are large enough to use split native
// codegen. Every non-entry module's guard passes `__init_body` to the
// exception boundary as an i64 constant expression. Keep the callee
// and wrapper in separate units so this covers declaration lookup,
// relocatable ptrtoint construction, object emission, and the partial
// link -- a parse-only test would miss the production-graph failure
// tracked in #8057.
let mut module = LlModule::new(crate::codegen::default_target_triple());
module.declare_function("js_run_module_init_catching", VOID, &[I64]);

let body = module.define_function("fixture_js__init_body", VOID, vec![]);
body.create_block("entry").ret_void();

let wrapper = module.define_function("fixture_js__init", VOID, vec![]);
let entry = wrapper.create_block("entry");
entry.call_void(
"js_run_module_init_catching",
&[(I64, "ptrtoint (ptr @fixture_js__init_body to i64)")],
);
entry.ret_void();

let object =
compile_module_units_native(&mut module, 2, None, "split_ptrtoint_init_body_fixture")
.expect("split native units must emit and partial-link");
assert!(
!object.is_empty(),
"merged object must contain emitted code"
);
}
}

/// Differential harness: text-parsed arm vs natively-built arm, same LLVM,
/// same plan. The verdict is **emitted object bytes** — the C-API builder
/// constant-folds at construction (`zext i1 false`, `select i1 false, ...`),
Expand Down
Loading