Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Minimal Std implementation for UEFI #105861

Merged
merged 8 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3006,6 +3006,27 @@ dependencies = [
"proc-macro2",
]

[[package]]
name = "r-efi"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575fc2d9b3da54adbdfaddf6eca48fec256d977c8630a1750b8991347d1ac911"
dependencies = [
"compiler_builtins",
"rustc-std-workspace-core",
]

[[package]]
name = "r-efi-alloc"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d6f09fe2b6ad044bc3d2c34ce4979796581afd2f1ebc185837e02421e02fd7"
dependencies = [
"compiler_builtins",
"r-efi",
"rustc-std-workspace-core",
]

[[package]]
name = "rand"
version = "0.8.5"
Expand Down Expand Up @@ -5012,6 +5033,8 @@ dependencies = [
"panic_abort",
"panic_unwind",
"profiler_builtins",
"r-efi",
"r-efi-alloc",
"rand",
"rand_xorshift",
"rustc-demangle",
Expand Down
28 changes: 22 additions & 6 deletions compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,11 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
rust_main_def_id: DefId,
entry_type: EntryFnType,
) -> Bx::Function {
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
// depending on whether the target needs `argc` and `argv` to be passed in.
let llfty = if cx.sess().target.main_needs_argc_argv {
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`, or
// `usize efi_main(void *handle, void *system_table)` depending on the target.
let llfty = if cx.sess().target.os.contains("uefi") {
cx.type_func(&[cx.type_ptr(), cx.type_ptr()], cx.type_isize())
} else if cx.sess().target.main_needs_argc_argv {
cx.type_func(&[cx.type_int(), cx.type_ptr()], cx.type_int())
} else {
cx.type_func(&[], cx.type_int())
Expand Down Expand Up @@ -485,8 +487,12 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
};

let result = bx.call(start_ty, None, None, start_fn, &args, None);
let cast = bx.intcast(result, cx.type_int(), true);
bx.ret(cast);
if cx.sess().target.os.contains("uefi") {
bx.ret(result);
} else {
let cast = bx.intcast(result, cx.type_int(), true);
bx.ret(cast);
}

llfn
}
Expand All @@ -497,7 +503,17 @@ fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
cx: &'a Bx::CodegenCx,
bx: &mut Bx,
) -> (Bx::Value, Bx::Value) {
if cx.sess().target.main_needs_argc_argv {
if cx.sess().target.os.contains("uefi") {
// Params for UEFI
let param_handle = bx.get_param(0);
let param_system_table = bx.get_param(1);
let arg_argc = bx.const_int(cx.type_isize(), 2);
let arg_argv = bx.alloca(cx.type_array(cx.type_ptr(), 2), Align::ONE);
bx.store(param_handle, arg_argv, Align::ONE);
let arg_argv_el1 = bx.gep(cx.type_ptr(), arg_argv, &[bx.const_int(cx.type_int(), 1)]);
bx.store(param_system_table, arg_argv_el1, Align::ONE);
(arg_argc, arg_argv)
Ayush1325 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned before, you stuff random pointers into argc/argv of #[start]. Instead, we could make the argument to #[start] a struct (or union) that passes the platform arguments to #[start] and avoid passing random data in argv.

There is no way we can stabilize #[start] with this cross use. I am not convinced this is a good idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify what the concern is here, exactly? Are we worrying about conflicting with someone else trying to use argc and argv, basically?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is an inherent conflict. But we would effectively make it impossible to use #[start] without platform knowledge. Because any crate that uses #[start] must be adjusted to NOT assume argv to be a c-string array for some platforms.

Arguments passed to the individual startup routines differ strongly across platforms. Up until now #[start] simply got argc+argv in the unix-style iff the platform passed it, and "0/[0; 1]" (or "0/NULL", not sure) otherwise. Other arguments were just silently dropped, since the platform provides separate accessors (maybe I am wrong and there is precedence; but I didn't see any so far).

There is some discussion in the tracking issue of #[start] (#29633), and I feel like there is consensus in using a custom enum to pass the data. But this is not like anyone is working on it, or any urgent need to implement it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe complicating the use of an unstable feature which is, as you noted, fairly "stuck", is undesirable but not unexpected. I do not believe a slightly hacky implementation on this count is sufficient reason to bar this PR, although it ought to come with an appropriately huge // FIXME, unless you believe that this choice will somehow become impossible to overcome later.

} else if cx.sess().target.main_needs_argc_argv {
// Params from native `main()` used as args for rust start function
let param_argc = bx.get_param(0);
let param_argv = bx.get_param(1);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_target/src/spec/uefi_msvc_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub fn opts() -> TargetOptions {
stack_probes: StackProbeType::Call,
singlethread: true,
linker: Some("rust-lld".into()),
entry_name: "efi_main".into(),
..base
}
}
3 changes: 2 additions & 1 deletion compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
// The win64 ABI is used. It differs from the sysv64 ABI, so we must use a windows target with
// LLVM. "x86_64-unknown-windows" is used to get the minimal subset of windows-specific features.

use crate::spec::Target;
use crate::{abi::call::Conv, spec::Target};

pub fn target() -> Target {
let mut base = super::uefi_msvc_base::opts();
base.cpu = "x86-64".into();
base.plt_by_default = false;
base.max_atomic_width = Some(64);
base.entry_abi = Conv::X86_64Win64;
Ayush1325 marked this conversation as resolved.
Show resolved Hide resolved

// We disable MMX and SSE for now, even though UEFI allows using them. Problem is, you have to
// enable these CPU features explicitly before their first use, otherwise their instructions
Expand Down
3 changes: 2 additions & 1 deletion library/panic_abort/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ pub unsafe fn __rust_start_panic(_payload: &mut dyn PanicPayload) -> u32 {
}
} else if #[cfg(any(target_os = "hermit",
all(target_vendor = "fortanix", target_env = "sgx"),
target_os = "xous"
target_os = "xous",
target_os = "uefi",
))] {
unsafe fn abort() -> ! {
// call std::sys::abort_internal
Expand Down
4 changes: 4 additions & 0 deletions library/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ hermit-abi = { version = "0.3.2", features = ['rustc-dep-of-std'], public = true
[target.'cfg(target_os = "wasi")'.dependencies]
wasi = { version = "0.11.0", features = ['rustc-dep-of-std'], default-features = false }

[target.'cfg(target_os = "uefi")'.dependencies]
r-efi = { version = "4.2.0", features = ['rustc-dep-of-std']}
r-efi-alloc = { version = "1.0.0", features = ['rustc-dep-of-std']}

[features]
backtrace = [
"gimli-symbolize",
Expand Down
2 changes: 1 addition & 1 deletion library/std/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn main() {
|| target.contains("nto")
|| target.contains("xous")
|| target.contains("hurd")
|| target.contains("uefi")
// See src/bootstrap/synthetic_targets.rs
|| env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok()
{
Expand All @@ -51,7 +52,6 @@ fn main() {
// - mipsel-sony-psp
// - nvptx64-nvidia-cuda
// - arch=avr
// - uefi (x86_64-unknown-uefi, i686-unknown-uefi)
// - JSON targets
// - Any new targets that have not been explicitly added above.
println!("cargo:rustc-cfg=feature=\"restricted-std\"");
Expand Down
8 changes: 4 additions & 4 deletions library/std/src/io/error.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#[cfg(test)]
mod tests;

#[cfg(target_pointer_width = "64")]
#[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))]
mod repr_bitpacked;
#[cfg(target_pointer_width = "64")]
#[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))]
use repr_bitpacked::Repr;

#[cfg(not(target_pointer_width = "64"))]
#[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))]
mod repr_unpacked;
#[cfg(not(target_pointer_width = "64"))]
#[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))]
use repr_unpacked::Repr;

use crate::error;
Expand Down
2 changes: 2 additions & 0 deletions library/std/src/os/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ pub mod solid;
#[cfg(target_os = "tvos")]
#[path = "ios/mod.rs"]
pub(crate) mod tvos;
#[cfg(target_os = "uefi")]
pub mod uefi;
#[cfg(target_os = "vita")]
pub mod vita;
#[cfg(target_os = "vxworks")]
Expand Down
92 changes: 92 additions & 0 deletions library/std/src/os/uefi/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! UEFI-specific extensions to the primitives in `std::env` module

#![unstable(feature = "uefi_std", issue = "100499")]

use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::{ffi::c_void, ptr::NonNull};

static SYSTEM_TABLE: AtomicPtr<c_void> = AtomicPtr::new(crate::ptr::null_mut());
static IMAGE_HANDLE: AtomicPtr<c_void> = AtomicPtr::new(crate::ptr::null_mut());
// Flag to check if BootServices are still valid.
// Start with assuming that they are not available
static BOOT_SERVICES_FLAG: AtomicBool = AtomicBool::new(false);

/// Initializes the global System Table and Image Handle pointers.
///
/// The standard library requires access to the UEFI System Table and the Application Image Handle
/// to operate. Those are provided to UEFI Applications via their application entry point. By
/// calling `init_globals()`, those pointers are retained by the standard library for future use.
/// Thus this function must be called before any of the standard library services are used.
///
/// The pointers are never exposed to any entity outside of this application and it is guaranteed
/// that, once the application exited, these pointers are never dereferenced again.
///
/// Callers are required to ensure the pointers are valid for the entire lifetime of this
/// application. In particular, UEFI Boot Services must not be exited while an application with the
/// standard library is loaded.
///
/// # SAFETY
/// Calling this function more than once will panic
pub(crate) unsafe fn init_globals(handle: NonNull<c_void>, system_table: NonNull<c_void>) {
IMAGE_HANDLE
.compare_exchange(
crate::ptr::null_mut(),
handle.as_ptr(),
Ordering::Release,
Ordering::Acquire,
)
.unwrap();
SYSTEM_TABLE
.compare_exchange(
crate::ptr::null_mut(),
system_table.as_ptr(),
Ordering::Release,
Ordering::Acquire,
)
.unwrap();
BOOT_SERVICES_FLAG.store(true, Ordering::Release)
}
Ayush1325 marked this conversation as resolved.
Show resolved Hide resolved

/// Get the SystemTable Pointer.
/// If you want to use `BootServices` then please use [`boot_services`] as it performs some
/// additional checks.
///
/// Note: This function panics if the System Table or Image Handle is not initialized
pub fn system_table() -> NonNull<c_void> {
try_system_table().unwrap()
}

/// Get the ImageHandle Pointer.
///
/// Note: This function panics if the System Table or Image Handle is not initialized
pub fn image_handle() -> NonNull<c_void> {
try_image_handle().unwrap()
}

/// Get the BootServices Pointer.
/// This function also checks if `ExitBootServices` has already been called.
pub fn boot_services() -> Option<NonNull<c_void>> {
if BOOT_SERVICES_FLAG.load(Ordering::Acquire) {
let system_table: NonNull<r_efi::efi::SystemTable> = try_system_table()?.cast();
let boot_services = unsafe { (*system_table.as_ptr()).boot_services };
NonNull::new(boot_services).map(|x| x.cast())
} else {
None
}
}

/// Get the SystemTable Pointer.
/// This function is mostly intended for places where panic is not an option
pub(crate) fn try_system_table() -> Option<NonNull<c_void>> {
NonNull::new(SYSTEM_TABLE.load(Ordering::Acquire))
}

/// Get the SystemHandle Pointer.
Ayush1325 marked this conversation as resolved.
Show resolved Hide resolved
/// This function is mostly intended for places where panicking is not an option
pub(crate) fn try_image_handle() -> Option<NonNull<c_void>> {
NonNull::new(IMAGE_HANDLE.load(Ordering::Acquire))
}

pub(crate) fn disable_boot_services() {
BOOT_SERVICES_FLAG.store(false, Ordering::Release)
}
8 changes: 8 additions & 0 deletions library/std/src/os/uefi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Platform-specific extensions to `std` for UEFI.

#![unstable(feature = "uefi_std", issue = "100499")]
#![doc(cfg(target_os = "uefi"))]

pub mod env;
#[path = "../windows/ffi.rs"]
pub mod ffi;
2 changes: 1 addition & 1 deletion library/std/src/sys/common/thread_local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// "static" is for single-threaded platforms where a global static is sufficient.

cfg_if::cfg_if! {
if #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] {
if #[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] {
#[doc(hidden)]
mod static_local;
#[doc(hidden)]
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ cfg_if::cfg_if! {
} else if #[cfg(target_os = "xous")] {
mod xous;
pub use self::xous::*;
} else if #[cfg(target_os = "uefi")] {
mod uefi;
pub use self::uefi::*;
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
mod sgx;
pub use self::sgx::*;
Expand Down Expand Up @@ -114,4 +117,5 @@ pub fn log_wrapper<F: Fn(f64) -> f64>(n: f64, log_fn: F) -> f64 {
log_fn(n)
}

#[cfg(not(target_os = "uefi"))]
pub type RawOsError = i32;
33 changes: 33 additions & 0 deletions library/std/src/sys/uefi/alloc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Global Allocator for UEFI.
//! Uses [r-efi-alloc](https://crates.io/crates/r-efi-alloc)

use crate::alloc::{GlobalAlloc, Layout, System};

const MEMORY_TYPE: u32 = r_efi::efi::LOADER_DATA;

#[stable(feature = "alloc_system_type", since = "1.28.0")]
unsafe impl GlobalAlloc for System {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Return null pointer if boot services are not available
if crate::os::uefi::env::boot_services().is_none() {
return crate::ptr::null_mut();
}

// If boot services is valid then SystemTable is not null.
let system_table = crate::os::uefi::env::system_table().as_ptr().cast();
// The caller must ensure non-0 layout
unsafe { r_efi_alloc::raw::alloc(system_table, layout, MEMORY_TYPE) }
workingjubilee marked this conversation as resolved.
Show resolved Hide resolved
}

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// Do nothing if boot services are not available
if crate::os::uefi::env::boot_services().is_none() {
return;
}

// If boot services is valid then SystemTable is not null.
let system_table = crate::os::uefi::env::system_table().as_ptr().cast();
// The caller must ensure non-0 layout
unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) }
}
}
9 changes: 9 additions & 0 deletions library/std/src/sys/uefi/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod os {
pub const FAMILY: &str = "";
pub const OS: &str = "uefi";
pub const DLL_PREFIX: &str = "";
pub const DLL_SUFFIX: &str = "";
pub const DLL_EXTENSION: &str = "";
pub const EXE_SUFFIX: &str = ".efi";
pub const EXE_EXTENSION: &str = "efi";
}
Loading