Skip to content

Commit

Permalink
Support native ARM64 MSVC toolchain, and fallback to x64 if emulation…
Browse files Browse the repository at this point in the history
… is available (#957)

* Use ARM64 host for MSVC if available

* Check if an MSVC toolchain can build for a target

* Fix MSRV compatibility

* Add support for Native ARM64 and x64 if available

---------

Co-authored-by: Alexander Ovchinnikov <8490695+Alovchin91@users.noreply.github.com>
Co-authored-by: Alexander Ovchinnikov <alexander.ovchinnikov@jetbrains.com>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent 3f59e09 commit 012226e
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 83 deletions.
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> {
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

0 comments on commit 012226e

Please sign in to comment.