Skip to content

Commit 2387999

Browse files
committed
Truncate IR dump basenames to respect NAME_MAX
Symbol-mangled Rust names can easily exceed the per-component filename length limit enforced by most filesystems (255 bytes on ext4/XFS/Btrfs, 143 on HFS+, 255 UTF-16 code units on NTFS), causing `File::create` in `write_ir_file` to fail with ENAMETOOLONG when `--emit=llvm-ir` is passed so cg_clif dumps CLIF/vcode per function. The previous code carried a `FIXME work around filename too long errors` marker. This change introduces `truncate_ir_basename`: names `\u{2264}` 200 bytes pass through unchanged; longer names are rewritten to `<first 160 bytes of stem>_h<16-hex-FNV-1a-64 of full stem>.<exts>`. The hash is computed over the original stem, so the transformation is deterministic and collision-resistant (any two distinct inputs map to distinct outputs with overwhelming probability). Extension suffix chains such as `.opt.clif`, `.unopt.clif`, and `.vcode` are preserved, so downstream tooling keying off the extension is unaffected. The function is invoked inside `write_ir_file` so that every caller (`write_clif_file` for CLIF dumps and the vcode dump in `base.rs`) benefits uniformly, and the FIXME at the top of `write_clif_file` is removed.
1 parent 203a324 commit 2387999

1 file changed

Lines changed: 36 additions & 2 deletions

File tree

src/pretty_clif.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ pub(crate) fn write_ir_file(
259259
res @ Err(_) => res.unwrap(),
260260
}
261261

262-
let clif_file_name = clif_output_dir.join(name);
262+
let clif_file_name = clif_output_dir.join(truncate_ir_basename(name).as_ref());
263263

264264
let res = std::fs::File::create(clif_file_name).and_then(|mut file| write(&mut file));
265265
if let Err(err) = res {
@@ -270,6 +270,41 @@ pub(crate) fn write_ir_file(
270270
}
271271
}
272272

273+
/// Ensure a generated IR filename fits in the filesystem's per-component
274+
/// length limit. Symbol-mangled names can exceed `NAME_MAX` (255 on ext4,
275+
/// 143 on HFS+), which causes `ENAMETOOLONG` on `File::create`.
276+
///
277+
/// Names ≤ 200 bytes pass through unchanged. Longer names are rewritten to
278+
/// `<first 160 bytes of stem>_h<16-hex-FNV-1a-64 of full stem>.<extensions>`.
279+
/// The hash is computed over the original stem so the transformation is
280+
/// deterministic and any two distinct inputs map to distinct outputs.
281+
fn truncate_ir_basename(name: &str) -> std::borrow::Cow<'_, str> {
282+
const MAX_BASENAME_LEN: usize = 200;
283+
if name.len() <= MAX_BASENAME_LEN {
284+
return std::borrow::Cow::Borrowed(name);
285+
}
286+
// Preserve extension suffix chain (e.g. ".opt.clif", ".unopt.clif", ".vcode").
287+
let (stem, ext) = match name.find('.') {
288+
Some(i) => (&name[..i], &name[i..]),
289+
None => (name, ""),
290+
};
291+
// FNV-1a 64-bit over the *full stem* (not the truncated prefix) so two
292+
// inputs sharing a long common prefix still hash apart.
293+
let mut hash: u64 = 0xcbf29ce484222325;
294+
for b in stem.as_bytes() {
295+
hash ^= *b as u64;
296+
hash = hash.wrapping_mul(0x100000001b3);
297+
}
298+
// Char-boundary-safe truncation of the stem prefix.
299+
let keep_bytes = 160.min(stem.len());
300+
let mut cut = keep_bytes;
301+
while cut > 0 && !stem.is_char_boundary(cut) {
302+
cut -= 1;
303+
}
304+
let prefix = &stem[..cut];
305+
std::borrow::Cow::Owned(format!("{prefix}_h{hash:016x}{ext}"))
306+
}
307+
273308
pub(crate) fn write_clif_file(
274309
output_filenames: &OutputFilenames,
275310
symbol_name: &str,
@@ -278,7 +313,6 @@ pub(crate) fn write_clif_file(
278313
func: &cranelift_codegen::ir::Function,
279314
mut clif_comments: &CommentWriter,
280315
) {
281-
// FIXME work around filename too long errors
282316
write_ir_file(output_filenames, &format!("{}.{}.clif", symbol_name, postfix), |file| {
283317
let mut clif = String::new();
284318
cranelift_codegen::write::decorate_function(&mut clif_comments, &mut clif, func).unwrap();

0 commit comments

Comments
 (0)