Skip to content

Commit 012226e

Browse files
dpaolielloAlovchin91Alexander Ovchinnikov
authored
Support native ARM64 MSVC toolchain, and fallback to x64 if emulation 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>
1 parent 3f59e09 commit 012226e

File tree

6 files changed

+186
-83
lines changed

6 files changed

+186
-83
lines changed

dev-tools/gen-windows-sys-binding/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ edition = "2018"
55
publish = false
66

77
[dependencies]
8-
windows-bindgen = "0.49"
8+
windows-bindgen = "0.53"
9+
tempfile = "3"

dev-tools/gen-windows-sys-binding/src/main.rs

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use std::{
55
fs,
6-
io::{self, Write},
6+
io::{self, Read, Write},
77
};
88

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

20-
const POSTLUDE: &str = r#"
21-
/// Adapted from
22-
/// [`core::ptr::invalid_mut()`](https://doc.rust-lang.org/src/core/ptr/mod.rs.html#600-607).
23-
///
24-
/// This function should actually use `core::mem::transmute` but due to msrv
25-
/// we use `as` casting instead.
26-
///
27-
/// Once msrv is bumped to 1.56, replace this with `core::mem::transmute` since
28-
/// it is const stablised in 1.56
29-
///
30-
/// NOTE that once supports `strict_provenance` we would also have to update
31-
/// this.
32-
const fn invalid_mut<T>(addr: usize) -> *mut T {
33-
addr as *mut T
34-
}
35-
"#;
36-
3720
fn main() -> io::Result<()> {
3821
let manifest_dir = env!("CARGO_MANIFEST_DIR");
39-
// Load the list of APIs
22+
let temp_file = tempfile::Builder::new()
23+
.suffix(".rs")
24+
.tempfile()
25+
.expect("failed to create temp file");
26+
27+
// Common args to windows_bindgen.
28+
let mut args = vec![
29+
"--config",
30+
"std",
31+
"flatten",
32+
"--out",
33+
temp_file.path().to_str().unwrap(),
34+
"--filter",
35+
];
36+
37+
// Append the list of APIs
4038
let buffer = fs::read_to_string(format!("{manifest_dir}/windows_sys.list"))
41-
.expect("failed to read windows_sys.list");
42-
let names: Vec<&str> = buffer
43-
.lines()
44-
.filter_map(|line| {
45-
let line = line.trim();
46-
if line.is_empty() || line.starts_with("//") {
47-
None
48-
} else {
49-
Some(line)
50-
}
51-
})
52-
.collect();
39+
.expect("failed to read list");
40+
args.extend(buffer.lines().filter_map(|line| {
41+
let line = line.trim();
42+
if line.is_empty() || line.starts_with("//") {
43+
None
44+
} else {
45+
Some(line)
46+
}
47+
}));
48+
49+
// Generate bindings.
50+
windows_bindgen::bindgen(&args).expect("running bindgen failed");
5351

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

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

6463
Ok(())
6564
}

dev-tools/gen-windows-sys-binding/windows_sys.list

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Windows.Win32.Foundation.WAIT_OBJECT_0
1111
Windows.Win32.Foundation.WAIT_TIMEOUT
1212
Windows.Win32.Foundation.WAIT_FAILED
1313
Windows.Win32.Foundation.WAIT_ABANDONED
14+
Windows.Win32.Foundation.FreeLibrary
1415

1516
Windows.Win32.System.Com.SAFEARRAY
1617
Windows.Win32.System.Com.SAFEARRAYBOUND
@@ -19,6 +20,9 @@ Windows.Win32.System.Com.COINIT_MULTITHREADED
1920
Windows.Win32.System.Com.CoCreateInstance
2021
Windows.Win32.System.Com.CoInitializeEx
2122

23+
Windows.Win32.System.LibraryLoader.GetProcAddress
24+
Windows.Win32.System.LibraryLoader.LoadLibraryA
25+
2226
Windows.Win32.System.Pipes.PeekNamedPipe
2327

2428
Windows.Win32.System.Registry.RegCloseKey
@@ -31,9 +35,13 @@ Windows.Win32.System.Registry.KEY_READ
3135
Windows.Win32.System.Registry.KEY_WOW64_32KEY
3236
Windows.Win32.System.Registry.REG_SZ
3337

38+
Windows.Win32.System.SystemInformation.IMAGE_FILE_MACHINE_AMD64
39+
40+
Windows.Win32.System.Threading.GetMachineTypeAttributes
3441
Windows.Win32.System.Threading.ReleaseSemaphore
3542
Windows.Win32.System.Threading.WaitForSingleObject
3643
Windows.Win32.System.Threading.SEMAPHORE_MODIFY_STATE
3744
Windows.Win32.System.Threading.THREAD_SYNCHRONIZE
45+
Windows.Win32.System.Threading.UserEnabled
3846

3947
Windows.Win32.System.WindowsProgramming.OpenSemaphoreA

src/windows/com.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::windows::{
1515
},
1616
};
1717
use std::{
18+
convert::TryInto,
1819
ffi::{OsStr, OsString},
1920
mem::ManuallyDrop,
2021
ops::Deref,
@@ -24,7 +25,7 @@ use std::{
2425
};
2526

2627
pub fn initialize() -> Result<(), HRESULT> {
27-
let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED) };
28+
let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED.try_into().unwrap()) };
2829
if err != S_OK && err != S_FALSE {
2930
// S_FALSE just means COM is already initialized
3031
Err(err)

src/windows/find_tools.rs

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ mod impl_ {
163163
use crate::windows::registry::{RegistryKey, LOCAL_MACHINE};
164164
use crate::windows::setup_config::SetupConfiguration;
165165
use crate::windows::vs_instances::{VsInstances, VswhereInstance};
166+
use crate::windows::windows_sys::{
167+
FreeLibrary, GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
168+
IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
169+
};
166170
use std::convert::TryFrom;
167171
use std::env;
168172
use std::ffi::OsString;
@@ -173,6 +177,8 @@ mod impl_ {
173177
use std::path::{Path, PathBuf};
174178
use std::process::Command;
175179
use std::str::FromStr;
180+
use std::sync::atomic::{AtomicBool, Ordering};
181+
use std::sync::Once;
176182

177183
use super::MSVC_FAMILY;
178184
use crate::Tool;
@@ -199,6 +205,71 @@ mod impl_ {
199205
include: Vec<PathBuf>,
200206
}
201207

208+
struct LibraryHandle(HMODULE);
209+
210+
impl LibraryHandle {
211+
fn new(name: &[u8]) -> Option<Self> {
212+
let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
213+
(!handle.is_null()).then(|| Self(handle))
214+
}
215+
216+
/// Get a function pointer to a function in the library.
217+
/// SAFETY: The caller must ensure that the function signature matches the actual function.
218+
/// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the
219+
/// generated function for `func_signature`.
220+
unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
221+
let symbol = unsafe { GetProcAddress(self.0, name.as_ptr() as _) };
222+
symbol.map(|symbol| unsafe { mem::transmute_copy(&symbol) })
223+
}
224+
}
225+
226+
impl Drop for LibraryHandle {
227+
fn drop(&mut self) {
228+
unsafe { FreeLibrary(self.0) };
229+
}
230+
}
231+
232+
type GetMachineTypeAttributesFuncType =
233+
unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
234+
const _: () = {
235+
// Ensure that our hand-written signature matches the actual function signature.
236+
// We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to
237+
// it, which will fail to load on older versions of Windows.
238+
let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
239+
};
240+
241+
fn is_amd64_emulation_supported_inner() -> Option<bool> {
242+
// GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it.
243+
let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
244+
// SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature.
245+
let get_machine_type_attributes = unsafe {
246+
kernel32
247+
.get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
248+
}?;
249+
let mut attributes = Default::default();
250+
if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
251+
{
252+
Some((attributes & UserEnabled) != 0)
253+
} else {
254+
Some(false)
255+
}
256+
}
257+
258+
fn is_amd64_emulation_supported() -> bool {
259+
// TODO: Replace with a OnceLock once MSRV is 1.70.
260+
static LOAD_VALUE: Once = Once::new();
261+
static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
262+
263+
// Using Relaxed ordering since the Once is providing synchronization.
264+
LOAD_VALUE.call_once(|| {
265+
IS_SUPPORTED.store(
266+
is_amd64_emulation_supported_inner().unwrap_or(false),
267+
Ordering::Relaxed,
268+
);
269+
});
270+
IS_SUPPORTED.load(Ordering::Relaxed)
271+
}
272+
202273
impl MsvcTool {
203274
fn new(tool: PathBuf) -> MsvcTool {
204275
MsvcTool {
@@ -226,7 +297,6 @@ mod impl_ {
226297

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

485-
let host = match host_arch() {
486-
X86 => "X86",
487-
X86_64 => "X64",
488-
// There is no natively hosted compiler on ARM64.
489-
// Instead, use the x86 toolchain under emulation (there is no x64 emulation).
490-
AARCH64 => "X86",
555+
let hosts = match host_arch() {
556+
X86 => &["X86"],
557+
X86_64 => &["X64"],
558+
// Starting with VS 17.4, there is a natively hosted compiler on ARM64:
559+
// https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/
560+
// On older versions of VS, we use x64 if running under emulation is supported,
561+
// otherwise use x86.
562+
AARCH64 => {
563+
if is_amd64_emulation_supported() {
564+
&["ARM64", "X64", "X86"][..]
565+
} else {
566+
&["ARM64", "X86"]
567+
}
568+
}
491569
_ => return None,
492570
};
493571
let target = lib_subdir(target)?;
494572
// The directory layout here is MSVC/bin/Host$host/$target/
495573
let path = instance_path.join(r"VC\Tools\MSVC").join(version);
574+
// We use the first available host architecture that can build for the target
575+
let (host_path, host) = hosts.iter().find_map(|&x| {
576+
let candidate = path.join("bin").join(format!("Host{}", x));
577+
if candidate.join(target).exists() {
578+
Some((candidate, x))
579+
} else {
580+
None
581+
}
582+
})?;
496583
// This is the path to the toolchain for a particular target, running
497584
// on a given host
498-
let bin_path = path.join("bin").join(format!("Host{}", host)).join(target);
585+
let bin_path = host_path.join(target);
499586
// But! we also need PATH to contain the target directory for the host
500587
// architecture, because it contains dlls like mspdb140.dll compiled for
501588
// the host architecture.
502-
let host_dylib_path = path
503-
.join("bin")
504-
.join(format!("Host{}", host))
505-
.join(host.to_lowercase());
589+
let host_dylib_path = host_path.join(host.to_lowercase());
506590
let lib_path = path.join("lib").join(target);
507591
let alt_lib_path = (target == "arm64ec").then(|| path.join("lib").join("arm64ec"));
508592
let include_path = path.join("include");

0 commit comments

Comments
 (0)