rustc_codegen_ssa/back/
metadata.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
//! Reading of the rustc metadata for rlibs and dylibs

use std::borrow::Cow;
use std::fs::File;
use std::io::Write;
use std::path::Path;

use object::write::{self, StandardSegment, Symbol, SymbolSection};
use object::{
    Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, ObjectSymbol,
    SectionFlags, SectionKind, SubArchitecture, SymbolFlags, SymbolKind, SymbolScope, elf, pe,
    xcoff,
};
use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::owned_slice::{OwnedSlice, try_slice_owned};
use rustc_metadata::EncodedMetadata;
use rustc_metadata::creader::MetadataLoader;
use rustc_metadata::fs::METADATA_FILENAME;
use rustc_middle::bug;
use rustc_session::Session;
use rustc_span::sym;
use rustc_target::abi::Endian;
use rustc_target::spec::{RelocModel, Target, ef_avr_arch};

/// The default metadata loader. This is used by cg_llvm and cg_clif.
///
/// # Metadata location
///
/// <dl>
/// <dt>rlib</dt>
/// <dd>The metadata can be found in the `lib.rmeta` file inside of the ar archive.</dd>
/// <dt>dylib</dt>
/// <dd>The metadata can be found in the `.rustc` section of the shared library.</dd>
/// </dl>
#[derive(Debug)]
pub(crate) struct DefaultMetadataLoader;

static AIX_METADATA_SYMBOL_NAME: &'static str = "__aix_rust_metadata";

fn load_metadata_with(
    path: &Path,
    f: impl for<'a> FnOnce(&'a [u8]) -> Result<&'a [u8], String>,
) -> Result<OwnedSlice, String> {
    let file =
        File::open(path).map_err(|e| format!("failed to open file '{}': {}", path.display(), e))?;

    unsafe { Mmap::map(file) }
        .map_err(|e| format!("failed to mmap file '{}': {}", path.display(), e))
        .and_then(|mmap| try_slice_owned(mmap, |mmap| f(mmap)))
}

impl MetadataLoader for DefaultMetadataLoader {
    fn get_rlib_metadata(&self, target: &Target, path: &Path) -> Result<OwnedSlice, String> {
        load_metadata_with(path, |data| {
            let archive = object::read::archive::ArchiveFile::parse(&*data)
                .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?;

            for entry_result in archive.members() {
                let entry = entry_result
                    .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?;
                if entry.name() == METADATA_FILENAME.as_bytes() {
                    let data = entry
                        .data(data)
                        .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?;
                    if target.is_like_aix {
                        return get_metadata_xcoff(path, data);
                    } else {
                        return search_for_section(path, data, ".rmeta");
                    }
                }
            }

            Err(format!("metadata not found in rlib '{}'", path.display()))
        })
    }

    fn get_dylib_metadata(&self, target: &Target, path: &Path) -> Result<OwnedSlice, String> {
        if target.is_like_aix {
            load_metadata_with(path, |data| get_metadata_xcoff(path, data))
        } else {
            load_metadata_with(path, |data| search_for_section(path, data, ".rustc"))
        }
    }
}

pub(super) fn search_for_section<'a>(
    path: &Path,
    bytes: &'a [u8],
    section: &str,
) -> Result<&'a [u8], String> {
    let Ok(file) = object::File::parse(bytes) else {
        // The parse above could fail for odd reasons like corruption, but for
        // now we just interpret it as this target doesn't support metadata
        // emission in object files so the entire byte slice itself is probably
        // a metadata file. Ideally though if necessary we could at least check
        // the prefix of bytes to see if it's an actual metadata object and if
        // not forward the error along here.
        return Ok(bytes);
    };
    file.section_by_name(section)
        .ok_or_else(|| format!("no `{}` section in '{}'", section, path.display()))?
        .data()
        .map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e))
}

fn add_gnu_property_note(
    file: &mut write::Object<'static>,
    architecture: Architecture,
    binary_format: BinaryFormat,
    endianness: Endianness,
) {
    // check bti protection
    if binary_format != BinaryFormat::Elf
        || !matches!(architecture, Architecture::X86_64 | Architecture::Aarch64)
    {
        return;
    }

    let section = file.add_section(
        file.segment_name(StandardSegment::Data).to_vec(),
        b".note.gnu.property".to_vec(),
        SectionKind::Note,
    );
    let mut data: Vec<u8> = Vec::new();
    let n_namsz: u32 = 4; // Size of the n_name field
    let n_descsz: u32 = 16; // Size of the n_desc field
    let n_type: u32 = object::elf::NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor
    let header_values = [n_namsz, n_descsz, n_type];
    header_values.iter().for_each(|v| {
        data.extend_from_slice(&match endianness {
            Endianness::Little => v.to_le_bytes(),
            Endianness::Big => v.to_be_bytes(),
        })
    });
    data.extend_from_slice(b"GNU\0"); // Owner of the program property note
    let pr_type: u32 = match architecture {
        Architecture::X86_64 => object::elf::GNU_PROPERTY_X86_FEATURE_1_AND,
        Architecture::Aarch64 => object::elf::GNU_PROPERTY_AARCH64_FEATURE_1_AND,
        _ => unreachable!(),
    };
    let pr_datasz: u32 = 4; //size of the pr_data field
    let pr_data: u32 = 3; //program property descriptor
    let pr_padding: u32 = 0;
    let property_values = [pr_type, pr_datasz, pr_data, pr_padding];
    property_values.iter().for_each(|v| {
        data.extend_from_slice(&match endianness {
            Endianness::Little => v.to_le_bytes(),
            Endianness::Big => v.to_be_bytes(),
        })
    });
    file.append_section_data(section, &data, 8);
}

pub(super) fn get_metadata_xcoff<'a>(path: &Path, data: &'a [u8]) -> Result<&'a [u8], String> {
    let Ok(file) = object::File::parse(data) else {
        return Ok(data);
    };
    let info_data = search_for_section(path, data, ".info")?;
    if let Some(metadata_symbol) =
        file.symbols().find(|sym| sym.name() == Ok(AIX_METADATA_SYMBOL_NAME))
    {
        let offset = metadata_symbol.address() as usize;
        // The offset specifies the location of rustc metadata in the .info section of XCOFF.
        // Each string stored in .info section of XCOFF is preceded by a 4-byte length field.
        if offset < 4 {
            return Err(format!("Invalid metadata symbol offset: {offset}"));
        }
        // XCOFF format uses big-endian byte order.
        let len = u32::from_be_bytes(info_data[(offset - 4)..offset].try_into().unwrap()) as usize;
        if offset + len > (info_data.len() as usize) {
            return Err(format!(
                "Metadata at offset {offset} with size {len} is beyond .info section"
            ));
        }
        Ok(&info_data[offset..(offset + len)])
    } else {
        Err(format!("Unable to find symbol {AIX_METADATA_SYMBOL_NAME}"))
    }
}

pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> {
    let endianness = match sess.target.options.endian {
        Endian::Little => Endianness::Little,
        Endian::Big => Endianness::Big,
    };
    let (architecture, sub_architecture) = match &sess.target.arch[..] {
        "arm" => (Architecture::Arm, None),
        "aarch64" => (
            if sess.target.pointer_width == 32 {
                Architecture::Aarch64_Ilp32
            } else {
                Architecture::Aarch64
            },
            None,
        ),
        "x86" => (Architecture::I386, None),
        "s390x" => (Architecture::S390x, None),
        "mips" | "mips32r6" => (Architecture::Mips, None),
        "mips64" | "mips64r6" => (Architecture::Mips64, None),
        "x86_64" => (
            if sess.target.pointer_width == 32 {
                Architecture::X86_64_X32
            } else {
                Architecture::X86_64
            },
            None,
        ),
        "powerpc" => (Architecture::PowerPc, None),
        "powerpc64" => (Architecture::PowerPc64, None),
        "riscv32" => (Architecture::Riscv32, None),
        "riscv64" => (Architecture::Riscv64, None),
        "sparc" => (Architecture::Sparc32Plus, None),
        "sparc64" => (Architecture::Sparc64, None),
        "avr" => (Architecture::Avr, None),
        "msp430" => (Architecture::Msp430, None),
        "hexagon" => (Architecture::Hexagon, None),
        "bpf" => (Architecture::Bpf, None),
        "loongarch64" => (Architecture::LoongArch64, None),
        "csky" => (Architecture::Csky, None),
        "arm64ec" => (Architecture::Aarch64, Some(SubArchitecture::Arm64EC)),
        // Unsupported architecture.
        _ => return None,
    };
    let binary_format = if sess.target.is_like_osx {
        BinaryFormat::MachO
    } else if sess.target.is_like_windows {
        BinaryFormat::Coff
    } else if sess.target.is_like_aix {
        BinaryFormat::Xcoff
    } else {
        BinaryFormat::Elf
    };

    let mut file = write::Object::new(binary_format, architecture, endianness);
    file.set_sub_architecture(sub_architecture);
    if sess.target.is_like_osx {
        if macho_is_arm64e(&sess.target) {
            file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E);
        }

        file.set_macho_build_version(macho_object_build_version_for_target(&sess.target))
    }
    if binary_format == BinaryFormat::Coff {
        // Disable the default mangler to avoid mangling the special "@feat.00" symbol name.
        let original_mangling = file.mangling();
        file.set_mangling(object::write::Mangling::None);

        let mut feature = 0;

        if file.architecture() == object::Architecture::I386 {
            // When linking with /SAFESEH on x86, lld requires that all linker inputs be marked as
            // safe exception handling compatible. Metadata files masquerade as regular COFF
            // objects and are treated as linker inputs, despite containing no actual code. Thus,
            // they still need to be marked as safe exception handling compatible. See #96498.
            // Reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
            feature |= 1;
        }

        file.add_symbol(object::write::Symbol {
            name: "@feat.00".into(),
            value: feature,
            size: 0,
            kind: object::SymbolKind::Data,
            scope: object::SymbolScope::Compilation,
            weak: false,
            section: object::write::SymbolSection::Absolute,
            flags: object::SymbolFlags::None,
        });

        file.set_mangling(original_mangling);
    }
    let e_flags = match architecture {
        Architecture::Mips => {
            let arch = match sess.target.options.cpu.as_ref() {
                "mips1" => elf::EF_MIPS_ARCH_1,
                "mips2" => elf::EF_MIPS_ARCH_2,
                "mips3" => elf::EF_MIPS_ARCH_3,
                "mips4" => elf::EF_MIPS_ARCH_4,
                "mips5" => elf::EF_MIPS_ARCH_5,
                s if s.contains("r6") => elf::EF_MIPS_ARCH_32R6,
                _ => elf::EF_MIPS_ARCH_32R2,
            };

            let mut e_flags = elf::EF_MIPS_CPIC | arch;

            // If the ABI is explicitly given, use it or default to O32.
            match sess.target.options.llvm_abiname.to_lowercase().as_str() {
                "n32" => e_flags |= elf::EF_MIPS_ABI2,
                "o32" => e_flags |= elf::EF_MIPS_ABI_O32,
                _ => e_flags |= elf::EF_MIPS_ABI_O32,
            };

            if sess.target.options.relocation_model != RelocModel::Static {
                e_flags |= elf::EF_MIPS_PIC;
            }
            if sess.target.options.cpu.contains("r6") {
                e_flags |= elf::EF_MIPS_NAN2008;
            }
            e_flags
        }
        Architecture::Mips64 => {
            // copied from `mips64el-linux-gnuabi64-gcc foo.c -c`
            let e_flags = elf::EF_MIPS_CPIC
                | elf::EF_MIPS_PIC
                | if sess.target.options.cpu.contains("r6") {
                    elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008
                } else {
                    elf::EF_MIPS_ARCH_64R2
                };
            e_flags
        }
        Architecture::Riscv32 | Architecture::Riscv64 => {
            // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc
            let mut e_flags: u32 = 0x0;

            // Check if compressed is enabled
            // `unstable_target_features` is used here because "c" is gated behind riscv_target_feature.
            if sess.unstable_target_features.contains(&sym::c) {
                e_flags |= elf::EF_RISCV_RVC;
            }

            // Set the appropriate flag based on ABI
            // This needs to match LLVM `RISCVELFStreamer.cpp`
            match &*sess.target.llvm_abiname {
                "" | "ilp32" | "lp64" => (),
                "ilp32f" | "lp64f" => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE,
                "ilp32d" | "lp64d" => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE,
                "ilp32e" => e_flags |= elf::EF_RISCV_RVE,
                _ => bug!("unknown RISC-V ABI name"),
            }

            e_flags
        }
        Architecture::LoongArch64 => {
            // Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version
            let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1;

            // Set the appropriate flag based on ABI
            // This needs to match LLVM `LoongArchELFStreamer.cpp`
            match &*sess.target.llvm_abiname {
                "ilp32s" | "lp64s" => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT,
                "ilp32f" | "lp64f" => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT,
                "ilp32d" | "lp64d" => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT,
                _ => bug!("unknown LoongArch ABI name"),
            }

            e_flags
        }
        Architecture::Avr => {
            // Resolve the ISA revision and set
            // the appropriate EF_AVR_ARCH flag.
            ef_avr_arch(&sess.target.options.cpu)
        }
        Architecture::Csky => {
            let e_flags = match sess.target.options.abi.as_ref() {
                "abiv2" => elf::EF_CSKY_ABIV2,
                _ => elf::EF_CSKY_ABIV1,
            };
            e_flags
        }
        _ => 0,
    };
    // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI`
    let os_abi = match sess.target.options.os.as_ref() {
        "hermit" => elf::ELFOSABI_STANDALONE,
        "freebsd" => elf::ELFOSABI_FREEBSD,
        "solaris" => elf::ELFOSABI_SOLARIS,
        _ => elf::ELFOSABI_NONE,
    };
    let abi_version = 0;
    add_gnu_property_note(&mut file, architecture, binary_format, endianness);
    file.flags = FileFlags::Elf { os_abi, abi_version, e_flags };
    Some(file)
}

/// Mach-O files contain information about:
/// - The platform/OS they were built for (macOS/watchOS/Mac Catalyst/iOS simulator etc).
/// - The minimum OS version / deployment target.
/// - The version of the SDK they were targetting.
///
/// In the past, this was accomplished using the LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS,
/// LC_VERSION_MIN_TVOS or LC_VERSION_MIN_WATCHOS load commands, which each contain information
/// about the deployment target and SDK version, and implicitly, by their presence, which OS they
/// target. Simulator targets were determined if the architecture was x86_64, but there was e.g. a
/// LC_VERSION_MIN_IPHONEOS present.
///
/// This is of course brittle and limited, so modern tooling emit the LC_BUILD_VERSION load
/// command (which contains all three pieces of information in one) when the deployment target is
/// high enough, or the target is something that wouldn't be encodable with the old load commands
/// (such as Mac Catalyst, or Aarch64 iOS simulator).
///
/// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this
/// returns the `MachOBuildVersion` for the target to do so.
fn macho_object_build_version_for_target(target: &Target) -> object::write::MachOBuildVersion {
    /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
    /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
    fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
        let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
        (major << 16) | (minor << 8) | patch
    }

    let platform =
        rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS");
    let min_os = rustc_target::spec::current_apple_deployment_target(target);

    let mut build_version = object::write::MachOBuildVersion::default();
    build_version.platform = platform;
    build_version.minos = pack_version(min_os);
    // The version here does not _really_ matter, since it is only used at runtime, and we specify
    // it when linking the final binary, so we will omit the version. This is also what LLVM does,
    // and the tooling also allows this (and shows the SDK version as `n/a`). Finally, it is the
    // semantically correct choice, as the SDK has not influenced the binary generated by rustc at
    // this point in time.
    build_version.sdk = 0;

    build_version
}

/// Is Apple's CPU subtype `arm64e`s
fn macho_is_arm64e(target: &Target) -> bool {
    target.llvm_target.starts_with("arm64e")
}

pub(crate) enum MetadataPosition {
    First,
    Last,
}

/// For rlibs we "pack" rustc metadata into a dummy object file.
///
/// Historically it was needed because rustc linked rlibs as whole-archive in some cases.
/// In that case linkers try to include all files located in an archive, so if metadata is stored
/// in an archive then it needs to be of a form that the linker is able to process.
/// Now it's not clear whether metadata still needs to be wrapped into an object file or not.
///
/// Note, though, that we don't actually want this metadata to show up in any
/// final output of the compiler. Instead this is purely for rustc's own
/// metadata tracking purposes.
///
/// With the above in mind, each "flavor" of object format gets special
/// handling here depending on the target:
///
/// * MachO - macos-like targets will insert the metadata into a section that
///   is sort of fake dwarf debug info. Inspecting the source of the macos
///   linker this causes these sections to be skipped automatically because
///   it's not in an allowlist of otherwise well known dwarf section names to
///   go into the final artifact.
///
/// * WebAssembly - this uses wasm files themselves as the object file format
///   so an empty file with no linking metadata but a single custom section is
///   created holding our metadata.
///
/// * COFF - Windows-like targets create an object with a section that has
///   the `IMAGE_SCN_LNK_REMOVE` flag set which ensures that if the linker
///   ever sees the section it doesn't process it and it's removed.
///
/// * ELF - All other targets are similar to Windows in that there's a
///   `SHF_EXCLUDE` flag we can set on sections in an object file to get
///   automatically removed from the final output.
pub(crate) fn create_wrapper_file(
    sess: &Session,
    section_name: String,
    data: &[u8],
) -> (Vec<u8>, MetadataPosition) {
    let Some(mut file) = create_object_file(sess) else {
        if sess.target.is_like_wasm {
            return (
                create_metadata_file_for_wasm(sess, data, &section_name),
                MetadataPosition::First,
            );
        }

        // Targets using this branch don't have support implemented here yet or
        // they're not yet implemented in the `object` crate and will likely
        // fill out this module over time.
        return (data.to_vec(), MetadataPosition::Last);
    };
    let section = if file.format() == BinaryFormat::Xcoff {
        file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug)
    } else {
        file.add_section(
            file.segment_name(StandardSegment::Debug).to_vec(),
            section_name.into_bytes(),
            SectionKind::Debug,
        )
    };
    match file.format() {
        BinaryFormat::Coff => {
            file.section_mut(section).flags =
                SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE };
        }
        BinaryFormat::Elf => {
            file.section_mut(section).flags =
                SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 };
        }
        BinaryFormat::Xcoff => {
            // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss.
            file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text);
            file.section_mut(section).flags =
                SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 };
            // Encode string stored in .info section of XCOFF.
            // FIXME: The length of data here is not guaranteed to fit in a u32.
            // We may have to split the data into multiple pieces in order to
            // store in .info section.
            let len: u32 = data.len().try_into().unwrap();
            let offset = file.append_section_data(section, &len.to_be_bytes(), 1);
            // Add a symbol referring to the data in .info section.
            file.add_symbol(Symbol {
                name: AIX_METADATA_SYMBOL_NAME.into(),
                value: offset + 4,
                size: 0,
                kind: SymbolKind::Unknown,
                scope: SymbolScope::Compilation,
                weak: false,
                section: SymbolSection::Section(section),
                flags: SymbolFlags::Xcoff {
                    n_sclass: xcoff::C_INFO,
                    x_smtyp: xcoff::C_HIDEXT,
                    x_smclas: xcoff::C_HIDEXT,
                    containing_csect: None,
                },
            });
        }
        _ => {}
    };
    file.append_section_data(section, data, 1);
    (file.write().unwrap(), MetadataPosition::First)
}

// Historical note:
//
// When using link.exe it was seen that the section name `.note.rustc`
// was getting shortened to `.note.ru`, and according to the PE and COFF
// specification:
//
// > Executable images do not use a string table and do not support
// > section names longer than 8 characters
//
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
//
// As a result, we choose a slightly shorter name! As to why
// `.note.rustc` works on MinGW, see
// https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/lld/COFF/Writer.cpp#L1190-L1197
pub fn create_compressed_metadata_file(
    sess: &Session,
    metadata: &EncodedMetadata,
    symbol_name: &str,
) -> Vec<u8> {
    let mut packed_metadata = rustc_metadata::METADATA_HEADER.to_vec();
    packed_metadata.write_all(&(metadata.raw_data().len() as u64).to_le_bytes()).unwrap();
    packed_metadata.extend(metadata.raw_data());

    let Some(mut file) = create_object_file(sess) else {
        if sess.target.is_like_wasm {
            return create_metadata_file_for_wasm(sess, &packed_metadata, ".rustc");
        }
        return packed_metadata.to_vec();
    };
    if file.format() == BinaryFormat::Xcoff {
        return create_compressed_metadata_file_for_xcoff(file, &packed_metadata, symbol_name);
    }
    let section = file.add_section(
        file.segment_name(StandardSegment::Data).to_vec(),
        b".rustc".to_vec(),
        SectionKind::ReadOnlyData,
    );
    match file.format() {
        BinaryFormat::Elf => {
            // Explicitly set no flags to avoid SHF_ALLOC default for data section.
            file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 };
        }
        _ => {}
    };
    let offset = file.append_section_data(section, &packed_metadata, 1);

    // For MachO and probably PE this is necessary to prevent the linker from throwing away the
    // .rustc section. For ELF this isn't necessary, but it also doesn't harm.
    file.add_symbol(Symbol {
        name: symbol_name.as_bytes().to_vec(),
        value: offset,
        size: packed_metadata.len() as u64,
        kind: SymbolKind::Data,
        scope: SymbolScope::Dynamic,
        weak: false,
        section: SymbolSection::Section(section),
        flags: SymbolFlags::None,
    });

    file.write().unwrap()
}

/// * Xcoff - On AIX, custom sections are merged into predefined sections,
///   so custom .rustc section is not preserved during linking.
///   For this reason, we store metadata in predefined .info section, and
///   define a symbol to reference the metadata. To preserve metadata during
///   linking on AIX, we have to
///   1. Create an empty .text section, a empty .data section.
///   2. Define an empty symbol named `symbol_name` inside .data section.
///   3. Define an symbol named `AIX_METADATA_SYMBOL_NAME` referencing
///      data inside .info section.
///   From XCOFF's view, (2) creates a csect entry in the symbol table, the
///   symbol created by (3) is a info symbol for the preceding csect. Thus
///   two symbols are preserved during linking and we can use the second symbol
///   to reference the metadata.
pub fn create_compressed_metadata_file_for_xcoff(
    mut file: write::Object<'_>,
    data: &[u8],
    symbol_name: &str,
) -> Vec<u8> {
    assert!(file.format() == BinaryFormat::Xcoff);
    // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss.
    file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text);
    let data_section = file.add_section(Vec::new(), b".data".to_vec(), SectionKind::Data);
    let section = file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug);
    file.add_file_symbol("lib.rmeta".into());
    file.section_mut(section).flags = SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 };
    // Add a global symbol to data_section.
    file.add_symbol(Symbol {
        name: symbol_name.as_bytes().into(),
        value: 0,
        size: 0,
        kind: SymbolKind::Data,
        scope: SymbolScope::Dynamic,
        weak: true,
        section: SymbolSection::Section(data_section),
        flags: SymbolFlags::None,
    });
    let len: u32 = data.len().try_into().unwrap();
    let offset = file.append_section_data(section, &len.to_be_bytes(), 1);
    // Add a symbol referring to the rustc metadata.
    file.add_symbol(Symbol {
        name: AIX_METADATA_SYMBOL_NAME.into(),
        value: offset + 4, // The metadata is preceded by a 4-byte length field.
        size: 0,
        kind: SymbolKind::Unknown,
        scope: SymbolScope::Dynamic,
        weak: false,
        section: SymbolSection::Section(section),
        flags: SymbolFlags::Xcoff {
            n_sclass: xcoff::C_INFO,
            x_smtyp: xcoff::C_HIDEXT,
            x_smclas: xcoff::C_HIDEXT,
            containing_csect: None,
        },
    });
    file.append_section_data(section, data, 1);
    file.write().unwrap()
}

/// Creates a simple WebAssembly object file, which is itself a wasm module,
/// that contains a custom section of the name `section_name` with contents
/// `data`.
///
/// NB: the `object` crate does not yet have support for writing the wasm
/// object file format. In lieu of that the `wasm-encoder` crate is used to
/// build a wasm file by hand.
///
/// The wasm object file format is defined at
/// <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md>
/// and mainly consists of a `linking` custom section. In this case the custom
/// section there is empty except for a version marker indicating what format
/// it's in.
///
/// The main purpose of this is to contain a custom section with `section_name`,
/// which is then appended after `linking`.
///
/// As a further detail the object needs to have a 64-bit memory if `wasm64` is
/// the target or otherwise it's interpreted as a 32-bit object which is
/// incompatible with 64-bit ones.
pub fn create_metadata_file_for_wasm(sess: &Session, data: &[u8], section_name: &str) -> Vec<u8> {
    assert!(sess.target.is_like_wasm);
    let mut module = wasm_encoder::Module::new();
    let mut imports = wasm_encoder::ImportSection::new();

    if sess.target.pointer_width == 64 {
        imports.import("env", "__linear_memory", wasm_encoder::MemoryType {
            minimum: 0,
            maximum: None,
            memory64: true,
            shared: false,
            page_size_log2: None,
        });
    }

    if imports.len() > 0 {
        module.section(&imports);
    }
    module.section(&wasm_encoder::CustomSection {
        name: "linking".into(),
        data: Cow::Borrowed(&[2]),
    });
    module.section(&wasm_encoder::CustomSection { name: section_name.into(), data: data.into() });
    module.finish()
}