From 23879997b43981bb1c9e8896dddf7d1396d6b648 Mon Sep 17 00:00:00 2001 From: npiesco Date: Sun, 19 Apr 2026 15:53:36 -0400 Subject: [PATCH] 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 `_h<16-hex-FNV-1a-64 of full stem>.`. 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. --- src/pretty_clif.rs | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/pretty_clif.rs b/src/pretty_clif.rs index 918fe3d2a3..8e60bf26a4 100644 --- a/src/pretty_clif.rs +++ b/src/pretty_clif.rs @@ -259,7 +259,7 @@ pub(crate) fn write_ir_file( res @ Err(_) => res.unwrap(), } - let clif_file_name = clif_output_dir.join(name); + let clif_file_name = clif_output_dir.join(truncate_ir_basename(name).as_ref()); let res = std::fs::File::create(clif_file_name).and_then(|mut file| write(&mut file)); if let Err(err) = res { @@ -270,6 +270,41 @@ pub(crate) fn write_ir_file( } } +/// Ensure a generated IR filename fits in the filesystem's per-component +/// length limit. Symbol-mangled names can exceed `NAME_MAX` (255 on ext4, +/// 143 on HFS+), which causes `ENAMETOOLONG` on `File::create`. +/// +/// Names ≤ 200 bytes pass through unchanged. Longer names are rewritten to +/// `_h<16-hex-FNV-1a-64 of full stem>.`. +/// The hash is computed over the original stem so the transformation is +/// deterministic and any two distinct inputs map to distinct outputs. +fn truncate_ir_basename(name: &str) -> std::borrow::Cow<'_, str> { + const MAX_BASENAME_LEN: usize = 200; + if name.len() <= MAX_BASENAME_LEN { + return std::borrow::Cow::Borrowed(name); + } + // Preserve extension suffix chain (e.g. ".opt.clif", ".unopt.clif", ".vcode"). + let (stem, ext) = match name.find('.') { + Some(i) => (&name[..i], &name[i..]), + None => (name, ""), + }; + // FNV-1a 64-bit over the *full stem* (not the truncated prefix) so two + // inputs sharing a long common prefix still hash apart. + let mut hash: u64 = 0xcbf29ce484222325; + for b in stem.as_bytes() { + hash ^= *b as u64; + hash = hash.wrapping_mul(0x100000001b3); + } + // Char-boundary-safe truncation of the stem prefix. + let keep_bytes = 160.min(stem.len()); + let mut cut = keep_bytes; + while cut > 0 && !stem.is_char_boundary(cut) { + cut -= 1; + } + let prefix = &stem[..cut]; + std::borrow::Cow::Owned(format!("{prefix}_h{hash:016x}{ext}")) +} + pub(crate) fn write_clif_file( output_filenames: &OutputFilenames, symbol_name: &str, @@ -278,7 +313,6 @@ pub(crate) fn write_clif_file( func: &cranelift_codegen::ir::Function, mut clif_comments: &CommentWriter, ) { - // FIXME work around filename too long errors write_ir_file(output_filenames, &format!("{}.{}.clif", symbol_name, postfix), |file| { let mut clif = String::new(); cranelift_codegen::write::decorate_function(&mut clif_comments, &mut clif, func).unwrap();