Skip to content

Commit

Permalink
Add tests for segmented stack.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ayak32 authored Aug 12, 2024
1 parent 4130236 commit 9f0dd1d
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/actions/build/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,19 @@ runs:
category: task
sub-category: unwind
test-name: deferred_nested_drop

# *** Tests for task - segmented stack ***

- name: Build test test-task-segmented_stack-function_arguments
uses: ./.github/workflows/actions/build-test
with:
category: task
sub-category: segmented_stack
test-name: function_arguments

- name: Build test test-task-segmented_stack-return_values
uses: ./.github/workflows/actions/build-test
with:
category: task
sub-category: segmented_stack
test-name: return_values
39 changes: 39 additions & 0 deletions .github/workflows/segmented_stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Run Tests for Segmented Stack

on:
workflow_call:
secrets:
cookie:
required: true

env:
CARGO_TERM_COLOR: always

jobs:
function_arguments:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Run test function_arguments
uses: ./.github/workflows/actions/run-test
with:
cookie: ${{ secrets.cookie }}
category: task
sub-category: segmented_stack
test-name: function_arguments

return_values:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Run test return_values
uses: ./.github/workflows/actions/run-test
with:
cookie: ${{ secrets.cookie }}
category: task
sub-category: segmented_stack
test-name: return_values
5 changes: 5 additions & 0 deletions .github/workflows/task.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ jobs:
uses: ./.github/workflows/unwind.yaml
secrets:
cookie: ${{ secrets.cookie }}

segmented_stack:
uses: ./.github/workflows/segmented_stack.yaml
secrets:
cookie: ${{ secrets.cookie }}
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,15 @@ path = "examples/tests/task/unwind/deferred_indirect_drop.rs"
[[example]]
name = "test-task-unwind-deferred_nested_drop"
path = "examples/tests/task/unwind/deferred_nested_drop.rs"

[[example]]
name = "test-task-segmented_stack-function_arguments"
path = "examples/tests/task/segmented_stack/function_arguments.rs"

[[example]]
name = "test-task-segmented_stack-nested_functions"
path = "examples/tests/task/segmented_stack/nested_functions.rs"

[[example]]
name = "test-task-segmented_stack-return_values"
path = "examples/tests/task/segmented_stack/return_values.rs"
133 changes: 133 additions & 0 deletions examples/tests/task/segmented_stack/function_arguments.rs
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions examples/tests/task/segmented_stack/function_arguments.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test Passed
106 changes: 106 additions & 0 deletions examples/tests/task/segmented_stack/return_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! 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, task};

#[naked]
extern "C" fn callee() {
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:",
// 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!(
// 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}}",

// Call the callee function.
"bl {callee}",

// verify return values in registers r0-r3
"cmp r0, #25",
"bne {error}",
"cmp r1, #26",
"bne {error}",
"cmp r2, #27",
"bne {error}",
"cmp r3, #28",
"bne {error}",

// Print success message.
"bl {success}",

// Return.
"pop {{pc}}",

callee = sym callee,
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(|| caller()).spawn().unwrap();
task::change_current_priority(10).unwrap();
semihosting::terminate(true);
}
1 change: 1 addition & 0 deletions examples/tests/task/segmented_stack/return_values.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test Passed

0 comments on commit 9f0dd1d

Please sign in to comment.