blob: 93b10845ea511b365491cc9570518163181db169 [file] [edit]
//@ add-minicore
//@ min-llvm-version: 22
//@ assembly-output: emit-asm
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib -Copt-level=1
//@ needs-llvm-components: arm
#![crate_type = "lib"]
#![feature(abi_cmse_nonsecure_call, cmse_nonsecure_entry, no_core, lang_items)]
#![no_core]
// Test that padding and other uninitialized bytes are zeroed when a value crosses the secure
// boundary.
//
// The assembly uses the following instructions for clearing the bits:
//
// - `uxtb` clears bits 8..32
// - `uxth` clears bits 16..32
// - `bic` clears bits based on a mask
//
// When passing arguments the current implementation of the C ABI already clears padding sometimes,
// but it's not something that can be relied on.
extern crate minicore;
use minicore::*;
#[repr(C)]
struct InnerPadding {
a: u8,
b: u16,
}
// CHECK-LABEL: c_ret_with_inner_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "C" fn c_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding {
InnerPadding { a, b }
}
// CHECK-LABEL: cmse_ret_with_inner_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: uxtb r0, r0
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "cmse-nonsecure-entry" fn cmse_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding {
InnerPadding { a, b }
}
// CHECK-LABEL: c_call_with_inner_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: mov r2, r0
// CHECK-NEXT: bic r0, r1, #65280
// CHECK-NEXT: pop.w {r7, lr}
#[no_mangle]
extern "C" fn c_call_with_inner_padding(f: unsafe extern "C" fn(InnerPadding), x: InnerPadding) {
unsafe { f(x) }
}
// CHECK-LABEL: cmse_call_with_inner_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: mov r2, r0
// CHECK-NEXT: bic r0, r1, #65280
// CHECK-NEXT: push.w {r4, r5, r6, r7, r8, r9, r10, r11}
#[no_mangle]
extern "C" fn cmse_call_with_inner_padding(
f: unsafe extern "cmse-nonsecure-call" fn(InnerPadding),
x: InnerPadding,
) {
unsafe { f(x) }
}
#[repr(C)]
struct TrailingPadding {
a: u16,
b: u8,
}
// CHECK-LABEL: c_ret_with_trailing_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "C" fn c_ret_with_trailing_padding(a: u16, b: u8) -> TrailingPadding {
TrailingPadding { a, b }
}
// CHECK-LABEL: cmse_ret_with_trailing_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: uxtb r1, r1
// CHECK-NEXT: uxth r0, r0
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "cmse-nonsecure-entry" fn cmse_ret_with_trailing_padding(a: u16, b: u8) -> TrailingPadding {
TrailingPadding { a, b }
}
// CHECK-LABEL: c_call_with_trailing_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: mov r2, r0
// CHECK-NEXT: bic r0, r1, #-16777216
// CHECK-NEXT: pop.w {r7, lr}
#[no_mangle]
extern "C" fn c_call_with_trailing_padding(
f: unsafe extern "C" fn(TrailingPadding),
x: TrailingPadding,
) {
unsafe { f(x) }
}
// CHECK-LABEL: cmse_call_with_trailing_padding:
// CHECK: mov r7, sp
// CHECK-NEXT: mov r2, r0
// CHECK-NEXT: bic r0, r1, #-16777216
// CHECK-NEXT: push.w {r4, r5, r6, r7, r8, r9, r10, r11}
#[no_mangle]
extern "C" fn cmse_call_with_trailing_padding(
f: unsafe extern "cmse-nonsecure-call" fn(TrailingPadding),
x: TrailingPadding,
) {
unsafe { f(x) }
}
#[repr(C, align(2))]
struct WideU8 {
a: u8,
}
// `extern "C"` does not clear the padding.
//
// CHECK-LABEL: c_ret_with_wide_u8:
// CHECK: mov r7, sp
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "C" fn c_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] {
[WideU8 { a }, WideU8 { a: b }]
}
// Upper bits are cleared by uxtb.
//
// CHECK-LABEL: cmse_ret_with_wide_u8:
// CHECK: mov r7, sp
// CHECK-NEXT: uxtb r1, r1
// CHECK-NEXT: uxtb r0, r0
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] {
[WideU8 { a }, WideU8 { a: b }]
}
// Same idea, the padding is recognized even through the MaybeUninit.
//
// CHECK-LABEL: cmse_ret_with_wide_u8_uninit:
// CHECK: mov r7, sp
// CHECK-NEXT: uxtb r0, r0
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
// CHECK-NEXT: bic r0, r0, #-16711936
#[no_mangle]
extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit(
a: u16,
b: u16,
) -> [MaybeUninit<WideU8>; 2] {
unsafe { [mem::transmute(a), mem::transmute(b)] }
}
// Same idea, the padding is recognized even through the MaybeUninit.
//
// CHECK-LABEL: cmse_ret_with_wide_u8_uninit_tuple:
// CHECK: mov r7, sp
// CHECK-NEXT: uxtb r0, r0
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
// CHECK-NEXT: bic r0, r0, #-16711936
#[no_mangle]
extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit_tuple(
a: u16,
b: u16,
) -> (MaybeUninit<WideU8>, MaybeUninit<WideU8>) {
unsafe { (mem::transmute(a), mem::transmute(b)) }
}
// CHECK-LABEL: c_call_with_inner_wide_u8:
// CHECK: push {r7, lr}
// CHECK-NEXT: .setfp r7, sp
// CHECK-NEXT: mov r7, sp
// CHECK-NEXT: mov lr, r3
// CHECK-NEXT: mov r12, r0
// CHECK-NEXT: ldr r3, [r7, #8]
// CHECK-NEXT: mov r0, r1
// CHECK-NEXT: mov r1, r2
// CHECK-NEXT: mov r2, lr
// CHECK-NEXT: pop.w {r7, lr}
// CHECK-NEXT: bx r12
#[no_mangle]
extern "C" fn c_call_with_inner_wide_u8(f: unsafe extern "C" fn([WideU8; 8]), x: [WideU8; 8]) {
unsafe { f(x) }
}
// CHECK-LABEL: cmse_call_with_inner_wide_u8:
// CHECK: push {r7, lr}
// CHECK-NEXT: .setfp r7, sp
// CHECK-NEXT: mov r7, sp
// CHECK-NEXT: mov r12, r0
// CHECK-NEXT: bic r0, r1, #-16711936
// CHECK-NEXT: bic r1, r2, #-16711936
// CHECK-NEXT: bic r2, r3, #-16711936
// CHECK-NEXT: ldr r3, [r7, #8]
// CHECK-NEXT: bic r3, r3, #-16711936
// CHECK-NEXT: push.w {r4, r5, r6, r7, r8, r9, r10, r11}
#[no_mangle]
extern "C" fn cmse_call_with_inner_wide_u8(
f: unsafe extern "cmse-nonsecure-call" fn([WideU8; 8]),
x: [WideU8; 8],
) {
unsafe { f(x) }
}
/// No variant-dependent padding.
#[repr(C)]
enum VariantsSameSize {
A(u16),
B(u16),
}
impl Copy for VariantsSameSize {}
// CHECK-LABEL: variants_same_size:
// CHECK: mov r7, sp
// CHECK-NEXT: ldrh r1, [r0, #2]
// CHECK-NEXT: ldrb r0, [r0]
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "cmse-nonsecure-entry" fn variants_same_size(v: &VariantsSameSize) -> VariantsSameSize {
*v
}
/// One byte of variant-dependent padding.
#[repr(C)]
enum VariantsDifferentSize {
A(u8),
B(u16),
}
impl Copy for VariantsDifferentSize {}
// A uxtbeq conditionally clears the padding only for variant A.
//
// CHECK-LABEL: variants_different_size:
// CHECK: mov r7, sp
// CHECK-NEXT: ldrh r1, [r0, #2]
// CHECK-NEXT: ldrb r0, [r0]
// CHECK-NEXT: lsls r2, r0, #31
// CHECK-NEXT: it eq
// CHECK-NEXT: uxtbeq r1, r1
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "cmse-nonsecure-entry" fn variants_different_size(
v: &VariantsDifferentSize,
) -> VariantsDifferentSize {
*v
}
enum Void {}
impl Copy for Void {}
#[repr(C)]
enum UninhabitedVariant {
A(Void),
B(u16),
}
impl Copy for UninhabitedVariant {}
// Only `B` is inhabited, so reading the tag is not needed.
//
// CHECK-LABEL: uninhabited_variant:
// CHECK: mov r7, sp
// CHECK-NEXT: ldrh r1, [r0, #2]
// CHECK-NEXT: ldrb r0, [r0]
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
extern "cmse-nonsecure-entry" fn uninhabited_variant(v: &UninhabitedVariant) -> UninhabitedVariant {
*v
}
// The single guaranteed-padding byte is cleared with a `bic` mask over the whole loaded word.
// CHECK-LABEL: variants_same_size_array:
// CHECK: mov r7, sp
// CHECK-NEXT: ldr r0, [r0]
// CHECK-NEXT: bic r0, r0, #65280
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "cmse-nonsecure-entry" fn variants_same_size_array(
v: &[VariantsSameSize; 1],
) -> [VariantsSameSize; 1] {
*v
}
// CHECK-LABEL: variants_different_size_array:
// CHECK: mov r7, sp
// CHECK-NEXT: ldrh r1, [r0, #2]
// CHECK-NEXT: ldrb r0, [r0]
// CHECK-NEXT: lsls r2, r0, #31
// CHECK-NEXT: it eq
// CHECK-NEXT: uxtbeq r1, r1
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "cmse-nonsecure-entry" fn variants_different_size_array(
v: &[VariantsDifferentSize; 1],
) -> [VariantsDifferentSize; 1] {
*v
}
// CHECK-LABEL: variants_same_size_tuple:
// CHECK: mov r7, sp
// CHECK-NEXT: ldrh r1, [r0, #2]
// CHECK-NEXT: ldrb r0, [r0]
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "cmse-nonsecure-entry" fn variants_same_size_tuple(
v: &(VariantsSameSize,),
) -> (VariantsSameSize,) {
*v
}
// CHECK-LABEL: variants_different_size_tuple:
// CHECK: mov r7, sp
// CHECK-NEXT: ldrh r1, [r0, #2]
// CHECK-NEXT: ldrb r0, [r0]
// CHECK-NEXT: lsls r2, r0, #31
// CHECK-NEXT: it eq
// CHECK-NEXT: uxtbeq r1, r1
// CHECK-NEXT: orr.w r0, r0, r1, lsl #16
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "cmse-nonsecure-entry" fn variants_different_size_tuple(
v: &(VariantsDifferentSize,),
) -> (VariantsDifferentSize,) {
*v
}
/// Three variants of different sizes.
#[repr(C)]
enum ThreeVariants {
A(u8),
B(u16),
C(u32),
}
// CHECK-LABEL: cmse_call_three_variants:
// CHECK: mov r3, r0
//
// Clears padding in the extend the discriminant.
// CHECK-NEXT: uxtb r0, r1
//
// Uses uxtb for the A variant, uxtheq for the B variant,
// and nothing for the C variant.
//
// CHECK-NEXT: cbz r0, .LBB{{[0-9_]+}}
// CHECK-NEXT: cmp r0, #1
// CHECK-NEXT: it eq
// CHECK-NEXT: uxtheq r2, r2
// CHECK-NEXT: b .LBB{{[0-9_]+}}
// CHECK-NEXT: .LBB{{[0-9_]+}}:
// CHECK-NEXT: uxtb r2, r2
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "C" fn cmse_call_three_variants(
f: unsafe extern "cmse-nonsecure-call" fn(ThreeVariants),
x: ThreeVariants,
) {
unsafe { f(x) }
}
/// Three variants of different sizes, with a nested enum.
#[repr(C)]
enum ThreeVariantsNested {
A(u8),
B(Option<u8>),
C(u32),
}
// CHECK-LABEL: cmse_call_three_variants_nested:
// CHECK: mov r3, r0
//
// Clears padding in the extend the discriminant.
// CHECK-NEXT: uxtb r0, r1
//
// Match on the discriminant, jump to A block
//
// CHECK-NEXT: cbz r0, .LBB{{[0-9_]+}}
//
// Match on the discriminant, do nothing for C.
//
// CHECK-NEXT: cmp r0, #1
// CHECK-NEXT: bne .LBB{{[0-9_]+}}
//
// The B variant conditionally clears the Option payload.
//
// CHECK-NEXT: lsls r1, r2, #31
// CHECK-NEXT: movw r1, #65535
// CHECK-NEXT: it eq
// CHECK-NEXT: moveq r1, #254
// CHECK-NEXT: ands r2, r1
// CHECK-NEXT: b .LBB{{[0-9_]+}}
//
// The A variant uses uxtb.
//
// CHECK-NEXT: .LBB{{[0-9_]+}}:
// CHECK-NEXT: uxtb r2, r2
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "C" fn cmse_call_three_variants_nested(
f: unsafe extern "cmse-nonsecure-call" fn(ThreeVariantsNested),
x: ThreeVariantsNested,
) {
unsafe { f(x) }
}
/// The tag is stored in the niche of the `bool`.
#[repr(C)]
struct BoolU32 {
flag: bool,
val: u32,
}
// Bit 0b0010 is used to encode the tag, so `r0 - 2 == 0` checks the tag value.
// Zero-extension clears padding in the tag 32-bit word, the `val` is either 0
// (stored in r1) when None or the actual value r2 when Some.
//
// CHECK-LABEL: cmse_call_niche:
// CHECK: mov r7, sp
// CHECK-NEXT: mov r3, r0
// CHECK-NEXT: uxtb r0, r1
// CHECK-NEXT: subs r1, r0, #2
// CHECK-NEXT: it ne
// CHECK-NEXT: movne r1, r2
#[no_mangle]
#[expect(improper_ctypes_definitions)]
extern "C" fn cmse_call_niche(
f: unsafe extern "cmse-nonsecure-call" fn(Option<BoolU32>),
x: Option<BoolU32>,
) {
unsafe { f(x) }
}