Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port most of --print=target-cpus to Rust #132514

Merged
merged 3 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2190,12 +2190,8 @@ unsafe extern "C" {

pub fn LLVMRustHasFeature(T: &TargetMachine, s: *const c_char) -> bool;

pub fn LLVMRustPrintTargetCPUs(
T: &TargetMachine,
cpu: *const c_char,
print: unsafe extern "C" fn(out: *mut c_void, string: *const c_char, len: usize),
out: *mut c_void,
);
#[allow(improper_ctypes)]
pub(crate) fn LLVMRustPrintTargetCPUs(TM: &TargetMachine, OutStr: &RustString);
pub fn LLVMRustGetTargetFeaturesCount(T: &TargetMachine) -> size_t;
pub fn LLVMRustGetTargetFeature(
T: &TargetMachine,
Expand All @@ -2204,7 +2200,7 @@ unsafe extern "C" {
Desc: &mut *const c_char,
);

pub fn LLVMRustGetHostCPUName(len: *mut usize) -> *const c_char;
pub fn LLVMRustGetHostCPUName(LenOut: &mut size_t) -> *const u8;

// This function makes copies of pointed to data, so the data's lifetime may end after this
// function returns.
Expand Down
122 changes: 80 additions & 42 deletions compiler/rustc_codegen_llvm/src/llvm_util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::ffi::{CStr, CString, c_char, c_void};
use std::collections::VecDeque;
use std::ffi::{CStr, CString};
use std::fmt::Write;
use std::path::Path;
use std::sync::Once;
Expand Down Expand Up @@ -387,7 +388,65 @@ fn llvm_target_features(tm: &llvm::TargetMachine) -> Vec<(&str, &str)> {
ret
}

fn print_target_features(out: &mut String, sess: &Session, tm: &llvm::TargetMachine) {
pub(crate) fn print(req: &PrintRequest, out: &mut String, sess: &Session) {
require_inited();
let tm = create_informational_target_machine(sess, false);
match req.kind {
PrintKind::TargetCPUs => print_target_cpus(sess, &tm, out),
PrintKind::TargetFeatures => print_target_features(sess, &tm, out),
_ => bug!("rustc_codegen_llvm can't handle print request: {:?}", req),
}
}

fn print_target_cpus(sess: &Session, tm: &llvm::TargetMachine, out: &mut String) {
let cpu_names = llvm::build_string(|s| unsafe {
llvm::LLVMRustPrintTargetCPUs(&tm, s);
})
.unwrap();

struct Cpu<'a> {
cpu_name: &'a str,
remark: String,
}
// Compare CPU against current target to label the default.
let target_cpu = handle_native(&sess.target.cpu);
let make_remark = |cpu_name| {
if cpu_name == target_cpu {
// FIXME(#132514): This prints the LLVM target string, which can be
// different from the Rust target string. Is that intended?
let target = &sess.target.llvm_target;
format!(
" - This is the default target CPU for the current build target (currently {target})."
)
} else {
"".to_owned()
}
};
let mut cpus = cpu_names
.lines()
.map(|cpu_name| Cpu { cpu_name, remark: make_remark(cpu_name) })
.collect::<VecDeque<_>>();

// Only print the "native" entry when host and target are the same arch,
// since otherwise it could be wrong or misleading.
if sess.host.arch == sess.target.arch {
let host = get_host_cpu_name();
cpus.push_front(Cpu {
cpu_name: "native",
remark: format!(" - Select the CPU of the current host (currently {host})."),
});
}

let max_name_width = cpus.iter().map(|cpu| cpu.cpu_name.len()).max().unwrap_or(0);
writeln!(out, "Available CPUs for this target:").unwrap();
for Cpu { cpu_name, remark } in cpus {
// Only pad the CPU name if there's a remark to print after it.
let width = if remark.is_empty() { 0 } else { max_name_width };
writeln!(out, " {cpu_name:<width$}{remark}").unwrap();
}
}

fn print_target_features(sess: &Session, tm: &llvm::TargetMachine, out: &mut String) {
let mut llvm_target_features = llvm_target_features(tm);
let mut known_llvm_target_features = FxHashSet::<&'static str>::default();
let mut rustc_target_features = sess
Expand Down Expand Up @@ -447,52 +506,31 @@ fn print_target_features(out: &mut String, sess: &Session, tm: &llvm::TargetMach
writeln!(out, "and may be renamed or removed in a future version of LLVM or rustc.\n").unwrap();
}

pub(crate) fn print(req: &PrintRequest, mut out: &mut String, sess: &Session) {
require_inited();
let tm = create_informational_target_machine(sess, false);
match req.kind {
PrintKind::TargetCPUs => {
// SAFETY generate a C compatible string from a byte slice to pass
// the target CPU name into LLVM, the lifetime of the reference is
// at least as long as the C function
let cpu_cstring = CString::new(handle_native(sess.target.cpu.as_ref()))
.unwrap_or_else(|e| bug!("failed to convert to cstring: {}", e));
unsafe extern "C" fn callback(out: *mut c_void, string: *const c_char, len: usize) {
let out = unsafe { &mut *(out as *mut &mut String) };
let bytes = unsafe { slice::from_raw_parts(string as *const u8, len) };
write!(out, "{}", String::from_utf8_lossy(bytes)).unwrap();
}
unsafe {
llvm::LLVMRustPrintTargetCPUs(
&tm,
cpu_cstring.as_ptr(),
callback,
(&raw mut out) as *mut c_void,
);
}
}
PrintKind::TargetFeatures => print_target_features(out, sess, &tm),
_ => bug!("rustc_codegen_llvm can't handle print request: {:?}", req),
}
/// Returns the host CPU name, according to LLVM.
fn get_host_cpu_name() -> &'static str {
let mut len = 0;
// SAFETY: The underlying C++ global function returns a `StringRef` that
// isn't tied to any particular backing buffer, so it must be 'static.
let slice: &'static [u8] = unsafe {
let ptr = llvm::LLVMRustGetHostCPUName(&mut len);
assert!(!ptr.is_null());
slice::from_raw_parts(ptr, len)
};
str::from_utf8(slice).expect("host CPU name should be UTF-8")
}

fn handle_native(name: &str) -> &str {
if name != "native" {
return name;
}

unsafe {
let mut len = 0;
let ptr = llvm::LLVMRustGetHostCPUName(&mut len);
str::from_utf8(slice::from_raw_parts(ptr as *const u8, len)).unwrap()
/// If the given string is `"native"`, returns the host CPU name according to
/// LLVM. Otherwise, the string is returned as-is.
fn handle_native(cpu_name: &str) -> &str {
match cpu_name {
"native" => get_host_cpu_name(),
_ => cpu_name,
}
}

pub(crate) fn target_cpu(sess: &Session) -> &str {
match sess.opts.cg.target_cpu {
Some(ref name) => handle_native(name),
None => handle_native(sess.target.cpu.as_ref()),
}
let cpu_name = sess.opts.cg.target_cpu.as_deref().unwrap_or_else(|| &sess.target.cpu);
handle_native(cpu_name)
}

/// The list of LLVM features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
Expand Down
50 changes: 9 additions & 41 deletions compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,49 +317,17 @@ template <typename KV> static size_t getLongestEntryLength(ArrayRef<KV> Table) {
return MaxLen;
}

using PrintBackendInfo = void(void *, const char *Data, size_t Len);

extern "C" void LLVMRustPrintTargetCPUs(LLVMTargetMachineRef TM,
const char *TargetCPU,
PrintBackendInfo Print, void *Out) {
const TargetMachine *Target = unwrap(TM);
const Triple::ArchType HostArch =
Triple(sys::getDefaultTargetTriple()).getArch();
const Triple::ArchType TargetArch = Target->getTargetTriple().getArch();
RustStringRef OutStr) {
ArrayRef<SubtargetSubTypeKV> CPUTable =
unwrap(TM)->getMCSubtargetInfo()->getAllProcessorDescriptions();
auto OS = RawRustStringOstream(OutStr);

std::ostringstream Buf;

const MCSubtargetInfo *MCInfo = Target->getMCSubtargetInfo();
const ArrayRef<SubtargetSubTypeKV> CPUTable =
MCInfo->getAllProcessorDescriptions();
unsigned MaxCPULen = getLongestEntryLength(CPUTable);

Buf << "Available CPUs for this target:\n";
// Don't print the "native" entry when the user specifies --target with a
// different arch since that could be wrong or misleading.
if (HostArch == TargetArch) {
MaxCPULen = std::max(MaxCPULen, (unsigned)std::strlen("native"));
const StringRef HostCPU = sys::getHostCPUName();
Buf << " " << std::left << std::setw(MaxCPULen) << "native"
<< " - Select the CPU of the current host "
"(currently "
<< HostCPU.str() << ").\n";
}
// Just print a bare list of target CPU names, and let Rust-side code handle
// the full formatting of `--print=target-cpus`.
for (auto &CPU : CPUTable) {
// Compare cpu against current target to label the default
if (strcmp(CPU.Key, TargetCPU) == 0) {
Buf << " " << std::left << std::setw(MaxCPULen) << CPU.Key
<< " - This is the default target CPU for the current build target "
"(currently "
<< Target->getTargetTriple().str() << ").";
} else {
Buf << " " << CPU.Key;
}
Buf << "\n";
OS << CPU.Key << "\n";
}

const auto &BufString = Buf.str();
Print(Out, BufString.data(), BufString.size());
}

extern "C" size_t LLVMRustGetTargetFeaturesCount(LLVMTargetMachineRef TM) {
Expand All @@ -382,9 +350,9 @@ extern "C" void LLVMRustGetTargetFeature(LLVMTargetMachineRef TM, size_t Index,
*Desc = Feat.Desc;
}

extern "C" const char *LLVMRustGetHostCPUName(size_t *len) {
extern "C" const char *LLVMRustGetHostCPUName(size_t *OutLen) {
StringRef Name = sys::getHostCPUName();
*len = Name.size();
*OutLen = Name.size();
return Name.data();
}

Expand Down
39 changes: 39 additions & 0 deletions tests/run-make/print-target-cpus-native/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//@ ignore-cross-compile
//@ needs-llvm-components: aarch64 x86
// FIXME(#132514): Is needs-llvm-components actually necessary for this test?

use run_make_support::{assert_contains_regex, rfs, rustc, target};

// Test that when querying `--print=target-cpus` for a target with the same
// architecture as the host, the first CPU is "native" with a suitable remark.

fn main() {
let expected = r"^Available CPUs for this target:
native +- Select the CPU of the current host \(currently [^ )]+\)\.
";

// Without an explicit target.
rustc().print("target-cpus").run().assert_stdout_contains_regex(expected);

// With an explicit target that happens to be the host.
let host = target(); // Because of ignore-cross-compile, assume host == target.
rustc().print("target-cpus").target(host).run().assert_stdout_contains_regex(expected);

// With an explicit output path.
rustc().print("target-cpus=./xyzzy.txt").run().assert_stdout_equals("");
assert_contains_regex(rfs::read_to_string("./xyzzy.txt"), expected);

// Now try some cross-target queries with the same arch as the host.
// (Specify multiple targets so that at least one of them is not the host.)
let cross_targets: &[&str] = if cfg!(target_arch = "aarch64") {
&["aarch64-unknown-linux-gnu", "aarch64-apple-darwin"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: ... would this need needs-llvm-component

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure.

My guess is no, because it's guaranteed by ignore-cross-compile that we at least have the relevant arch component for the host system, and we're only testing other targets with the same arch as the host.

But I know very little about LLVM components.

Copy link
Member

@jieyouxu jieyouxu Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked because I have dealt with run-make tests that seemed innocent (like see #129390). Apparently even --print=target-list needed relevant llvm components to be available.

So I think we should have the //@ needs-llvm-components for the cross-compiled targets: usually we have them (e.g. CI LLVM or locally), so it would be trivially satisfied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the condition will usually be true in CI and locally, then I don't mind adding the directive as a precaution.

Copy link
Member

@jieyouxu jieyouxu Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's mostly just that some contributors do build local llvms without unneeded components.

} else if cfg!(target_arch = "x86_64") {
&["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"]
} else {
&[]
};
for target in cross_targets {
println!("Trying target: {target}");
rustc().print("target-cpus").target(target).run().assert_stdout_contains_regex(expected);
}
}
Loading