@@ -163,6 +163,10 @@ mod impl_ {
163
163
use crate :: windows:: registry:: { RegistryKey , LOCAL_MACHINE } ;
164
164
use crate :: windows:: setup_config:: SetupConfiguration ;
165
165
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
+ } ;
166
170
use std:: convert:: TryFrom ;
167
171
use std:: env;
168
172
use std:: ffi:: OsString ;
@@ -173,6 +177,8 @@ mod impl_ {
173
177
use std:: path:: { Path , PathBuf } ;
174
178
use std:: process:: Command ;
175
179
use std:: str:: FromStr ;
180
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
181
+ use std:: sync:: Once ;
176
182
177
183
use super :: MSVC_FAMILY ;
178
184
use crate :: Tool ;
@@ -199,6 +205,71 @@ mod impl_ {
199
205
include : Vec < PathBuf > ,
200
206
}
201
207
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
+
202
273
impl MsvcTool {
203
274
fn new ( tool : PathBuf ) -> MsvcTool {
204
275
MsvcTool {
@@ -226,7 +297,6 @@ mod impl_ {
226
297
227
298
/// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the
228
299
/// given target's arch. Returns `None` if the variable does not exist.
229
- #[ cfg( windows) ]
230
300
fn is_vscmd_target ( target : TargetArch < ' _ > ) -> Option < bool > {
231
301
let vscmd_arch = env:: var ( "VSCMD_ARG_TGT_ARCH" ) . ok ( ) ?;
232
302
// Convert the Rust target arch to its VS arch equivalent.
@@ -482,27 +552,41 @@ mod impl_ {
482
552
) -> Option < ( PathBuf , PathBuf , PathBuf , PathBuf , Option < PathBuf > , PathBuf ) > {
483
553
let version = vs15plus_vc_read_version ( instance_path) ?;
484
554
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
+ }
491
569
_ => return None ,
492
570
} ;
493
571
let target = lib_subdir ( target) ?;
494
572
// The directory layout here is MSVC/bin/Host$host/$target/
495
573
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
+ } ) ?;
496
583
// This is the path to the toolchain for a particular target, running
497
584
// 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) ;
499
586
// But! we also need PATH to contain the target directory for the host
500
587
// architecture, because it contains dlls like mspdb140.dll compiled for
501
588
// 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 ( ) ) ;
506
590
let lib_path = path. join ( "lib" ) . join ( target) ;
507
591
let alt_lib_path = ( target == "arm64ec" ) . then ( || path. join ( "lib" ) . join ( "arm64ec" ) ) ;
508
592
let include_path = path. join ( "include" ) ;
0 commit comments