fortrangoingonforty/armfortas / 39ac9c3

Browse files

Fix fortsh link contract

Authored by espadonne
SHA
39ac9c35387a869d5e68b6b72766df1b422cc551
Parents
1dd8886
Tree
ffe5328

7 changed files

StatusFile+-
M src/driver/mod.rs 0 1
M src/ir/lower.rs 28 1
M src/testing.rs 0 1
M tests/artifact_audit_29_10.rs 45 11
M tests/cli_driver.rs 152 0
M tests/i128_cross_object.rs 37 6
M tests/sprint29_audit_realworld.rs 40 6
src/driver/mod.rsmodified
@@ -1413,7 +1413,6 @@ fn link_inputs(inputs: &[PathBuf], output: &Path, opts: &Options) -> Result<(),
14131413
     args.extend([
14141414
         rt_path,
14151415
         "-lSystem".into(),
1416
-        "-no_uuid".into(),
14171416
         "-syslibroot".into(),
14181417
         sysroot,
14191418
         "-e".into(),
src/ir/lower.rsmodified
@@ -402,6 +402,7 @@ pub fn lower_file(
402402
             &no_host_param_consts,
403403
             &no_host,
404404
             None,
405
+            None,
405406
             &alloc_return_funcs,
406407
             &optional_params,
407408
             &descriptor_params,
@@ -2201,6 +2202,7 @@ fn lower_unit(
22012202
     // and character-kind for each host-associated variable the
22022203
     // closure-passing ABI threads in as a hidden pointer param.
22032204
     host_decls: &[crate::ast::decl::SpannedDecl],
2205
+    host_link_name: Option<&str>,
22042206
     host_module: Option<&str>,
22052207
     alloc_return_funcs: &HashSet<String>,
22062208
     optional_params: &HashMap<String, Vec<bool>>,
@@ -2302,6 +2304,7 @@ fn lower_unit(
23022304
                     &combined_uses,
23032305
                     &visible_param_consts,
23042306
                     &child_host_decls,
2307
+                    Some(body_fname.as_str()),
23052308
                     host_module,
23062309
                     alloc_return_funcs,
23072310
                     optional_params,
@@ -2328,6 +2331,7 @@ fn lower_unit(
23282331
             let func_name = lowered_procedure_symbol_name(
23292332
                 name,
23302333
                 bind.as_ref(),
2334
+                host_link_name,
23312335
                 host_module,
23322336
                 internal_only,
23332337
                 internal_funcs,
@@ -2604,6 +2608,7 @@ fn lower_unit(
26042608
                     &combined_uses,
26052609
                     &visible_param_consts,
26062610
                     &child_host_decls,
2611
+                    Some(func_name.as_str()),
26072612
                     host_module,
26082613
                     alloc_return_funcs,
26092614
                     optional_params,
@@ -2632,6 +2637,7 @@ fn lower_unit(
26322637
             let func_name = lowered_procedure_symbol_name(
26332638
                 name,
26342639
                 bind.as_ref(),
2640
+                host_link_name,
26352641
                 host_module,
26362642
                 internal_only,
26372643
                 internal_funcs,
@@ -3095,6 +3101,7 @@ fn lower_unit(
30953101
                     &combined_uses,
30963102
                     &visible_param_consts,
30973103
                     &child_host_decls,
3104
+                    Some(func_name.as_str()),
30983105
                     host_module,
30993106
                     alloc_return_funcs,
31003107
                     optional_params,
@@ -3139,6 +3146,7 @@ fn lower_unit(
31393146
                     &combined_uses,
31403147
                     &visible_param_consts,
31413148
                     &no_host_decls,
3149
+                    None,
31423150
                     module_name,
31433151
                     alloc_return_funcs,
31443152
                     optional_params,
@@ -3182,6 +3190,7 @@ fn lower_unit(
31823190
                     &combined_uses,
31833191
                     &visible_param_consts,
31843192
                     &no_host_decls,
3193
+                    None,
31853194
                     Some(parent.as_str()),
31863195
                     alloc_return_funcs,
31873196
                     optional_params,
@@ -7129,9 +7138,23 @@ fn module_procedure_symbol_name(module_name: &str, proc_name: &str) -> String {
71297138
     format!("afs_modproc_{}_{}", module_name.to_lowercase(), proc_name)
71307139
 }
71317140
 
7141
+fn sanitize_internal_host_symbol(host_link_name: &str) -> String {
7142
+    host_link_name
7143
+        .chars()
7144
+        .map(|ch| {
7145
+            if ch.is_ascii_alphanumeric() {
7146
+                ch.to_ascii_lowercase()
7147
+            } else {
7148
+                '_'
7149
+            }
7150
+        })
7151
+        .collect()
7152
+}
7153
+
71327154
 fn lowered_procedure_symbol_name(
71337155
     name: &str,
71347156
     bind: Option<&crate::ast::unit::BindInfo>,
7157
+    host_link_name: Option<&str>,
71357158
     host_module: Option<&str>,
71367159
     internal_only: bool,
71377160
     internal_funcs: &HashMap<String, u32>,
@@ -7147,7 +7170,11 @@ fn lowered_procedure_symbol_name(
71477170
     }
71487171
     if internal_only {
71497172
         if let Some(idx) = internal_funcs.get(&name.to_lowercase()) {
7150
-            return format!("afs_internal_{}", idx);
7173
+            let host_prefix = host_link_name
7174
+                .map(sanitize_internal_host_symbol)
7175
+                .filter(|prefix| !prefix.is_empty())
7176
+                .unwrap_or_else(|| "local".into());
7177
+            return format!("afs_internal_{}_{}", host_prefix, idx);
71517178
         }
71527179
     }
71537180
     if let Some(module_name) = host_module {
src/testing.rsmodified
@@ -893,7 +893,6 @@ fn link_with_runtime(obj: &Path, output: &Path) -> Result<(), String> {
893893
             obj.to_str().unwrap(),
894894
             &rt_path,
895895
             "-lSystem",
896
-            "-no_uuid",
897896
             "-syslibroot",
898897
             &sysroot,
899898
             "-e",
tests/artifact_audit_29_10.rsmodified
@@ -63,6 +63,38 @@ fn tool_output(tool: &str, args: &[&str]) -> String {
6363
     String::from_utf8_lossy(&output.stdout).into_owned()
6464
 }
6565
 
66
+fn normalize_lc_uuid(mut bytes: Vec<u8>) -> Vec<u8> {
67
+    const MH_MAGIC_64: u32 = 0xfeedfacf;
68
+    const LC_UUID: u32 = 0x1b;
69
+    const MACH_HEADER_64_SIZE: usize = 32;
70
+
71
+    if bytes.len() < MACH_HEADER_64_SIZE {
72
+        return bytes;
73
+    }
74
+    let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
75
+    if magic != MH_MAGIC_64 {
76
+        return bytes;
77
+    }
78
+    let ncmds = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize;
79
+    let mut offset = MACH_HEADER_64_SIZE;
80
+    for _ in 0..ncmds {
81
+        if offset + 8 > bytes.len() {
82
+            break;
83
+        }
84
+        let cmd = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
85
+        let cmdsize = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap());
86
+        let cmdsize = cmdsize as usize;
87
+        if cmdsize < 8 || offset + cmdsize > bytes.len() {
88
+            break;
89
+        }
90
+        if cmd == LC_UUID && cmdsize >= 24 {
91
+            bytes[offset + 8..offset + 24].fill(0);
92
+        }
93
+        offset += cmdsize;
94
+    }
95
+    bytes
96
+}
97
+
6698
 fn opt_name(opt: OptLevel) -> &'static str {
6799
     match opt {
68100
         OptLevel::O0 => "-O0",
@@ -149,7 +181,7 @@ fn object_snapshot_is_deterministic_with_module_globals_across_opt_levels() {
149181
 }
150182
 
151183
 #[test]
152
-fn linked_binary_is_deterministic_for_same_output_path_and_has_no_uuid() {
184
+fn linked_binary_is_deterministic_for_same_output_path_and_has_uuid() {
153185
     let compiler = find_compiler();
154186
     let source = fixture("module_init.f90");
155187
     let stem = source.file_stem().unwrap().to_str().unwrap();
@@ -165,19 +197,20 @@ fn linked_binary_is_deterministic_for_same_output_path_and_has_no_uuid() {
165197
         compile_binary(&compiler, &source, opt, &bin_path);
166198
         let load_commands = tool_output("otool", &["-l", bin_path.to_str().unwrap()]);
167199
         assert!(
168
-            !load_commands.contains("LC_UUID"),
169
-            "linked binary at {} should omit LC_UUID for reproducible builds:\n{}",
200
+            load_commands.contains("LC_UUID"),
201
+            "linked binary at {} should carry LC_UUID so dyld accepts it:\n{}",
170202
             opt,
171203
             load_commands
172204
         );
173
-        let first = fs::read(&bin_path).expect("cannot read first binary image");
205
+        let first = normalize_lc_uuid(fs::read(&bin_path).expect("cannot read first binary image"));
174206
 
175207
         compile_binary(&compiler, &source, opt, &bin_path);
176
-        let second = fs::read(&bin_path).expect("cannot read second binary image");
208
+        let second =
209
+            normalize_lc_uuid(fs::read(&bin_path).expect("cannot read second binary image"));
177210
 
178211
         assert_eq!(
179212
             first, second,
180
-            "linked binary should be byte-identical when rebuilt at the same output path ({})",
213
+            "linked binary should stay deterministic modulo LC_UUID when rebuilt at the same output path ({})",
181214
             opt
182215
         );
183216
 
@@ -202,19 +235,20 @@ fn linked_runtime_heavy_binary_is_deterministic_for_section_reads() {
202235
         compile_binary(&compiler, &source, opt, &bin_path);
203236
         let load_commands = tool_output("otool", &["-l", bin_path.to_str().unwrap()]);
204237
         assert!(
205
-            !load_commands.contains("LC_UUID"),
206
-            "runtime-heavy linked binary at {} should omit LC_UUID:\n{}",
238
+            load_commands.contains("LC_UUID"),
239
+            "runtime-heavy linked binary at {} should carry LC_UUID:\n{}",
207240
             opt,
208241
             load_commands
209242
         );
210
-        let first = fs::read(&bin_path).expect("cannot read first binary image");
243
+        let first = normalize_lc_uuid(fs::read(&bin_path).expect("cannot read first binary image"));
211244
 
212245
         compile_binary(&compiler, &source, opt, &bin_path);
213
-        let second = fs::read(&bin_path).expect("cannot read second binary image");
246
+        let second =
247
+            normalize_lc_uuid(fs::read(&bin_path).expect("cannot read second binary image"));
214248
 
215249
         assert_eq!(
216250
             first, second,
217
-            "runtime-heavy linked binary should be byte-identical when rebuilt at the same output path ({})",
251
+            "runtime-heavy linked binary should stay deterministic modulo LC_UUID when rebuilt at the same output path ({})",
218252
             opt
219253
         );
220254
 
tests/cli_driver.rsmodified
@@ -3110,6 +3110,57 @@ fn use_renamed_procedure_call_uses_remote_symbol() {
31103110
     let _ = std::fs::remove_dir_all(&dir);
31113111
 }
31123112
 
3113
+#[test]
3114
+fn linked_binary_carries_uuid_and_launches() {
3115
+    let dir = unique_dir("linked_binary_uuid");
3116
+    let src = write_program_in(
3117
+        &dir,
3118
+        "hello.f90",
3119
+        "program hello\n  print *, 42\nend program hello\n",
3120
+    );
3121
+
3122
+    let exe = dir.join("hello.bin");
3123
+    let compile = Command::new(compiler("armfortas"))
3124
+        .current_dir(&dir)
3125
+        .args([src.to_str().unwrap(), "-o", exe.to_str().unwrap()])
3126
+        .output()
3127
+        .expect("hello compile spawn failed");
3128
+    assert!(
3129
+        compile.status.success(),
3130
+        "linked hello should compile: {}",
3131
+        String::from_utf8_lossy(&compile.stderr)
3132
+    );
3133
+
3134
+    let otool = Command::new("otool")
3135
+        .args(["-l", exe.to_str().unwrap()])
3136
+        .output()
3137
+        .expect("otool spawn failed");
3138
+    assert!(
3139
+        otool.status.success(),
3140
+        "otool should inspect linked hello: {}",
3141
+        String::from_utf8_lossy(&otool.stderr)
3142
+    );
3143
+    let load_commands = String::from_utf8_lossy(&otool.stdout);
3144
+    assert!(
3145
+        load_commands.contains("LC_UUID"),
3146
+        "linked hello should carry LC_UUID so dyld accepts it:\n{}",
3147
+        load_commands
3148
+    );
3149
+
3150
+    let run = Command::new(&exe)
3151
+        .current_dir(&dir)
3152
+        .output()
3153
+        .expect("hello run spawn failed");
3154
+    assert!(
3155
+        run.status.success(),
3156
+        "linked hello should launch successfully:\nstdout:\n{}\nstderr:\n{}",
3157
+        String::from_utf8_lossy(&run.stdout),
3158
+        String::from_utf8_lossy(&run.stderr)
3159
+    );
3160
+
3161
+    let _ = std::fs::remove_dir_all(&dir);
3162
+}
3163
+
31133164
 #[test]
31143165
 fn same_named_module_helpers_link_without_colliding() {
31153166
     let dir = unique_dir("module_helper_link_names");
@@ -3211,6 +3262,107 @@ fn same_named_module_helpers_link_without_colliding() {
32113262
     let _ = std::fs::remove_dir_all(&dir);
32123263
 }
32133264
 
3265
+#[test]
3266
+fn contained_helpers_link_without_cross_object_internal_symbol_collisions() {
3267
+    let dir = unique_dir("contained_helper_link_names");
3268
+    let mod_a = write_program_in(
3269
+        &dir,
3270
+        "mod_a.f90",
3271
+        "module mod_a\ncontains\n  subroutine run_a()\n    implicit none\n    call helper()\n  contains\n    subroutine helper()\n      print *, 11\n    end subroutine helper\n  end subroutine run_a\nend module mod_a\n",
3272
+    );
3273
+    let mod_b = write_program_in(
3274
+        &dir,
3275
+        "mod_b.f90",
3276
+        "module mod_b\ncontains\n  subroutine run_b()\n    implicit none\n    call helper()\n  contains\n    subroutine helper()\n      print *, 22\n    end subroutine helper\n  end subroutine run_b\nend module mod_b\n",
3277
+    );
3278
+    let main_src = write_program_in(
3279
+        &dir,
3280
+        "main.f90",
3281
+        "program p\n  use mod_a, only: run_a\n  use mod_b, only: run_b\n  call run_a()\n  call run_b()\nend program p\n",
3282
+    );
3283
+
3284
+    let mod_a_obj = dir.join("mod_a.o");
3285
+    let compile_a = Command::new(compiler("armfortas"))
3286
+        .current_dir(&dir)
3287
+        .args([
3288
+            "-c",
3289
+            "-J",
3290
+            dir.to_str().unwrap(),
3291
+            mod_a.to_str().unwrap(),
3292
+            "-o",
3293
+            mod_a_obj.to_str().unwrap(),
3294
+        ])
3295
+        .output()
3296
+        .expect("mod_a compile spawn failed");
3297
+    assert!(
3298
+        compile_a.status.success(),
3299
+        "mod_a should compile: {}",
3300
+        String::from_utf8_lossy(&compile_a.stderr)
3301
+    );
3302
+
3303
+    let mod_b_obj = dir.join("mod_b.o");
3304
+    let compile_b = Command::new(compiler("armfortas"))
3305
+        .current_dir(&dir)
3306
+        .args([
3307
+            "-c",
3308
+            "-I",
3309
+            dir.to_str().unwrap(),
3310
+            "-J",
3311
+            dir.to_str().unwrap(),
3312
+            mod_b.to_str().unwrap(),
3313
+            "-o",
3314
+            mod_b_obj.to_str().unwrap(),
3315
+        ])
3316
+        .output()
3317
+        .expect("mod_b compile spawn failed");
3318
+    assert!(
3319
+        compile_b.status.success(),
3320
+        "mod_b should compile: {}",
3321
+        String::from_utf8_lossy(&compile_b.stderr)
3322
+    );
3323
+
3324
+    let main_obj = dir.join("main.o");
3325
+    let compile_main = Command::new(compiler("armfortas"))
3326
+        .current_dir(&dir)
3327
+        .args([
3328
+            "-c",
3329
+            "-I",
3330
+            dir.to_str().unwrap(),
3331
+            "-J",
3332
+            dir.to_str().unwrap(),
3333
+            main_src.to_str().unwrap(),
3334
+            "-o",
3335
+            main_obj.to_str().unwrap(),
3336
+        ])
3337
+        .output()
3338
+        .expect("main compile spawn failed");
3339
+    assert!(
3340
+        compile_main.status.success(),
3341
+        "main should compile: {}",
3342
+        String::from_utf8_lossy(&compile_main.stderr)
3343
+    );
3344
+
3345
+    let exe = dir.join("contained_helpers.bin");
3346
+    let link = Command::new(compiler("armfortas"))
3347
+        .current_dir(&dir)
3348
+        .args([
3349
+            mod_a_obj.to_str().unwrap(),
3350
+            mod_b_obj.to_str().unwrap(),
3351
+            main_obj.to_str().unwrap(),
3352
+            "-o",
3353
+            exe.to_str().unwrap(),
3354
+        ])
3355
+        .output()
3356
+        .expect("contained helper link spawn failed");
3357
+    assert!(
3358
+        link.status.success(),
3359
+        "contained helpers in different objects should link cleanly: {}",
3360
+        String::from_utf8_lossy(&link.stderr)
3361
+    );
3362
+
3363
+    let _ = std::fs::remove_dir_all(&dir);
3364
+}
3365
+
32143366
 #[test]
32153367
 fn program_internal_char_helper_assignment_uses_internal_symbol() {
32163368
     let dir = unique_dir("program_internal_char_helper");
tests/i128_cross_object.rsmodified
@@ -159,7 +159,6 @@ fn link_objects(objects: &[&Path], output: &Path) {
159159
     args.push(rt_path.to_string_lossy().into_owned());
160160
     args.extend([
161161
         "-lSystem".into(),
162
-        "-no_uuid".into(),
163162
         "-syslibroot".into(),
164163
         sysroot,
165164
         "-e".into(),
@@ -204,6 +203,38 @@ fn tool_output(tool: &str, args: &[&str]) -> String {
204203
     String::from_utf8_lossy(&output.stdout).into_owned()
205204
 }
206205
 
206
+fn normalize_lc_uuid(mut bytes: Vec<u8>) -> Vec<u8> {
207
+    const MH_MAGIC_64: u32 = 0xfeedfacf;
208
+    const LC_UUID: u32 = 0x1b;
209
+    const MACH_HEADER_64_SIZE: usize = 32;
210
+
211
+    if bytes.len() < MACH_HEADER_64_SIZE {
212
+        return bytes;
213
+    }
214
+    let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
215
+    if magic != MH_MAGIC_64 {
216
+        return bytes;
217
+    }
218
+    let ncmds = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize;
219
+    let mut offset = MACH_HEADER_64_SIZE;
220
+    for _ in 0..ncmds {
221
+        if offset + 8 > bytes.len() {
222
+            break;
223
+        }
224
+        let cmd = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
225
+        let cmdsize = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap());
226
+        let cmdsize = cmdsize as usize;
227
+        if cmdsize < 8 || offset + cmdsize > bytes.len() {
228
+            break;
229
+        }
230
+        if cmd == LC_UUID && cmdsize >= 24 {
231
+            bytes[offset + 8..offset + 24].fill(0);
232
+        }
233
+        offset += cmdsize;
234
+    }
235
+    bytes
236
+}
237
+
207238
 fn run_cross_object_case_named(
208239
     program_name: &str,
209240
     helper_name: &str,
@@ -290,18 +321,18 @@ fn deterministic_cross_object_case_named(
290321
     link_objects(&[&fortran_obj, &helper_obj], &binary);
291322
     let load_commands = tool_output("otool", &["-l", binary.to_str().unwrap()]);
292323
     assert!(
293
-        !load_commands.contains("LC_UUID"),
294
-        "linked cross-object integer(16) binary should omit LC_UUID:\n{}",
324
+        load_commands.contains("LC_UUID"),
325
+        "linked cross-object integer(16) binary should carry LC_UUID:\n{}",
295326
         load_commands
296327
     );
297
-    let first = fs::read(&binary).expect("cannot read first linked binary");
328
+    let first = normalize_lc_uuid(fs::read(&binary).expect("cannot read first linked binary"));
298329
 
299330
     link_objects(&[&fortran_obj, &helper_obj], &binary);
300
-    let second = fs::read(&binary).expect("cannot read second linked binary");
331
+    let second = normalize_lc_uuid(fs::read(&binary).expect("cannot read second linked binary"));
301332
 
302333
     assert_eq!(
303334
         first, second,
304
-        "linked cross-object integer(16) binary should be byte-identical across rebuilds"
335
+        "linked cross-object integer(16) binary should stay deterministic modulo LC_UUID"
305336
     );
306337
 
307338
     let _ = fs::remove_file(&fortran_obj);
tests/sprint29_audit_realworld.rsmodified
@@ -63,6 +63,38 @@ fn tool_output(tool: &str, args: &[&str]) -> String {
6363
     String::from_utf8_lossy(&output.stdout).into_owned()
6464
 }
6565
 
66
+fn normalize_lc_uuid(mut bytes: Vec<u8>) -> Vec<u8> {
67
+    const MH_MAGIC_64: u32 = 0xfeedfacf;
68
+    const LC_UUID: u32 = 0x1b;
69
+    const MACH_HEADER_64_SIZE: usize = 32;
70
+
71
+    if bytes.len() < MACH_HEADER_64_SIZE {
72
+        return bytes;
73
+    }
74
+    let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
75
+    if magic != MH_MAGIC_64 {
76
+        return bytes;
77
+    }
78
+    let ncmds = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize;
79
+    let mut offset = MACH_HEADER_64_SIZE;
80
+    for _ in 0..ncmds {
81
+        if offset + 8 > bytes.len() {
82
+            break;
83
+        }
84
+        let cmd = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
85
+        let cmdsize = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap());
86
+        let cmdsize = cmdsize as usize;
87
+        if cmdsize < 8 || offset + cmdsize > bytes.len() {
88
+            break;
89
+        }
90
+        if cmd == LC_UUID && cmdsize >= 24 {
91
+            bytes[offset + 8..offset + 24].fill(0);
92
+        }
93
+        offset += cmdsize;
94
+    }
95
+    bytes
96
+}
97
+
6698
 #[test]
6799
 fn realworld_object_snapshots_stay_deterministic_at_o2() {
68100
     for name in [
@@ -154,7 +186,7 @@ fn realworld_opt_ir_differs_from_raw_ir_at_o2() {
154186
 }
155187
 
156188
 #[test]
157
-fn linked_realworld_binaries_are_deterministic_and_uuid_free() {
189
+fn linked_realworld_binaries_are_deterministic_modulo_uuid() {
158190
     let compiler = find_compiler();
159191
 
160192
     for name in [
@@ -188,20 +220,22 @@ fn linked_realworld_binaries_are_deterministic_and_uuid_free() {
188220
             compile_binary(&compiler, &source, opt, &bin_path);
189221
             let load_commands = tool_output("otool", &["-l", bin_path.to_str().unwrap()]);
190222
             assert!(
191
-                !load_commands.contains("LC_UUID"),
192
-                "linked binary at {} should omit LC_UUID for {}:\n{}",
223
+                load_commands.contains("LC_UUID"),
224
+                "linked binary at {} should carry LC_UUID for {}:\n{}",
193225
                 opt,
194226
                 name,
195227
                 load_commands
196228
             );
197
-            let first = fs::read(&bin_path).expect("cannot read first binary image");
229
+            let first =
230
+                normalize_lc_uuid(fs::read(&bin_path).expect("cannot read first binary image"));
198231
 
199232
             compile_binary(&compiler, &source, opt, &bin_path);
200
-            let second = fs::read(&bin_path).expect("cannot read second binary image");
233
+            let second =
234
+                normalize_lc_uuid(fs::read(&bin_path).expect("cannot read second binary image"));
201235
 
202236
             assert_eq!(
203237
                 first, second,
204
-                "real-world linked binary should be byte-identical when rebuilt at the same output path ({} {})",
238
+                "real-world linked binary should stay deterministic modulo LC_UUID when rebuilt at the same output path ({} {})",
205239
                 name,
206240
                 opt
207241
             );