diff --git a/.github/workflows/actions/build/action.yaml b/.github/workflows/actions/build/action.yaml index 6f46ed7..b28f30b 100644 --- a/.github/workflows/actions/build/action.yaml +++ b/.github/workflows/actions/build/action.yaml @@ -160,12 +160,12 @@ runs: # *** Tests for task - segmented stack *** - - name: Build test test-task-segmented_stack-verify_regs_stack_new_function + - name: Build test test-task-segmented_stack-function_arguments uses: ./.github/workflows/actions/build-test with: category: task sub-category: segmented_stack - test-name: verify_regs_stack_new_function + test-name: function_aruments - name: Build test test-task-segmented_stack-nested_functions uses: ./.github/workflows/actions/build-test diff --git a/.github/workflows/segmented_stack.yaml b/.github/workflows/segmented_stack.yaml index ff2ab1d..55bca64 100644 --- a/.github/workflows/segmented_stack.yaml +++ b/.github/workflows/segmented_stack.yaml @@ -10,33 +10,19 @@ env: CARGO_TERM_COLOR: always jobs: - verify_regs_stack_new_function: + function_arguments: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Run test verify_regs_stack_new_function + - name: Run test function_arguments uses: ./.github/workflows/actions/run-test with: cookie: ${{ secrets.cookie }} category: task sub-category: segmented_stack - test-name: verify_regs_stack_new_function - - nested_functions: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Run test nested_functions - uses: ./.github/workflows/actions/run-test - with: - cookie: ${{ secrets.cookie }} - category: task - sub-category: segmented_stack - test-name: nested_functions + test-name: function_arguments return_values: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 2b5488c..12e7ac2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,8 +135,8 @@ name = "test-task-unwind-deferred_nested_drop" path = "examples/tests/task/unwind/deferred_nested_drop.rs" [[example]] -name = "test-task-segmented_stack-verify_regs_stack_new_function" -path = "examples/tests/task/segmented_stack/verify_regs_stack_new_function.rs" +name = "test-task-segmented_stack-function_arguments" +path = "examples/tests/task/segmented_stack/function_arguments.rs" [[example]] name = "test-task-segmented_stack-nested_functions" diff --git a/examples/tests/task/segmented_stack/function_arguments.rs b/examples/tests/task/segmented_stack/function_arguments.rs new file mode 100644 index 0000000..3ae261f --- /dev/null +++ b/examples/tests/task/segmented_stack/function_arguments.rs @@ -0,0 +1,133 @@ +//! Test function arguments passing via both registers and the stack when a new +//! stacklet is allocated for a function call. + +#![no_std] +#![no_main] +#![feature(naked_functions)] + +extern crate alloc; +use core::arch::asm; +use hopter::{boot::main, debug::semihosting, hprintln, task}; + +#[naked] +extern "C" fn prepare_regs_stack() { + unsafe { + asm!( + // Segmented stack prologue. + // Stacklet allocation size 36, 20 for preserved values plus 16 for + // stack arguments. + // Stack argument size 0. + "mov.w r12, #0x20000000", + "ldr.w r12, [r12]", + "subs.w r12, sp, r12", + "cmp.w r12, #36", + "bge.n 0f", + "svc #255", + ".short 9", + ".short 0", + + // Preserve callee-saved registers and return address. + "0:", + "push {{r4-r7, lr}}", + + // Prepare arguments in registers r0-r3. + "mov r0, #1", + "mov r1, #2", + "mov r2, #3", + "mov r3, #4", + + // Prepare arguments on stack. + "mov r4, #5", + "mov r5, #6", + "mov r6, #7", + "mov r7, #8", + "push {{r4-r7}}", + + // Call `verify_arguments` function. + "bl {verify_arguments}", + + // Discard stack arguments. + "add sp, #16", + + // Restore callee-saved registers and return. + "pop {{r4-r7, pc}}", + + verify_arguments = sym verify_arguments, + options(noreturn) + ) + } +} + +#[naked] +extern "C" fn verify_arguments() { + unsafe { + asm!( + // Segmented stack prologue. Request a huge stack frame of size + // 16384 bytes, which will very likely cause a new stacklet + // allocation. + // + // Stacklet allocation size 16384. + // Stack argument size 16. + "mov.w r12, #0x20000000", + "ldr.w r12, [r12]", + "subs.w r12, sp, r12", + "cmp.w r12, #16384", + "bge.n 0f", + "svc #255", + ".short 4096", + ".short 4", + + "0:", + // Verify register arguments. + "cmp r0, #1", + "bne {error}", + "cmp r1, #2", + "bne {error}", + "cmp r2, #3", + "bne {error}", + "cmp r3, #4", + "bne {error}", + + // Verify stack arguments. + "ldr r0, [sp, #0]", + "cmp r0, #5", + "bne {error}", + "ldr r0, [sp, #4]", + "cmp r0, #6", + "bne {error}", + "ldr r0, [sp, #8]", + "cmp r0, #7", + "bne {error}", + "ldr r0, [sp, #12]", + "cmp r0, #8", + "bne {error}", + + // Print success message. + // This also tests tail call optimization. + "b {success}", + + error = sym error, + success = sym success, + options(noreturn) + ) + } +} + +extern "C" fn success() { + hprintln!("Test Passed"); +} + +extern "C" fn error() { + hprintln!("Test Failed"); + semihosting::terminate(false); +} + +#[main] +fn main(_: cortex_m::Peripherals) { + task::build() + .set_entry(|| prepare_regs_stack()) + .spawn() + .unwrap(); + task::change_current_priority(10).unwrap(); + semihosting::terminate(true); +} diff --git a/examples/tests/task/segmented_stack/verify_regs_stack_new_function.txt b/examples/tests/task/segmented_stack/function_arguments.txt similarity index 100% rename from examples/tests/task/segmented_stack/verify_regs_stack_new_function.txt rename to examples/tests/task/segmented_stack/function_arguments.txt diff --git a/examples/tests/task/segmented_stack/nested_functions.rs b/examples/tests/task/segmented_stack/nested_functions.rs index 2af8045..8094f47 100644 --- a/examples/tests/task/segmented_stack/nested_functions.rs +++ b/examples/tests/task/segmented_stack/nested_functions.rs @@ -4,8 +4,7 @@ extern crate alloc; use core::arch::asm; -use hopter::{boot::main, debug::semihosting, hprintln, schedule}; - +use hopter::{boot::main, debug::semihosting, hprintln, task}; #[naked] extern "C" fn nested_function_calls() { @@ -21,9 +20,9 @@ extern "C" fn nested_function_calls() { ".short 4", /* function_arg_size (right shifted by 2) ; <- PC + 2 */ "1:", - "push {{lr}}", - "mov r0, #0", - "push {{r0}}", + "push {{lr}}", + "mov r0, #0", + "push {{r0}}", "bl {inner_function1}", "pop {{r0, lr}}", "bx lr", @@ -99,24 +98,23 @@ extern "C" fn verify_arguments() { "svc #255", /* otherwise, invoke SVC */ ".short 250", /* (right shifted by 2) <- preserved PC */ ".short 4", /* (right shifted by 2)" ; <- PC + 2 */ - - "1:", + + "1:", "push {{r4-r5, lr}}", /* verify arguments on stack */ "ldr r3, [sp, #12]", /* Check last pushed value (r3). Load the value into r3 */ - "cmp r3, #2", + "cmp r3, #2", "bne {error}", "ldr r4, [sp, #20]", /* Check second last pushed value (r2). Load the value into r4 */ - "cmp r4, #1", + "cmp r4, #1", "bne {error}", "ldr r5, [sp, #28]", /*Check third last pushed value (r1). Load the value into r5 */ - "cmp r5, #0", + "cmp r5, #0", "bne {error}", - "bl {print_success}", "pop {{r4-r5,lr}}", "bx lr", @@ -124,9 +122,7 @@ extern "C" fn verify_arguments() { error = sym error, print_success = sym success, options(noreturn) - ) - } } @@ -136,13 +132,15 @@ extern "C" fn success() { extern "C" fn error() { hprintln!("Test Failed"); + semihosting::terminate(false); } - - #[main] fn main(_: cortex_m::Peripherals) { - schedule::start_task(2, |_| nested_function_calls(), (), 0, 4).unwrap(); - schedule::change_current_task_priority(10).unwrap(); + task::build() + .set_entry(|| nested_function_calls()) + .spawn() + .unwrap(); + task::change_current_priority(10).unwrap(); semihosting::terminate(true); } diff --git a/examples/tests/task/segmented_stack/return_values.rs b/examples/tests/task/segmented_stack/return_values.rs index cbd6c47..c4c7ebd 100644 --- a/examples/tests/task/segmented_stack/return_values.rs +++ b/examples/tests/task/segmented_stack/return_values.rs @@ -1,56 +1,69 @@ +//! Test function return values via registers. +//! +//! FIXME: Does return value ever get passed through stack? + #![no_std] #![no_main] #![feature(naked_functions)] extern crate alloc; use core::arch::asm; -use hopter::{boot::main, debug::semihosting, hprintln, schedule}; - - +use hopter::{boot::main, debug::semihosting, hprintln, task}; #[naked] extern "C" fn callee() { unsafe { asm!( - "mov.w r12, #0x20000000", /* take stacklet boundary address */ - "ldr.w r12, [r12]", /* read stacklet boundary */ - "subs.w r12, sp, r12", /* calculate remaining free size */ - "cmp.w r12, #100", /* compare with requested size */ - "bge.n 1f", /* if enough free space, goto func_body */ - "svc #255", /* otherwise, invoke SVC */ - ".short 25", /* function_stack_size (right shifted by 2) <- preserved PC */ - ".short 4", /* function_arg_size (right shifted by 2) ; <- PC + 2 */ - - "1:", - // set r0-r3 to known values and return to caller - "mov r0, #25", - "mov r1, #26", - "mov r2, #27", - "mov r3, #28", + // Segmented stack prologue. Request a huge stack frame of size + // 16384 bytes, which will very likely cause a new stacklet + // allocation. + // + // Stacklet allocation size 16384. + // Stack argument size 16. + "mov.w r12, #0x20000000", + "ldr.w r12, [r12]", + "subs.w r12, sp, r12", + "cmp.w r12, #16384", + "bge.n 0f", + "svc #255", + ".short 4096", + ".short 4", + "0:", + // Set r0-r3 to known values as the return values. + "mov r0, #25", + "mov r1, #26", + "mov r2, #27", + "mov r3, #28", + // Return. "bx lr", - options(noreturn) ) } } - #[naked] extern "C" fn caller() { unsafe { asm!( - "mov.w r12, #0x20000000", /* take stacklet boundary address */ - "ldr.w r12, [r12]", /* read stacklet boundary */ - "subs.w r12, sp, r12", /* calculate remaining free size */ - "cmp.w r12, #100", /* compare with requested size */ - "bge.n 1f", /* if enough free space, goto func_body */ - "svc #255", /* otherwise, invoke SVC */ - ".short 25", /* function_stack_size (right shifted by 2) <- preserved PC */ - ".short 4", /* function_arg_size (right shifted by 2)" ; <- PC + 2 */ - - "1:", + // Segmented stack prologue. + // Stacklet allocation size 4 for preserved return address. + // stack arguments. + // Stack argument size 0. + "mov.w r12, #0x20000000", + "ldr.w r12, [r12]", + "subs.w r12, sp, r12", + "cmp.w r12, #4", + "bge.n 0f", + "svc #255", + ".short 1", + ".short 0", + + "0:", + // Preserve return address. "push {{lr}}", - "bl {callee_function}", + + // Call the callee function. + "bl {callee}", // verify return values in registers r0-r3 "cmp r0, #25", @@ -62,34 +75,32 @@ extern "C" fn caller() { "cmp r3, #28", "bne {error}", - "bl {print_success}", - "pop {{lr}}", - "bx lr", + // Print success message. + "bl {success}", - callee_function = sym callee, + // Return. + "pop {{pc}}", + + callee = sym callee, error = sym error, - print_success = sym success, + success = sym success, options(noreturn) - ) - } } - extern "C" fn success() { hprintln!("Test Passed"); } extern "C" fn error() { hprintln!("Test Failed"); + semihosting::terminate(false); } - - #[main] fn main(_: cortex_m::Peripherals) { - schedule::start_task(2, |_| caller(), (), 0, 4).unwrap(); - schedule::change_current_task_priority(10).unwrap(); + task::build().set_entry(|| caller()).spawn().unwrap(); + task::change_current_priority(10).unwrap(); semihosting::terminate(true); -} \ No newline at end of file +} diff --git a/examples/tests/task/segmented_stack/verify_regs_stack_new_function.rs b/examples/tests/task/segmented_stack/verify_regs_stack_new_function.rs deleted file mode 100644 index 1db3b2a..0000000 --- a/examples/tests/task/segmented_stack/verify_regs_stack_new_function.rs +++ /dev/null @@ -1,134 +0,0 @@ - -#![no_std] -#![no_main] -#![feature(naked_functions)] - -extern crate alloc; -use core::arch::asm; -use hopter::{boot::main, debug::semihosting, hprintln, schedule}; - - -#[naked] -extern "C" fn prepare_regs_stack() { - unsafe { - asm!( - "mov.w r12, #0x20000000", /* take stacklet boundary address */ - "ldr.w r12, [r12]", /* read stacklet boundary */ - "subs.w r12, sp, r12", /* calculate remaining free size */ - "cmp.w r12, #100", /* compare with requested size */ - "bge.n 1f", /* if enough free space, goto func_body */ - "svc #255", /* otherwise, invoke SVC */ - ".short 25", /* function_stack_size (right shifted by 2) <- preserved PC */ - ".short 4", /* function_arg_size (right shifted by 2) ; <- PC + 2 */ - - "1:", - "push {{r4-r7, lr}}", - /* Prepare arguments in registers */ - "mov r0, #1", - "mov r1, #2", - "mov r2, #3", - "mov r3, #4", - - - /* Prepare arguments on stack */ - "mov r4, #5", - "mov r5, #6", - "mov r6, #7", - "mov r7, #8", - "push {{r4, r5, r6, r7}}",/* Push values onto the stack in reverse order */ - - /* call verify_arguments function */ - "bl {verify_arguments}", - "pop {{r4, r5, r6, r7}}", - "pop {{r4-r7,lr}}", - "bx lr", - - verify_arguments = sym verify_arguments, - options(noreturn) - - ) - } -} - - - -#[naked] -extern "C" fn verify_arguments() { - unsafe { - asm!( - "mov.w r12, #0x20000000", /* take stacklet boundary address */ - "ldr.w r12, [r12]", /* read stacklet boundary */ - "subs.w r12, sp, r12", /* calculate remaining free size */ - "cmp.w r12, #1000", /* compare with requested size */ - "bge.n 2f", /* if enough free space, goto func_body */ - "svc #255", /* otherwise, invoke SVC */ - ".short 250", /* (right shifted by 2) <- preserved PC */ - ".short 4", /* (right shifted by 2)" ; <- PC + 2 */ - - "2:", - "push {{r4-r7, lr}}", - - /* verify arguments in registers */ - "cmp r0, #1", - "bne {error}", - "cmp r1, #2", - "bne {error}", - "cmp r2, #3", - "bne {error}", - "cmp r3, #4", - "bne {error}", - - /* verify arguments on stack */ - "ldr r8, [sp, #12]", /* Check last pushed value (r7). Load the value into r8 */ - "cmp r8, #8", - "bne {error}", - - "ldr r9, [sp, #8]", /* Check second last pushed value (r6). Load the value into r9 */ - "cmp r9, #7", - "bne {error}", - - "ldr r10, [sp, #4]", /*Check third last pushed value (r5). Load the value into r10 */ - "cmp r10, #6", - "bne {error}", - - "ldr r11, [sp, #0]", /* Check first pushed value (r4). Load the value into r11 */ - "cmp r11, #5", - "bne {error}", - - "bl {print_success}", - "pop {{r4-r7,lr}}", - "bx lr", - - - error = sym error, - print_success = sym success, - options(noreturn) - ) - } -} - -extern "C" fn success() { - hprintln!("Test Passed"); -} - -extern "C" fn error() { - hprintln!("Test Failed"); -} - - - - -#[main] -fn main(_: cortex_m::Peripherals) { - - schedule::start_task(2, |_| prepare_regs_stack(), (), 0, 4).unwrap(); - schedule::change_current_task_priority(10).unwrap(); - - semihosting::terminate(true); - -} - - - - -