Using Rust main from custom entry point (Part 3)
Even though GSoC 2022 has ended, I am still working on getting Rust std PR merged into the master. In the initial PR, I still used the entry function described in the prior post. However, this had many limitations, and thus I have finally transitioned to using a compiler-generated entry function.
Limitations of the older method
In the initial PR, I used an extern efi_main
function called the compiler-generated main
function. This had the following limitations:
- The
efi_main
function is generated in all projects using std. This means it will be present when using theno_main
attribute or library crate, thus diminishing the usefulness of std in libraries and drivers. It also rules out anything that does not want to use Heap allocation since Rust does some Heap allocations during the runtime setup. - Trying to pass pointers to
SystemTable
andImageHandle
as argv caused errors when building in release mode. This meant I was doing initialization from a function with no runtime setup and could thus cause all kinds of UB in case of panics. - Added a layer of redirection.
The new implementation
If you are unaware of everything that goes on before Rust main
, then you should read my previous post about this. I am replacing the compiler-generated C main
in the new implementation with the Win64 efi_main
function. Inspecting the LLVM-IR of a simple application, we can see the generated efi_main
:
; Function Attrs: noredzone nounwind
define win64cc i64 @efi_main(ptr %0, ptr %1) unnamed_addr #4 {
top:
%_9.i = alloca ptr, align 8
%2 = alloca [2 x ptr], align 8
store ptr %0, ptr %2, align 8
%3 = getelementptr inbounds ptr, ptr %2, i64 1
store ptr %1, ptr %3, align 8
call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %_9.i)
store ptr @_ZN11hello_world4main17h57c4996130ab67deE, ptr %_9.i, align 8
; call std::rt::lang_start_internal
%4 = call i64 @_ZN3std2rt19lang_start_internal17hd4c288149f777b4aE(ptr noundef nonnull align 1 %_9.i, ptr noalias noundef nonnull readonly align 8 dereferenceable(24) @vtable.0, i64 2, ptr nonnull %2, i8 2) #6
call void @llvm.lifetime.end.p0(i64 8, ptr nonnull %_9.i)
ret i64 %4
}
What this function does is pass an array [ImageHandle, *mut SystemTable]
as argv
and 2
as argc
to Rust lang_start
. We then initialize the global variables from Rust sys::init()
function.
Benefits of new implementation
no_main
attribute works as intended now sincestd::os::uefi::init()
is part of public API, this allows using Rust std in cases where we want to initialize something before the Heap is available (in some driver) but would still like to usestd
after initialization.- Allows UEFI to work like all other Rust platforms since
efi_main
is only generated in places where Cmain
would typically be generated. - It seems to improve startup speed.
Conclusion
With this implementation, one of the major blockers for the PR has been resolved. While implementing this, I also modified the Rust target specification to allow specifying the Entry Function name and ABI, which might enable making compiler-generated entry functions easier for other targets. Feel free to comment on the PR and follow it if interested.
Consider supporting me if you like my work.