@@ -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 ;
@@ -199,6 +203,64 @@ mod impl_ {
199203 include : Vec < PathBuf > ,
200204 }
201205
206+ struct LibraryHandle ( HMODULE ) ;
207+
208+ impl LibraryHandle {
209+ fn new ( name : & [ u8 ] ) -> Option < Self > {
210+ let handle = unsafe { LoadLibraryA ( name. as_ptr ( ) as _ ) } ;
211+ ( !handle. is_null ( ) ) . then ( || Self ( handle) )
212+ }
213+
214+ /// Get a function pointer to a function in the library.
215+ /// SAFETY: The caller must ensure that the function signature matches the actual function.
216+ /// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the
217+ /// generated function for `func_signature`.
218+ unsafe fn get_proc_address < F > ( & self , name : & [ u8 ] ) -> Option < F > {
219+ let symbol = unsafe { GetProcAddress ( self . 0 , name. as_ptr ( ) as _ ) } ;
220+ symbol. map ( |symbol| unsafe { mem:: transmute_copy ( & symbol) } )
221+ }
222+ }
223+
224+ impl Drop for LibraryHandle {
225+ fn drop ( & mut self ) {
226+ unsafe { FreeLibrary ( self . 0 ) } ;
227+ }
228+ }
229+
230+ type GetMachineTypeAttributesFuncType =
231+ unsafe extern "system" fn ( u16 , * mut MACHINE_ATTRIBUTES ) -> i32 ;
232+ const _: ( ) = {
233+ // Ensure that our hand-written signature matches the actual function signature.
234+ // We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to
235+ // it, which will fail to load on older versions of Windows.
236+ let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes ;
237+ } ;
238+
239+ fn is_amd64_emulation_supported ( ) -> bool {
240+ // GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it.
241+ if let Some ( kernel32) = LibraryHandle :: new ( b"kernel32.dll\0 " ) {
242+ // SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature.
243+ if let Some ( get_machine_type_attributes) = unsafe {
244+ kernel32. get_proc_address :: < GetMachineTypeAttributesFuncType > (
245+ b"GetMachineTypeAttributes\0 " ,
246+ )
247+ } {
248+ let mut attributes = Default :: default ( ) ;
249+ if unsafe { get_machine_type_attributes ( IMAGE_FILE_MACHINE_AMD64 , & mut attributes) }
250+ == S_OK
251+ {
252+ ( attributes & UserEnabled ) != 0
253+ } else {
254+ false
255+ }
256+ } else {
257+ false
258+ }
259+ } else {
260+ false
261+ }
262+ }
263+
202264 impl MsvcTool {
203265 fn new ( tool : PathBuf ) -> MsvcTool {
204266 MsvcTool {
@@ -226,7 +288,6 @@ mod impl_ {
226288
227289 /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the
228290 /// given target's arch. Returns `None` if the variable does not exist.
229- #[ cfg( windows) ]
230291 fn is_vscmd_target ( target : TargetArch < ' _ > ) -> Option < bool > {
231292 let vscmd_arch = env:: var ( "VSCMD_ARG_TGT_ARCH" ) . ok ( ) ?;
232293 // Convert the Rust target arch to its VS arch equivalent.
@@ -483,34 +544,41 @@ mod impl_ {
483544 let version = vs15plus_vc_read_version ( instance_path) ?;
484545
485546 let hosts = match host_arch ( ) {
486- X86 => vec ! [ "X86" ] ,
487- X86_64 => vec ! [ "X64" ] ,
488- // Starting with VS 17.3, there is a natively hosted compiler on ARM64.
489- // On older versions of VS, we use the x86 toolchain under emulation.
490- // We don't want to overcomplicate compatibility checks, so we ignore x64 emulation.
491- AARCH64 => vec ! [ "ARM64" , "X86" ] ,
547+ X86 => & [ "X86" ] ,
548+ X86_64 => & [ "X64" ] ,
549+ // Starting with VS 17.4, there is a natively hosted compiler on ARM64:
550+ // https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/
551+ // On older versions of VS, we use x64 if running under emulation is supported,
552+ // otherwise use x86.
553+ AARCH64 => {
554+ if is_amd64_emulation_supported ( ) {
555+ & [ "ARM64" , "X64" , "X86" ] [ ..]
556+ } else {
557+ & [ "ARM64" , "X86" ]
558+ }
559+ }
492560 _ => return None ,
493561 } ;
494562 let target = lib_subdir ( target) ?;
495563 // The directory layout here is MSVC/bin/Host$host/$target/
496564 let path = instance_path. join ( r"VC\Tools\MSVC" ) . join ( version) ;
497565 // We use the first available host architecture that can build for the target
498566 let ( host_path, host) = hosts. iter ( ) . find_map ( |& x| {
499- let candidate = path. join ( "bin" ) . join ( & format ! ( "Host{}" , x) ) ;
500- if candidate. join ( & target) . exists ( ) {
567+ let candidate = path. join ( "bin" ) . join ( format ! ( "Host{}" , x) ) ;
568+ if candidate. join ( target) . exists ( ) {
501569 Some ( ( candidate, x) )
502570 } else {
503571 None
504572 }
505573 } ) ?;
506574 // This is the path to the toolchain for a particular target, running
507575 // on a given host
508- let bin_path = host_path. join ( & target) ;
576+ let bin_path = host_path. join ( target) ;
509577 // But! we also need PATH to contain the target directory for the host
510578 // architecture, because it contains dlls like mspdb140.dll compiled for
511579 // the host architecture.
512- let host_dylib_path = host_path. join ( & host. to_lowercase ( ) ) ;
513- let lib_path = path. join ( "lib" ) . join ( & target) ;
580+ let host_dylib_path = host_path. join ( host. to_lowercase ( ) ) ;
581+ let lib_path = path. join ( "lib" ) . join ( target) ;
514582 let alt_lib_path = ( target == "arm64ec" ) . then ( || path. join ( "lib" ) . join ( "arm64ec" ) ) ;
515583 let include_path = path. join ( "include" ) ;
516584 Some ( (
0 commit comments