@@ -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