|
| 1 | +// This defines the ia32 target for UEFI systems as described in the UEFI specification. See the |
| 2 | +// uefi-base module for generic UEFI options. On ia32 systems |
| 3 | +// UEFI systems always run in protected-mode, have the interrupt-controller pre-configured and |
| 4 | +// force a single-CPU execution. |
| 5 | +// The cdecl ABI is used. It differs from the stdcall or fastcall ABI. |
| 6 | +// "i686-unknown-windows" is used to get the minimal subset of windows-specific features. |
| 7 | + |
| 8 | +use crate::spec::{LinkerFlavor, LldFlavor, Target, TargetResult}; |
| 9 | + |
| 10 | +pub fn target() -> TargetResult { |
| 11 | + let mut base = super::uefi_base::opts(); |
| 12 | + base.cpu = "pentium4".to_string(); |
| 13 | + base.max_atomic_width = Some(64); |
| 14 | + |
| 15 | + // We disable MMX and SSE for now, even though UEFI allows using them. Problem is, you have to |
| 16 | + // enable these CPU features explicitly before their first use, otherwise their instructions |
| 17 | + // will trigger an exception. Rust does not inject any code that enables AVX/MMX/SSE |
| 18 | + // instruction sets, so this must be done by the firmware. However, existing firmware is known |
| 19 | + // to leave these uninitialized, thus triggering exceptions if we make use of them. Which is |
| 20 | + // why we avoid them and instead use soft-floats. This is also what GRUB and friends did so |
| 21 | + // far. |
| 22 | + // If you initialize FP units yourself, you can override these flags with custom linker |
| 23 | + // arguments, thus giving you access to full MMX/SSE acceleration. |
| 24 | + base.features = "-mmx,-sse,+soft-float".to_string(); |
| 25 | + |
| 26 | + // UEFI mirrors the calling-conventions used on windows. In case of i686 this means small |
| 27 | + // structs will be returned as int. This shouldn't matter much, since the restrictions placed |
| 28 | + // by the UEFI specifications forbid any ABI to return structures. |
| 29 | + base.abi_return_struct_as_int = true; |
| 30 | + |
| 31 | + // Use -GNU here, because of the reason below: |
| 32 | + // Backgound and Problem: |
| 33 | + // If we use i686-unknown-windows, the LLVM IA32 MSVC generates compiler intrinsic |
| 34 | + // _alldiv, _aulldiv, _allrem, _aullrem, _allmul, which will cause undefined symbol. |
| 35 | + // A real issue is __aulldiv() is refered by __udivdi3() - udivmod_inner!(), from |
| 36 | + // https://github.com/rust-lang-nursery/compiler-builtins. |
| 37 | + // As result, rust-lld generates link error finally. |
| 38 | + // Root-cause: |
| 39 | + // In rust\src\llvm-project\llvm\lib\Target\X86\X86ISelLowering.cpp, |
| 40 | + // we have below code to use MSVC intrinsics. It assumes MSVC target |
| 41 | + // will link MSVC library. But that is NOT true in UEFI environment. |
| 42 | + // UEFI does not link any MSVC or GCC standard library. |
| 43 | + // if (Subtarget.isTargetKnownWindowsMSVC() || |
| 44 | + // Subtarget.isTargetWindowsItanium()) { |
| 45 | + // // Setup Windows compiler runtime calls. |
| 46 | + // setLibcallName(RTLIB::SDIV_I64, "_alldiv"); |
| 47 | + // setLibcallName(RTLIB::UDIV_I64, "_aulldiv"); |
| 48 | + // setLibcallName(RTLIB::SREM_I64, "_allrem"); |
| 49 | + // setLibcallName(RTLIB::UREM_I64, "_aullrem"); |
| 50 | + // setLibcallName(RTLIB::MUL_I64, "_allmul"); |
| 51 | + // setLibcallCallingConv(RTLIB::SDIV_I64, CallingConv::X86_StdCall); |
| 52 | + // setLibcallCallingConv(RTLIB::UDIV_I64, CallingConv::X86_StdCall); |
| 53 | + // setLibcallCallingConv(RTLIB::SREM_I64, CallingConv::X86_StdCall); |
| 54 | + // setLibcallCallingConv(RTLIB::UREM_I64, CallingConv::X86_StdCall); |
| 55 | + // setLibcallCallingConv(RTLIB::MUL_I64, CallingConv::X86_StdCall); |
| 56 | + // } |
| 57 | + // The compiler intrisics should be implemented by compiler-builtins. |
| 58 | + // Unfortunately, compiler-builtins has not provided those intrinsics yet. Such as: |
| 59 | + // i386/divdi3.S |
| 60 | + // i386/lshrdi3.S |
| 61 | + // i386/moddi3.S |
| 62 | + // i386/muldi3.S |
| 63 | + // i386/udivdi3.S |
| 64 | + // i386/umoddi3.S |
| 65 | + // Possible solution: |
| 66 | + // 1. Eliminate Intrinsics generation. |
| 67 | + // 1.1 Choose differnt target to bypass isTargetKnownWindowsMSVC(). |
| 68 | + // 1.2 Remove the "Setup Windows compiler runtime calls" in LLVM |
| 69 | + // 2. Implement Intrinsics. |
| 70 | + // We evaluated all options. |
| 71 | + // #2 is hard because we need implement the intrinsics (_aulldiv) generated |
| 72 | + // from the other intrinscis (__udivdi3) implementation with the same |
| 73 | + // functionality (udivmod_inner). If we let _aulldiv() call udivmod_inner!(), |
| 74 | + // then we are in loop. We may have to find another way to implement udivmod_inner!(). |
| 75 | + // #1.2 may break the existing usage. |
| 76 | + // #1.1 seems the simplest solution today. |
| 77 | + // The IA32 -gnu calling convention is same as the one defined in UEFI specification. |
| 78 | + // It uses cdecl, EAX/ECX/EDX as volatile register, and EAX/EDX as return value. |
| 79 | + // We also checked the LLVM X86TargetLowering, the differences between -gnu and -msvc |
| 80 | + // is fmodf(f32), longjmp() and TLS. None of them impacts the UEFI code. |
| 81 | + // As a result, we choose -gnu for i686 version before those intrisics are implemented in |
| 82 | + // compiler-builtins. After compiler-builtins implements all required intrinsics, we may |
| 83 | + // remove -gnu and use the default one. |
| 84 | + Ok(Target { |
| 85 | + llvm_target: "i686-unknown-windows-gnu".to_string(), |
| 86 | + target_endian: "little".to_string(), |
| 87 | + target_pointer_width: "32".to_string(), |
| 88 | + target_c_int_width: "32".to_string(), |
| 89 | + data_layout: "e-m:x-p:32:32-i64:64-f80:32-n8:16:32-a:0:32-S32".to_string(), |
| 90 | + target_os: "uefi".to_string(), |
| 91 | + target_env: "".to_string(), |
| 92 | + target_vendor: "unknown".to_string(), |
| 93 | + arch: "x86".to_string(), |
| 94 | + linker_flavor: LinkerFlavor::Lld(LldFlavor::Link), |
| 95 | + |
| 96 | + options: base, |
| 97 | + }) |
| 98 | +} |
0 commit comments