Skip to content
This repository has been archived by the owner on Jun 10, 2024. It is now read-only.

Commit

Permalink
Make swap_stack compatible with rust nightly
Browse files Browse the repository at this point in the history
Nightly no longer allows parameters to naked functions, it is seen as a bug that it error did since naked functions have no recognized calling convention, so naked functions need to be called from another `asm!` block that does the calling convention manually, so `swap_stack` is that function while `swap_stack_naked` is the `naked` function with just the `asm!` block.

`llvm_asm!` is no longer allowed in `naked` functions, so convert to `asm!`.  `llvm_asm!` used AT&T syntax by default, but `asm!` uses  Intel syntax.
  • Loading branch information
KronicDeth committed Feb 28, 2021
1 parent 3fc57b7 commit e4d59e9
Showing 1 changed file with 117 additions and 111 deletions.
228 changes: 117 additions & 111 deletions runtimes/minimal/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ impl Scheduler {
// function to perform some initial one-time setup to link
// call frames for the unwinder and call __lumen_trap_exceptions
let r13 = &process.registers.r13 as *const u64 as *mut _;
ptr::write(r13, 0xdeadbeef as u64);
ptr::write(r13, FIRST_SWAP);

// The function that __lumen_trap_exceptions will call as entry
let r14 = &process.registers.r14 as *const u64 as *mut _;
Expand All @@ -710,122 +710,128 @@ fn reset_reduction_counter() -> u64 {
//CURRENT_REDUCTION_COUNT.swap(0, Ordering::Relaxed)
}

/// `naked` functions can't take parameters because `naked` means it has a calling convention
/// that Rust can't understand, so they need a wrapper function (this function) to call the
/// naked function in asm
#[inline(never)]
#[unwind(allowed)]
#[cfg(all(unix, target_arch = "x86_64"))]
unsafe extern "C" fn swap_stack(prev: *mut CalleeSavedRegisters, new: *const CalleeSavedRegisters) {
asm!(
"call {}",
sym naked_swap_stack,
in("rdi") prev, in("rsi") new, in("rdx") FIRST_SWAP
)
}

const FIRST_SWAP: u64 = 0xdeadbeef;

/// This function uses inline assembly to save the callee-saved registers for the outgoing
/// process, and restore them for the incoming process. When this function returns, it will
/// resume execution where `swap_stack` was called previously.
#[naked]
#[inline(never)]
#[unwind(allowed)]
#[cfg(all(unix, target_arch = "x86_64"))]
unsafe extern "C" fn swap_stack(prev: *mut CalleeSavedRegisters, new: *const CalleeSavedRegisters) {
const FIRST_SWAP: u64 = 0xdeadbeef;
llvm_asm!("
# Save the return address to a register
leaq 0f(%rip), %rax
# Save the parent base pointer for when control returns to this call frame.
# CFA directives will inform the unwinder to expect %rbp at the bottom of the
# stack for this frame, so this should be the last value on the stack in the caller
pushq %rbp
# We also save %rbp and %rsp to registers so that we can setup CFA directives if this
# is the first swap for the target process
movq %rbp, %rcx
movq %rsp, %r9
# Save the stack pointer, and callee-saved registers of `prev`
movq %rsp, ($0)
movq %r15, 8($0)
movq %r14, 16($0)
movq %r13, 24($0)
movq %r12, 32($0)
movq %rbx, 40($0)
movq %rbp, 48($0)
# Restore the stack pointer, and callee-saved registers of `new`
movq ($1), %rsp
movq 8($1), %r15
movq 16($1), %r14
movq 24($1), %r13
movq 32($1), %r12
movq 40($1), %rbx
movq 48($1), %rbp
# The value of all the callee-saved registers has changed, so we
# need to inform the unwinder of that fact before proceeding
.cfi_restore %rsp
.cfi_restore %r15
.cfi_restore %r14
.cfi_restore %r13
.cfi_restore %r12
.cfi_restore %rbx
.cfi_restore %rbp
# If this is the first time swapping to this process,
# we need to to perform some one-time initialization to
# link the stack to the original parent stack (i.e. the scheduler),
# which is important for the unwinder
cmpq %r13, $2
jne ${:private}_resume
# Ensure we never perform initialization twice
movq $$0x0, %r13
# Store the original base pointer at the top of the stack
pushq %rcx
# Followed by the return address
pushq %rax
# Finally we store a pointer to the bottom of the stack in the
# parent call frame. The unwinder will expect to restore %rbp
# from this address
pushq %r9
# These CFI directives inform the unwinder of where it can expect
# to find the CFA relative to %rbp. This matches how we've laid out the stack.
#
# - The current %rbp is now 24 bytes (3 words) above %rsp.
# - 16 bytes _down_ from the current %rbp is the value from %r9 that
# we pushed, containing the parent call frame's stack pointer.
#
# The first directive tells the unwinder that it can expect to find the
# CFA (call frame address) 16 bytes above %rbp. The second directive then
# tells the unwinder that it can find the previous %rbp 16 bytes _down_
# from the current %rbp. The result is that the unwinder will restore %rbp
# from that stack slot, and will then expect to find the previous CFA 16 bytes
# above that address, allowing the unwinder to walk back into the parent frame
.cfi_def_cfa %rbp, 16
.cfi_offset %rbp, -16
# Now that the frames are linked, we can call the entry point. For now, this
# is __lumen_trap_exceptions, which expects to receive two arguments: the function
# being wrapped by the exception handler, and the value of the closure environment,
# _if_ it is a closure being called, otherwise the value of that argument is Term::NONE
movq %r14, %rdi
movq %r12, %rsi
# We have already set up the stack precisely, so we don't use callq here, instead
# we go ahead and jump straight to the beginning of the entry function.
# NOTE: This call never truly returns, as the exception handler calls __lumen_builtin_exit
# with the return value of the 'real' entry function, or with an exception if one
# is caught. However, swap_stack _does_ return for all other swaps, just not the first.
jmpq *%r15
${:private}_resume:
# We land here only on a context switch, and since the last switch _away_ from
# this process pushed %rbp on to the stack, and we don't need that value, we
# adjust the stack pointer accordingly.
add $$8, %rsp
# At this point we will return back to where execution left off:
# For the 'root' (scheduler) process, this returns back into `swap_process`;
# for all other processes, this returns to the code which was executing when
# it yielded, the address of which is 8 bytes above the current stack pointer.
# We pop and jmp rather than ret to avoid branch mispredictions.
popq %rax
jmpq *%rax
0:
"
:
: "{rdi}"(prev), "{rsi}"(new), "{rdx}"(FIRST_SWAP)
:
: "volatile", "alignstack"
unsafe extern "C" fn naked_swap_stack() {
asm!(
// Save the return address to a register
"lea rax, [rip+0f]",
// Save the parent base pointer for when control returns to this call frame.
// CFA directives will inform the unwinder to expect %rbp at the bottom of the
// stack for this frame, so this should be the last value on the stack in the caller
"push rbp",
// We also save %rbp and %rsp to registers so that we can setup CFA directives if this
// is the first swap for the target process
"mov rcx, rbp",
"mov r9, rsp",
// Save the stack pointer, and callee-saved registers of `prev` set in `rdi` by
// `swap_stack` above
"mov [rdi], rsp",
"mov [rdi+8], r15",
"mov [rdi+16], r14",
"mov [rdi+24], r13",
"mov [rdi+32], r12",
"mov [rdi+40], rbx",
"mov [rdi+48], rbp",
// Restore the stack pointer, and callee-saved registers of `new` set in `rsi` by
// `swap_stack` above
"mov rsp, [rsi]",
"mov r15, [rsi+8]",
"mov r14, [rsi+16]",
"mov r13, [rsi+24]",
"mov r12, [rsi+32]",
"mov rbx, [rsi+40]",
"mov rbp, [rsi+48]",
// The value of all the callee-saved registers has changed, so we
// need to inform the unwinder of that fact before proceeding
".cfi_restore rsp",
".cfi_restore r15",
".cfi_restore r14",
".cfi_restore r13",
".cfi_restore r12",
".cfi_restore rbx",
".cfi_restore rbp",
// If this is the first time swapping to this process,
// we need to to perform some one-time initialization to
// link the stack to the original parent stack (i.e. the scheduler),
// which is important for the unwinder
//
// cmp doesn't support 64-bit immediates, so the constant is put in `rdx` in `swap_stack`
// instead.
"cmp rdx, r13",
"jne .L1",
// Ensure we never perform initialization twice
"mov r13, 0x0",
// Store the original base pointer at the top of the stack
"push rcx",
// Followed by the return address
"push rax",
// Finally we store a pointer to the bottom of the stack in the
// parent call frame. The unwinder will expect to restore %rbp
// from this address
"push r9",
// These CFI directives inform the unwinder of where it can expect
// to find the CFA relative to %rbp. This matches how we've laid out the stack.
//
// - The current %rbp is now 24 bytes (3 words) above %rsp.
// - 16 bytes _down_ from the current %rbp is the value from %r9 that
// we pushed, containing the parent call frame's stack pointer.
//
// The first directive tells the unwinder that it can expect to find the
// CFA (call frame address) 16 bytes above %rbp. The second directive then
// tells the unwinder that it can find the previous %rbp 16 bytes _down_
// from the current %rbp. The result is that the unwinder will restore %rbp
// from that stack slot, and will then expect to find the previous CFA 16 bytes
// above that address, allowing the unwinder to walk back into the parent frame
".cfi_def_cfa rbp, 16",
".cfi_offset rbp, -16",
// Now that the frames are linked, we can call the entry point. For now, this
// is __lumen_trap_exceptions, which expects to receive two arguments: the function
// being wrapped by the exception handler, and the value of the closure environment,
// _if_ it is a closure being called, otherwise the value of that argument is Term::NONE
"mov rdi, r14",
"mov rsi, r12",
// We have already set up the stack precisely, so we don't use callq here, instead
// we go ahead and jump straight to the beginning of the entry function.
// NOTE: This call never truly returns, as the exception handler calls __lumen_builtin_exit
// with the return value of the 'real' entry function, or with an exception if one
// is caught. However, swap_stack _does_ return for all other swaps, just not the first.
"jmp r15",
"1:",
// We land here only on a context switch, and since the last switch _away_ from
// this process pushed %rbp on to the stack, and we don't need that value, we
// adjust the stack pointer accordingly.
"add rsp, 8",
// At this point we will return back to where execution left off:
// For the 'root' (scheduler) process, this returns back into `swap_process`;
// for all other processes, this returns to the code which was executing when
// it yielded, the address of which is 8 bytes above the current stack pointer.
// We pop and jmp rather than ret to avoid branch mispredictions.
"pop rax",
"jmp rax",
"0:",
// asm in naked functions must use `noreturn` option
options(noreturn)
);
}

0 comments on commit e4d59e9

Please sign in to comment.