|  | 
|  | 1 | +//! The compiler_builtins library is special. It can call functions in core, but it must not | 
|  | 2 | +//! require linkage against a build of core. If it ever does, building the standard library *may* | 
|  | 3 | +//! result in linker errors, depending on whether the linker in use applies optimizations first or | 
|  | 4 | +//! resolves symbols first. So the portable and safe approach is to forbid such a linkage | 
|  | 5 | +//! requirement entirely. | 
|  | 6 | +//! | 
|  | 7 | +//! In addition, whether compiler_builtins requires linkage against core can depend on optimization | 
|  | 8 | +//! settings. Turning off optimizations and enabling debug assertions tends to produce the most | 
|  | 9 | +//! dependence on core that is possible, so that is the configuration we test here. | 
|  | 10 | +
 | 
|  | 11 | +#![deny(warnings)] | 
|  | 12 | + | 
|  | 13 | +extern crate run_make_support; | 
|  | 14 | + | 
|  | 15 | +use run_make_support::object; | 
|  | 16 | +use run_make_support::object::read::archive::ArchiveFile; | 
|  | 17 | +use run_make_support::object::read::Object; | 
|  | 18 | +use run_make_support::object::ObjectSection; | 
|  | 19 | +use run_make_support::object::ObjectSymbol; | 
|  | 20 | +use run_make_support::object::RelocationTarget; | 
|  | 21 | +use run_make_support::out_dir; | 
|  | 22 | +use std::collections::HashSet; | 
|  | 23 | + | 
|  | 24 | +const MANIFEST: &str = r#" | 
|  | 25 | +[package] | 
|  | 26 | +name = "scratch" | 
|  | 27 | +version = "0.1.0" | 
|  | 28 | +edition = "2021" | 
|  | 29 | +
 | 
|  | 30 | +[lib] | 
|  | 31 | +path = "lib.rs""#; | 
|  | 32 | + | 
|  | 33 | +fn main() { | 
|  | 34 | +    let target_dir = out_dir().join("target"); | 
|  | 35 | +    let target = std::env::var("TARGET").unwrap(); | 
|  | 36 | +    if target.starts_with("wasm") || target.starts_with("nvptx") { | 
|  | 37 | +        // wasm and nvptx targets don't produce rlib files that object can parse. | 
|  | 38 | +        return; | 
|  | 39 | +    } | 
|  | 40 | + | 
|  | 41 | +    println!("Testing compiler_builtins for {}", target); | 
|  | 42 | + | 
|  | 43 | +    // Set up the tiniest Cargo project: An empty no_std library. Just enough to run -Zbuild-std. | 
|  | 44 | +    let manifest_path = out_dir().join("Cargo.toml"); | 
|  | 45 | +    std::fs::write(&manifest_path, MANIFEST.as_bytes()).unwrap(); | 
|  | 46 | +    std::fs::write(out_dir().join("lib.rs"), b"#![no_std]").unwrap(); | 
|  | 47 | + | 
|  | 48 | +    let path = std::env::var("PATH").unwrap(); | 
|  | 49 | +    let rustc = std::env::var("RUSTC").unwrap(); | 
|  | 50 | +    let bootstrap_cargo = std::env::var("BOOTSTRAP_CARGO").unwrap(); | 
|  | 51 | +    let status = std::process::Command::new(bootstrap_cargo) | 
|  | 52 | +        .args([ | 
|  | 53 | +            "build", | 
|  | 54 | +            "--manifest-path", | 
|  | 55 | +            manifest_path.to_str().unwrap(), | 
|  | 56 | +            "-Zbuild-std=core", | 
|  | 57 | +            "--target", | 
|  | 58 | +            &target, | 
|  | 59 | +        ]) | 
|  | 60 | +        .env_clear() | 
|  | 61 | +        .env("PATH", path) | 
|  | 62 | +        .env("RUSTC", rustc) | 
|  | 63 | +        .env("RUSTFLAGS", "-Copt-level=0 -Cdebug-assertions=yes") | 
|  | 64 | +        .env("CARGO_TARGET_DIR", &target_dir) | 
|  | 65 | +        .env("RUSTC_BOOTSTRAP", "1") | 
|  | 66 | +        .status() | 
|  | 67 | +        .unwrap(); | 
|  | 68 | + | 
|  | 69 | +    assert!(status.success()); | 
|  | 70 | + | 
|  | 71 | +    let rlibs_path = target_dir.join(target).join("debug").join("deps"); | 
|  | 72 | +    let compiler_builtins_rlib = std::fs::read_dir(rlibs_path) | 
|  | 73 | +        .unwrap() | 
|  | 74 | +        .find_map(|e| { | 
|  | 75 | +            let path = e.unwrap().path(); | 
|  | 76 | +            let file_name = path.file_name().unwrap().to_str().unwrap(); | 
|  | 77 | +            if file_name.starts_with("libcompiler_builtins") && file_name.ends_with(".rlib") { | 
|  | 78 | +                Some(path) | 
|  | 79 | +            } else { | 
|  | 80 | +                None | 
|  | 81 | +            } | 
|  | 82 | +        }) | 
|  | 83 | +        .unwrap(); | 
|  | 84 | + | 
|  | 85 | +    // rlib files are archives, where the archive members each a CGU, and we also have one called | 
|  | 86 | +    // lib.rmeta which is the encoded metadata. Each of the CGUs is an object file. | 
|  | 87 | +    let data = std::fs::read(compiler_builtins_rlib).unwrap(); | 
|  | 88 | + | 
|  | 89 | +    let mut defined_symbols = HashSet::new(); | 
|  | 90 | +    let mut undefined_relocations = HashSet::new(); | 
|  | 91 | + | 
|  | 92 | +    let archive = ArchiveFile::parse(&*data).unwrap(); | 
|  | 93 | +    for member in archive.members() { | 
|  | 94 | +        let member = member.unwrap(); | 
|  | 95 | +        if member.name() == b"lib.rmeta" { | 
|  | 96 | +            continue; | 
|  | 97 | +        } | 
|  | 98 | +        let data = member.data(&*data).unwrap(); | 
|  | 99 | +        let object = object::File::parse(&*data).unwrap(); | 
|  | 100 | + | 
|  | 101 | +        // Record all defined symbols in this CGU. | 
|  | 102 | +        for symbol in object.symbols() { | 
|  | 103 | +            if !symbol.is_undefined() { | 
|  | 104 | +                let name = symbol.name().unwrap(); | 
|  | 105 | +                defined_symbols.insert(name); | 
|  | 106 | +            } | 
|  | 107 | +        } | 
|  | 108 | + | 
|  | 109 | +        // Find any relocations against undefined symbols. Calls within this CGU are relocations | 
|  | 110 | +        // against a defined symbol. | 
|  | 111 | +        for (_offset, relocation) in object.sections().flat_map(|section| section.relocations()) { | 
|  | 112 | +            let RelocationTarget::Symbol(symbol_index) = relocation.target() else { | 
|  | 113 | +                continue; | 
|  | 114 | +            }; | 
|  | 115 | +            let symbol = object.symbol_by_index(symbol_index).unwrap(); | 
|  | 116 | +            if symbol.is_undefined() { | 
|  | 117 | +                let name = symbol.name().unwrap(); | 
|  | 118 | +                undefined_relocations.insert(name); | 
|  | 119 | +            } | 
|  | 120 | +        } | 
|  | 121 | +    } | 
|  | 122 | + | 
|  | 123 | +    // We can have symbols in the compiler_builtins rlib that are actually from core, if they were | 
|  | 124 | +    // monomorphized in the compiler_builtins crate. This is totally fine, because though the call | 
|  | 125 | +    // is to a function in core, it's resolved internally. | 
|  | 126 | +    // | 
|  | 127 | +    // It is normal to have relocations against symbols not defined in the rlib for things like | 
|  | 128 | +    // unwinding, or math functions provided the target's platform libraries. Finding these is not | 
|  | 129 | +    // a problem, we want to specifically ban relocations against core which are not resolved | 
|  | 130 | +    // internally. | 
|  | 131 | +    undefined_relocations | 
|  | 132 | +        .retain(|symbol| !defined_symbols.contains(symbol) && symbol.contains("core")); | 
|  | 133 | + | 
|  | 134 | +    if !undefined_relocations.is_empty() { | 
|  | 135 | +        panic!( | 
|  | 136 | +            "compiler_builtins must not link against core, but it does. \n\ | 
|  | 137 | +            These symbols may be undefined in a debug build of compiler_builtins:\n\ | 
|  | 138 | +            {:?}", | 
|  | 139 | +            undefined_relocations | 
|  | 140 | +        ); | 
|  | 141 | +    } | 
|  | 142 | +} | 
0 commit comments