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

Support native ARM64 MSVC toolchain, and fallback to x64 if emulation is available #957

Merged
merged 4 commits into from
Feb 23, 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
3 changes: 2 additions & 1 deletion dev-tools/gen-windows-sys-binding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ edition = "2018"
publish = false

[dependencies]
windows-bindgen = "0.49"
windows-bindgen = "0.53"
tempfile = "3"
69 changes: 34 additions & 35 deletions dev-tools/gen-windows-sys-binding/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::{
fs,
io::{self, Write},
io::{self, Read, Write},
};

/// This is printed to the file before the rest of the contents.
Expand All @@ -17,49 +17,48 @@ const PRELUDE: &str = r#"// This file is autogenerated.
// ```
"#;

const POSTLUDE: &str = r#"
/// Adapted from
/// [`core::ptr::invalid_mut()`](https://doc.rust-lang.org/src/core/ptr/mod.rs.html#600-607).
///
/// This function should actually use `core::mem::transmute` but due to msrv
/// we use `as` casting instead.
///
/// Once msrv is bumped to 1.56, replace this with `core::mem::transmute` since
/// it is const stablised in 1.56
///
/// NOTE that once supports `strict_provenance` we would also have to update
/// this.
const fn invalid_mut<T>(addr: usize) -> *mut T {
addr as *mut T
}
"#;

fn main() -> io::Result<()> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
// Load the list of APIs
let temp_file = tempfile::Builder::new()
.suffix(".rs")
.tempfile()
.expect("failed to create temp file");

// Common args to windows_bindgen.
let mut args = vec![
"--config",
"std",
"flatten",
"--out",
temp_file.path().to_str().unwrap(),
"--filter",
];

// Append the list of APIs
let buffer = fs::read_to_string(format!("{manifest_dir}/windows_sys.list"))
.expect("failed to read windows_sys.list");
let names: Vec<&str> = buffer
.lines()
.filter_map(|line| {
let line = line.trim();
if line.is_empty() || line.starts_with("//") {
None
} else {
Some(line)
}
})
.collect();
.expect("failed to read list");
args.extend(buffer.lines().filter_map(|line| {
let line = line.trim();
if line.is_empty() || line.starts_with("//") {
None
} else {
Some(line)
}
}));

// Generate bindings.
windows_bindgen::bindgen(&args).expect("running bindgen failed");

// Write the bindings to windows-sys.rs
let bindings =
windows_bindgen::standalone_std(&names).replace("::core::ptr::invalid_mut", "invalid_mut");
let mut bindings = String::new();
fs::File::open(temp_file.path())
.expect("failed to open temp windows_sys.rs")
.read_to_string(&mut bindings)
.expect("failed to read temp windows_sys.rs");

let mut f = fs::File::create(format!("{manifest_dir}/../../src/windows/windows_sys.rs"))
.expect("failed to create windows_sys.rs");
f.write_all(PRELUDE.as_bytes())?;
f.write_all(bindings.as_bytes())?;
f.write_all(POSTLUDE.as_bytes())?;

Ok(())
}
8 changes: 8 additions & 0 deletions dev-tools/gen-windows-sys-binding/windows_sys.list
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Windows.Win32.Foundation.WAIT_OBJECT_0
Windows.Win32.Foundation.WAIT_TIMEOUT
Windows.Win32.Foundation.WAIT_FAILED
Windows.Win32.Foundation.WAIT_ABANDONED
Windows.Win32.Foundation.FreeLibrary

Windows.Win32.System.Com.SAFEARRAY
Windows.Win32.System.Com.SAFEARRAYBOUND
Expand All @@ -19,6 +20,9 @@ Windows.Win32.System.Com.COINIT_MULTITHREADED
Windows.Win32.System.Com.CoCreateInstance
Windows.Win32.System.Com.CoInitializeEx

Windows.Win32.System.LibraryLoader.GetProcAddress
Windows.Win32.System.LibraryLoader.LoadLibraryA

Windows.Win32.System.Pipes.PeekNamedPipe

Windows.Win32.System.Registry.RegCloseKey
Expand All @@ -31,9 +35,13 @@ Windows.Win32.System.Registry.KEY_READ
Windows.Win32.System.Registry.KEY_WOW64_32KEY
Windows.Win32.System.Registry.REG_SZ

Windows.Win32.System.SystemInformation.IMAGE_FILE_MACHINE_AMD64

Windows.Win32.System.Threading.GetMachineTypeAttributes
Windows.Win32.System.Threading.ReleaseSemaphore
Windows.Win32.System.Threading.WaitForSingleObject
Windows.Win32.System.Threading.SEMAPHORE_MODIFY_STATE
Windows.Win32.System.Threading.THREAD_SYNCHRONIZE
Windows.Win32.System.Threading.UserEnabled

Windows.Win32.System.WindowsProgramming.OpenSemaphoreA
3 changes: 2 additions & 1 deletion src/windows/com.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::windows::{
},
};
use std::{
convert::TryInto,
ffi::{OsStr, OsString},
mem::ManuallyDrop,
ops::Deref,
Expand All @@ -24,7 +25,7 @@ use std::{
};

pub fn initialize() -> Result<(), HRESULT> {
let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED) };
let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED.try_into().unwrap()) };
if err != S_OK && err != S_FALSE {
// S_FALSE just means COM is already initialized
Err(err)
Expand Down
108 changes: 96 additions & 12 deletions src/windows/find_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ mod impl_ {
use crate::windows::registry::{RegistryKey, LOCAL_MACHINE};
use crate::windows::setup_config::SetupConfiguration;
use crate::windows::vs_instances::{VsInstances, VswhereInstance};
use crate::windows::windows_sys::{
FreeLibrary, GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
};
use std::convert::TryFrom;
use std::env;
use std::ffi::OsString;
Expand All @@ -173,6 +177,8 @@ mod impl_ {
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;

use super::MSVC_FAMILY;
use crate::Tool;
Expand All @@ -199,6 +205,71 @@ mod impl_ {
include: Vec<PathBuf>,
}

struct LibraryHandle(HMODULE);

impl LibraryHandle {
fn new(name: &[u8]) -> Option<Self> {
let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
(!handle.is_null()).then(|| Self(handle))
}

/// Get a function pointer to a function in the library.
/// SAFETY: The caller must ensure that the function signature matches the actual function.
/// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the
/// generated function for `func_signature`.
unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
Copy link

Choose a reason for hiding this comment

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

This should be tying the lifetime of F to the lifetime of self, if any.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks, opened #1010 to update safety comment for the function.

I can't add lifetime to the return type directly, I can only add bound to the generic F.

However, I don't think we can add lifetime to function pointer, a newtype that dereferences to F would have the same problem since function pointer is copyable, so the only option is to add a safety comment for this unsafe function.

let symbol = unsafe { GetProcAddress(self.0, name.as_ptr() as _) };
symbol.map(|symbol| unsafe { mem::transmute_copy(&symbol) })
}
}

impl Drop for LibraryHandle {
fn drop(&mut self) {
unsafe { FreeLibrary(self.0) };
}
}

type GetMachineTypeAttributesFuncType =
unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
const _: () = {
// Ensure that our hand-written signature matches the actual function signature.
// We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to
// it, which will fail to load on older versions of Windows.
let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
};

fn is_amd64_emulation_supported_inner() -> Option<bool> {
// GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it.
let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
// SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature.
let get_machine_type_attributes = unsafe {
kernel32
.get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
}?;
let mut attributes = Default::default();
if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
{
Some((attributes & UserEnabled) != 0)
} else {
Some(false)
}
}

fn is_amd64_emulation_supported() -> bool {
// TODO: Replace with a OnceLock once MSRV is 1.70.
static LOAD_VALUE: Once = Once::new();
static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);

// Using Relaxed ordering since the Once is providing synchronization.
LOAD_VALUE.call_once(|| {
IS_SUPPORTED.store(
is_amd64_emulation_supported_inner().unwrap_or(false),
Ordering::Relaxed,
);
});
IS_SUPPORTED.load(Ordering::Relaxed)
}

impl MsvcTool {
fn new(tool: PathBuf) -> MsvcTool {
MsvcTool {
Expand Down Expand Up @@ -226,7 +297,6 @@ mod impl_ {

/// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the
/// given target's arch. Returns `None` if the variable does not exist.
#[cfg(windows)]
fn is_vscmd_target(target: TargetArch<'_>) -> Option<bool> {
let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?;
// Convert the Rust target arch to its VS arch equivalent.
Expand Down Expand Up @@ -482,27 +552,41 @@ mod impl_ {
) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
let version = vs15plus_vc_read_version(instance_path)?;

let host = match host_arch() {
X86 => "X86",
X86_64 => "X64",
// There is no natively hosted compiler on ARM64.
// Instead, use the x86 toolchain under emulation (there is no x64 emulation).
AARCH64 => "X86",
let hosts = match host_arch() {
X86 => &["X86"],
X86_64 => &["X64"],
// Starting with VS 17.4, there is a natively hosted compiler on ARM64:
// https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/
// On older versions of VS, we use x64 if running under emulation is supported,
// otherwise use x86.
AARCH64 => {
if is_amd64_emulation_supported() {
&["ARM64", "X64", "X86"][..]
} else {
&["ARM64", "X86"]
}
}
_ => return None,
};
let target = lib_subdir(target)?;
// The directory layout here is MSVC/bin/Host$host/$target/
let path = instance_path.join(r"VC\Tools\MSVC").join(version);
// We use the first available host architecture that can build for the target
let (host_path, host) = hosts.iter().find_map(|&x| {
let candidate = path.join("bin").join(format!("Host{}", x));
if candidate.join(target).exists() {
Some((candidate, x))
} else {
None
}
})?;
// This is the path to the toolchain for a particular target, running
// on a given host
let bin_path = path.join("bin").join(format!("Host{}", host)).join(target);
let bin_path = host_path.join(target);
// But! we also need PATH to contain the target directory for the host
// architecture, because it contains dlls like mspdb140.dll compiled for
// the host architecture.
let host_dylib_path = path
.join("bin")
.join(format!("Host{}", host))
.join(host.to_lowercase());
let host_dylib_path = host_path.join(host.to_lowercase());
let lib_path = path.join("lib").join(target);
let alt_lib_path = (target == "arm64ec").then(|| path.join("lib").join("arm64ec"));
let include_path = path.join("include");
Expand Down
Loading