From b1df52f2ba5085601b9797a133e19a694a16421c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 3 Jun 2026 13:59:41 +0200 Subject: [PATCH 1/4] add `TyAndLayout::uninit_ranges` --- compiler/rustc_abi/src/layout/ty.rs | 131 +++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index b09afc9ec8af6..9d53fb70c9b90 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -1,5 +1,5 @@ use std::fmt; -use std::ops::Deref; +use std::ops::{Deref, Range}; use rustc_data_structures::intern::Interned; use rustc_macros::StableHash; @@ -282,4 +282,133 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } found } + + pub fn uninit_ranges(&self, cx: &C) -> Vec> + where + Ty: TyAbiInterface<'a, C> + Copy, + { + let mut data = RangeSet(Vec::new()); + self.add_data_ranges(cx, Size::ZERO, &mut data); + + // Find gaps between the data ranges. + let mut uninit_ranges = Vec::new(); + let mut covered_until = Size::ZERO; + for &(offset, size) in data.0.iter() { + if offset > covered_until { + uninit_ranges.push(covered_until..offset); + } + covered_until = Ord::max(covered_until, offset + size); + } + + // Add trailing padding. + if self.size > covered_until { + uninit_ranges.push(covered_until..self.size); + } + + uninit_ranges + } + + /// Ranges of bytes that are initialized for some valid value of this type. In particular for + /// enums and unions there are offsets that are initialized for some variants but not for + /// others. + fn add_data_ranges(self, cx: &C, base_offset: Size, out: &mut RangeSet) + where + Ty: TyAbiInterface<'a, C> + Copy, + { + if self.is_zst() { + return; + } + + match &self.variants { + Variants::Empty => { /* done */ } + Variants::Single { index: _ } => match &self.fields { + FieldsShape::Primitive => { + out.add_range(base_offset, self.size); + } + &FieldsShape::Union(field_count) => { + for field in 0..field_count.get() { + let field = self.field(cx, field); + field.add_data_ranges(cx, base_offset, out); + } + } + &FieldsShape::Array { stride, count } => { + let elem = self.field(cx, 0); + + // For scalars we know there is no padding between the elements. + if elem.backend_repr.is_scalar() { + out.add_range(base_offset, elem.size * count); + } else { + // FIXME: this is really inefficient for large arrays. + for idx in 0..count { + elem.add_data_ranges(cx, base_offset + idx * stride, out); + } + } + } + FieldsShape::Arbitrary { offsets, in_memory_order: _ } => { + for (field, &offset) in offsets.iter_enumerated() { + let field = self.field(cx, field.as_usize()); + field.add_data_ranges(cx, base_offset + offset, out); + } + } + }, + Variants::Multiple { variants, .. } => { + for variant in variants.indices() { + let variant = self.for_variant(cx, variant); + variant.add_data_ranges(cx, base_offset, out); + } + } + } + } +} + +// FIXME: dedup with the one in +// `compiler/rustc_const_eval/src/interpret/validity.rs` +/// Represents a set of `Size` values as a sorted list of ranges. +// These are (offset, length) pairs, and they are sorted and mutually disjoint, +// and never adjacent (i.e. there's always a gap between two of them). +#[derive(Debug, Clone)] +struct RangeSet(Vec<(Size, Size)>); + +impl RangeSet { + fn add_range(&mut self, offset: Size, size: Size) { + if size.bytes() == 0 { + // No need to track empty ranges. + return; + } + let v = &mut self.0; + // We scan for a partition point where the left partition is all the elements that end + // strictly before we start. Those are elements that are too "low" to merge with us. + let idx = + v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset); + // Now we want to either merge with the first element of the second partition, or insert ourselves before that. + if let Some(&(other_offset, other_size)) = v.get(idx) + && offset + size >= other_offset + { + // Their end is >= our start (otherwise it would not be in the 2nd partition) and + // our end is >= their start. This means we can merge the ranges. + let new_start = other_offset.min(offset); + let mut new_end = (other_offset + other_size).max(offset + size); + // We grew to the right, so merge with overlapping/adjacent elements. + // (We also may have grown to the left, but that can never make us adjacent with + // anything there since we selected the first such candidate via `partition_point`.) + let mut scan_right = 1; + while let Some(&(next_offset, next_size)) = v.get(idx + scan_right) + && new_end >= next_offset + { + // Increase our size to absorb the next element. + new_end = new_end.max(next_offset + next_size); + // Look at the next element. + scan_right += 1; + } + // Update the element we grew. + v[idx] = (new_start, new_end - new_start); + // Remove the elements we absorbed (if any). + if scan_right > 1 { + drop(v.drain((idx + 1)..(idx + scan_right))); + } + } else { + // Insert new element. + v.insert(idx, (offset, size)); + } + } } From a691f47b2a2fc2477e1cae9f2b77fd51716f0719 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 3 Jun 2026 17:29:52 +0200 Subject: [PATCH 2/4] zero out padding in cmse entry return values --- compiler/rustc_codegen_ssa/src/mir/block.rs | 39 ++++++- tests/assembly-llvm/cmse-clear-padding.rs | 119 ++++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 tests/assembly-llvm/cmse-clear-padding.rs diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index b6b95c5f12aae..d5e6deeba1072 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1,6 +1,8 @@ use std::cmp; -use rustc_abi::{Align, BackendRepr, ExternAbi, HasDataLayout, Reg, Size, WrappingRange}; +use rustc_abi::{ + Align, ArmCall, BackendRepr, CanonAbi, ExternAbi, HasDataLayout, Reg, Size, WrappingRange, +}; use rustc_ast as ast; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_data_structures::packed::Pu128; @@ -533,6 +535,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => bug!("C-variadic function must have a `VaList` place"), } } + if self.fn_abi.ret.layout.is_uninhabited() { // Functions with uninhabited return values are marked `noreturn`, // so we should make sure that we never actually do. @@ -544,6 +547,40 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.unreachable(); return; } + + if self.fn_abi.conv == CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) + && let PassMode::Cast { cast, .. } = &self.fn_abi.ret.mode + { + // The return value of an `extern "cmse-nonsecure-entry"` function crosses the secure + // boundary. Ensure that stale information in padding or otherwise uninitialized memory + // does not leak across the secure boundary. + // + // We zero all bytes that are statically know to be uninitialized, we do not inspect the + // runtime value. + let ret_layout = self.fn_abi.ret.layout; + let uninit_ranges = ret_layout.uninit_ranges(bx.cx()); + if !uninit_ranges.is_empty() { + // Materialize the return value. + let tmp = PlaceRef::alloca(bx, ret_layout); + let op = self.codegen_consume(bx, mir::Place::return_place().as_ref()); + op.val.store(bx, tmp); + + let zero = bx.const_u8(0); + for range in uninit_ranges { + let len = bx.const_usize((range.end - range.start).bytes()); + let offset = bx.const_usize(range.start.bytes()); + + let ptr = bx.inbounds_ptradd(tmp.val.llval, offset); + bx.memset(ptr, zero, len, Align::ONE, MemFlags::empty()); + } + + // Load the value back and return. + let llval = load_cast(bx, cast, tmp.val.llval, ret_layout.align.abi); + bx.ret(llval); + return; + } + } + let llval = match &self.fn_abi.ret.mode { PassMode::Ignore | PassMode::Indirect { .. } => { bx.ret_void(); diff --git a/tests/assembly-llvm/cmse-clear-padding.rs b/tests/assembly-llvm/cmse-clear-padding.rs new file mode 100644 index 0000000000000..28be248027ba5 --- /dev/null +++ b/tests/assembly-llvm/cmse-clear-padding.rs @@ -0,0 +1,119 @@ +//@ 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 + +extern crate minicore; +use minicore::*; + +#[repr(C)] +pub 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] +pub 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] +pub extern "cmse-nonsecure-entry" fn cmse_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding { + InnerPadding { a, b } +} + +#[repr(C)] +pub 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] +pub 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] +pub extern "cmse-nonsecure-entry" fn cmse_ret_with_trailing_padding( + a: u16, + b: u8, +) -> TrailingPadding { + TrailingPadding { a, b } +} + +#[repr(C, align(2))] +pub struct WideU8 { + a: u8, +} + +// CHECK-LABEL: c_ret_with_wide_u8: +// CHECK: mov r7, sp +// CHECK-NEXT: orr.w r0, r0, r1, lsl #16 +#[no_mangle] +pub extern "C" fn c_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { + [WideU8 { a }, WideU8 { a: b }] +} + +// 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] +pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { + [WideU8 { a }, WideU8 { a: b }] +} + +// 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] +pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit( + a: u16, + b: u16, +) -> [MaybeUninit; 2] { + unsafe { [mem::transmute(a), mem::transmute(b)] } +} + +// 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] +pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit_tuple( + a: u16, + b: u16, +) -> (MaybeUninit, MaybeUninit) { + unsafe { (mem::transmute(a), mem::transmute(b)) } +} From f49423bc78fee0752c3df16f2a0116798c9d1e37 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 3 Jun 2026 19:28:21 +0200 Subject: [PATCH 3/4] zero out padding in cmse call arguments --- compiler/rustc_codegen_ssa/src/mir/block.rs | 91 ++++++++++------ tests/assembly-llvm/cmse-clear-padding.rs | 113 +++++++++++++++++--- 2 files changed, 156 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index d5e6deeba1072..c01146f87757b 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1,4 +1,5 @@ use std::cmp; +use std::ops::Range; use rustc_abi::{ Align, ArmCall, BackendRepr, CanonAbi, ExternAbi, HasDataLayout, Reg, Size, WrappingRange, @@ -548,39 +549,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { return; } - if self.fn_abi.conv == CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) - && let PassMode::Cast { cast, .. } = &self.fn_abi.ret.mode - { - // The return value of an `extern "cmse-nonsecure-entry"` function crosses the secure - // boundary. Ensure that stale information in padding or otherwise uninitialized memory - // does not leak across the secure boundary. - // - // We zero all bytes that are statically know to be uninitialized, we do not inspect the - // runtime value. - let ret_layout = self.fn_abi.ret.layout; - let uninit_ranges = ret_layout.uninit_ranges(bx.cx()); - if !uninit_ranges.is_empty() { - // Materialize the return value. - let tmp = PlaceRef::alloca(bx, ret_layout); - let op = self.codegen_consume(bx, mir::Place::return_place().as_ref()); - op.val.store(bx, tmp); - - let zero = bx.const_u8(0); - for range in uninit_ranges { - let len = bx.const_usize((range.end - range.start).bytes()); - let offset = bx.const_usize(range.start.bytes()); - - let ptr = bx.inbounds_ptradd(tmp.val.llval, offset); - bx.memset(ptr, zero, len, Align::ONE, MemFlags::empty()); - } - - // Load the value back and return. - let llval = load_cast(bx, cast, tmp.val.llval, ret_layout.align.abi); - bx.ret(llval); - return; - } - } - let llval = match &self.fn_abi.ret.mode { PassMode::Ignore | PassMode::Indirect { .. } => { bx.ret_void(); @@ -622,6 +590,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } ZeroSized => bug!("ZST return value shouldn't be in PassMode::Cast"), }; + + if self.fn_abi.conv == CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) { + // The return value of an `extern "cmse-nonsecure-entry"` function crosses the secure + // boundary. Zero padding and uninitialized bytes so information does not leak. + let ret_layout = self.fn_abi.ret.layout; + let uninit_ranges = ret_layout.uninit_ranges(bx.cx()); + self.zero_byte_ranges(bx, llslot, ret_layout.size, &uninit_ranges); + } + load_cast(bx, cast_ty, llslot, self.fn_abi.ret.layout.align.abi) } }; @@ -1363,6 +1340,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.codegen_argument( bx, + fn_abi.conv, op, by_move, &mut llargs, @@ -1373,6 +1351,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let num_untupled = untuple.map(|tup| { self.codegen_arguments_untupled( bx, + fn_abi.conv, &tup.node, &mut llargs, &fn_abi.args[first_args.len()..], @@ -1402,6 +1381,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let last_arg = fn_abi.args.last().unwrap(); self.codegen_argument( bx, + fn_abi.conv, location, /* by_move */ false, &mut llargs, @@ -1718,9 +1698,31 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } + fn zero_byte_ranges( + &mut self, + bx: &mut Bx, + ptr: Bx::Value, + limit: Size, + ranges: &[Range], + ) { + let zero = bx.const_u8(0); + + for range in ranges { + let end = cmp::min(range.end, limit); + if range.start >= end { + continue; + } + let offset = bx.const_usize(range.start.bytes()); + let len = bx.const_usize((end - range.start).bytes()); + let ptr = bx.inbounds_ptradd(ptr, offset); + bx.memset(ptr, zero, len, Align::ONE, MemFlags::empty()); + } + } + fn codegen_argument( &mut self, bx: &mut Bx, + conv: CanonAbi, op: OperandRef<'tcx, Bx::Value>, by_move: bool, llargs: &mut Vec, @@ -1843,6 +1845,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { MemFlags::empty(), None, ); + + // Arguments cross the secure boundary, so zero padding and uninitialized bytes. + if conv == CanonAbi::Arm(ArmCall::CCmseNonSecureCall) { + self.zero_byte_ranges( + bx, + llscratch, + Size::from_bytes(copy_bytes), + &arg.layout.uninit_ranges(bx.cx()), + ); + } + // ...and then load it with the ABI type. llval = load_cast(bx, cast, llscratch, scratch_align); bx.lifetime_end(llscratch, scratch_size); @@ -1869,6 +1882,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { fn codegen_arguments_untupled( &mut self, bx: &mut Bx, + conv: CanonAbi, operand: &mir::Operand<'tcx>, llargs: &mut Vec, args: &[ArgAbi<'tcx, Ty<'tcx>>], @@ -1888,6 +1902,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let field = bx.load_operand(field_ptr); self.codegen_argument( bx, + conv, field, by_move, llargs, @@ -1899,7 +1914,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // If the tuple is immediate, the elements are as well. for i in 0..tuple.layout.fields.count() { let op = tuple.extract_field(self, bx, i); - self.codegen_argument(bx, op, by_move, llargs, &args[i], lifetime_ends_after_call); + self.codegen_argument( + bx, + conv, + op, + by_move, + llargs, + &args[i], + lifetime_ends_after_call, + ); } } tuple.layout.fields.count() diff --git a/tests/assembly-llvm/cmse-clear-padding.rs b/tests/assembly-llvm/cmse-clear-padding.rs index 28be248027ba5..b092bf304dcca 100644 --- a/tests/assembly-llvm/cmse-clear-padding.rs +++ b/tests/assembly-llvm/cmse-clear-padding.rs @@ -15,12 +15,15 @@ // - `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)] -pub struct InnerPadding { +struct InnerPadding { a: u8, b: u16, } @@ -29,7 +32,7 @@ pub struct InnerPadding { // CHECK: mov r7, sp // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 #[no_mangle] -pub extern "C" fn c_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding { +extern "C" fn c_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding { InnerPadding { a, b } } @@ -38,12 +41,35 @@ pub extern "C" fn c_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding { // CHECK-NEXT: uxtb r0, r0 // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 #[no_mangle] -pub extern "cmse-nonsecure-entry" fn cmse_ret_with_inner_padding(a: u8, b: u16) -> InnerPadding { +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)] -pub struct TrailingPadding { +struct TrailingPadding { a: u16, b: u8, } @@ -52,7 +78,7 @@ pub struct TrailingPadding { // CHECK: mov r7, sp // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 #[no_mangle] -pub extern "C" fn c_ret_with_trailing_padding(a: u16, b: u8) -> TrailingPadding { +extern "C" fn c_ret_with_trailing_padding(a: u16, b: u8) -> TrailingPadding { TrailingPadding { a, b } } @@ -62,15 +88,38 @@ pub extern "C" fn c_ret_with_trailing_padding(a: u16, b: u8) -> TrailingPadding // CHECK-NEXT: uxth r0, r0 // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 #[no_mangle] -pub extern "cmse-nonsecure-entry" fn cmse_ret_with_trailing_padding( - a: u16, - b: u8, -) -> TrailingPadding { +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))] -pub struct WideU8 { +struct WideU8 { a: u8, } @@ -78,7 +127,7 @@ pub struct WideU8 { // CHECK: mov r7, sp // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 #[no_mangle] -pub extern "C" fn c_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { +extern "C" fn c_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { [WideU8 { a }, WideU8 { a: b }] } @@ -88,17 +137,53 @@ pub extern "C" fn c_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { // CHECK-NEXT: uxtb r0, r0 // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 #[no_mangle] -pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { +extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8(a: u8, b: u8) -> [WideU8; 2] { [WideU8 { a }, WideU8 { a: 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) } +} + // 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] -pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit( +extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit( a: u16, b: u16, ) -> [MaybeUninit; 2] { @@ -111,7 +196,7 @@ pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit( // CHECK-NEXT: orr.w r0, r0, r1, lsl #16 // CHECK-NEXT: bic r0, r0, #-16711936 #[no_mangle] -pub extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit_tuple( +extern "cmse-nonsecure-entry" fn cmse_ret_with_wide_u8_uninit_tuple( a: u16, b: u16, ) -> (MaybeUninit, MaybeUninit) { From 3be4beefb63b5dfc6325179955df25f4738fc7b7 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 3 Jun 2026 22:13:47 +0200 Subject: [PATCH 4/4] deduplicate `RangeSet` --- compiler/rustc_abi/src/layout/ty.rs | 57 +----------------- compiler/rustc_abi/src/lib.rs | 2 +- .../src/interpret/validity.rs | 54 +---------------- compiler/rustc_data_structures/src/lib.rs | 1 + .../rustc_data_structures/src/range_set.rs | 58 +++++++++++++++++++ 5 files changed, 66 insertions(+), 106 deletions(-) create mode 100644 compiler/rustc_data_structures/src/range_set.rs diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 9d53fb70c9b90..f1f02d2a31809 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -2,6 +2,7 @@ use std::fmt; use std::ops::{Deref, Range}; use rustc_data_structures::intern::Interned; +use rustc_data_structures::range_set::RangeSet; use rustc_macros::StableHash; use crate::layout::{FieldIdx, VariantIdx}; @@ -287,7 +288,7 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { where Ty: TyAbiInterface<'a, C> + Copy, { - let mut data = RangeSet(Vec::new()); + let mut data = RangeSet::new(); self.add_data_ranges(cx, Size::ZERO, &mut data); // Find gaps between the data ranges. @@ -311,7 +312,7 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { /// Ranges of bytes that are initialized for some valid value of this type. In particular for /// enums and unions there are offsets that are initialized for some variants but not for /// others. - fn add_data_ranges(self, cx: &C, base_offset: Size, out: &mut RangeSet) + fn add_data_ranges(self, cx: &C, base_offset: Size, out: &mut RangeSet) where Ty: TyAbiInterface<'a, C> + Copy, { @@ -360,55 +361,3 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } } } - -// FIXME: dedup with the one in -// `compiler/rustc_const_eval/src/interpret/validity.rs` -/// Represents a set of `Size` values as a sorted list of ranges. -// These are (offset, length) pairs, and they are sorted and mutually disjoint, -// and never adjacent (i.e. there's always a gap between two of them). -#[derive(Debug, Clone)] -struct RangeSet(Vec<(Size, Size)>); - -impl RangeSet { - fn add_range(&mut self, offset: Size, size: Size) { - if size.bytes() == 0 { - // No need to track empty ranges. - return; - } - let v = &mut self.0; - // We scan for a partition point where the left partition is all the elements that end - // strictly before we start. Those are elements that are too "low" to merge with us. - let idx = - v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset); - // Now we want to either merge with the first element of the second partition, or insert ourselves before that. - if let Some(&(other_offset, other_size)) = v.get(idx) - && offset + size >= other_offset - { - // Their end is >= our start (otherwise it would not be in the 2nd partition) and - // our end is >= their start. This means we can merge the ranges. - let new_start = other_offset.min(offset); - let mut new_end = (other_offset + other_size).max(offset + size); - // We grew to the right, so merge with overlapping/adjacent elements. - // (We also may have grown to the left, but that can never make us adjacent with - // anything there since we selected the first such candidate via `partition_point`.) - let mut scan_right = 1; - while let Some(&(next_offset, next_size)) = v.get(idx + scan_right) - && new_end >= next_offset - { - // Increase our size to absorb the next element. - new_end = new_end.max(next_offset + next_size); - // Look at the next element. - scan_right += 1; - } - // Update the element we grew. - v[idx] = (new_start, new_end - new_start); - // Remove the elements we absorbed (if any). - if scan_right > 1 { - drop(v.drain((idx + 1)..(idx + scan_right))); - } - } else { - // Insert new element. - v.insert(idx, (offset, size)); - } - } -} diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index 166c8bea6f354..a5a4411146c44 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -792,7 +792,7 @@ impl FromStr for Endian { } /// Size of a type in bytes. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[cfg_attr(feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash))] pub struct Size { raw: u64, diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 249a65e228245..0a897ac3f2673 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -345,55 +345,7 @@ fn write_path(out: &mut String, path: &[PathElem<'_>]) { } } -/// Represents a set of `Size` values as a sorted list of ranges. -// These are (offset, length) pairs, and they are sorted and mutually disjoint, -// and never adjacent (i.e. there's always a gap between two of them). -#[derive(Debug, Clone)] -pub struct RangeSet(Vec<(Size, Size)>); - -impl RangeSet { - fn add_range(&mut self, offset: Size, size: Size) { - if size.bytes() == 0 { - // No need to track empty ranges. - return; - } - let v = &mut self.0; - // We scan for a partition point where the left partition is all the elements that end - // strictly before we start. Those are elements that are too "low" to merge with us. - let idx = - v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset); - // Now we want to either merge with the first element of the second partition, or insert ourselves before that. - if let Some(&(other_offset, other_size)) = v.get(idx) - && offset + size >= other_offset - { - // Their end is >= our start (otherwise it would not be in the 2nd partition) and - // our end is >= their start. This means we can merge the ranges. - let new_start = other_offset.min(offset); - let mut new_end = (other_offset + other_size).max(offset + size); - // We grew to the right, so merge with overlapping/adjacent elements. - // (We also may have grown to the left, but that can never make us adjacent with - // anything there since we selected the first such candidate via `partition_point`.) - let mut scan_right = 1; - while let Some(&(next_offset, next_size)) = v.get(idx + scan_right) - && new_end >= next_offset - { - // Increase our size to absorb the next element. - new_end = new_end.max(next_offset + next_size); - // Look at the next element. - scan_right += 1; - } - // Update the element we grew. - v[idx] = (new_start, new_end - new_start); - // Remove the elements we absorbed (if any). - if scan_right > 1 { - drop(v.drain((idx + 1)..(idx + scan_right))); - } - } else { - // Insert new element. - v.insert(idx, (offset, size)); - } - } -} +pub type RangeSet = rustc_data_structures::range_set::RangeSet; struct ValidityVisitor<'rt, 'tcx, M: Machine<'tcx>> { /// The `path` may be pushed to, but the part that is present when a function @@ -1190,7 +1142,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { assert!(layout.is_sized(), "there are no unsized unions"); let layout_cx = LayoutCx::new(*ecx.tcx, ecx.typing_env); return M::cached_union_data_range(ecx, layout.ty, || { - let mut out = RangeSet(Vec::new()); + let mut out = RangeSet::new(); union_data_range_uncached(&layout_cx, layout, Size::ZERO, &mut out); out }); @@ -1640,7 +1592,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ctfe_mode, ecx, reset_provenance_and_padding, - data_bytes: reset_padding.then_some(RangeSet(Vec::new())), + data_bytes: reset_padding.then_some(RangeSet::new()), may_dangle: start_in_may_dangle, }; v.visit_value(val)?; diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs index be8538acd30eb..73b0dbd1ceeba 100644 --- a/compiler/rustc_data_structures/src/lib.rs +++ b/compiler/rustc_data_structures/src/lib.rs @@ -68,6 +68,7 @@ pub mod obligation_forest; pub mod owned_slice; pub mod packed; pub mod profiling; +pub mod range_set; pub mod sharded; pub mod small_c_str; pub mod snapshot_map; diff --git a/compiler/rustc_data_structures/src/range_set.rs b/compiler/rustc_data_structures/src/range_set.rs new file mode 100644 index 0000000000000..19ce00c4cd2fd --- /dev/null +++ b/compiler/rustc_data_structures/src/range_set.rs @@ -0,0 +1,58 @@ +/// Represents a set of `Size` values as a sorted list of ranges. +// These are (offset, length) pairs, and they are sorted and mutually disjoint, +// and never adjacent (i.e. there's always a gap between two of them). +#[derive(Debug, Clone)] +pub struct RangeSet(pub Vec<(T, T)>); + +impl RangeSet +where + T: Copy + Ord + Default, + T: core::ops::Add, + T: core::ops::Sub, +{ + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn add_range(&mut self, offset: T, size: T) { + if size == T::default() { + // No need to track empty ranges. + return; + } + let v = &mut self.0; + // We scan for a partition point where the left partition is all the elements that end + // strictly before we start. Those are elements that are too "low" to merge with us. + let idx = + v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset); + // Now we want to either merge with the first element of the second partition, or insert ourselves before that. + if let Some(&(other_offset, other_size)) = v.get(idx) + && offset + size >= other_offset + { + // Their end is >= our start (otherwise it would not be in the 2nd partition) and + // our end is >= their start. This means we can merge the ranges. + let new_start = other_offset.min(offset); + let mut new_end = (other_offset + other_size).max(offset + size); + // We grew to the right, so merge with overlapping/adjacent elements. + // (We also may have grown to the left, but that can never make us adjacent with + // anything there since we selected the first such candidate via `partition_point`.) + let mut scan_right = 1; + while let Some(&(next_offset, next_size)) = v.get(idx + scan_right) + && new_end >= next_offset + { + // Increase our size to absorb the next element. + new_end = new_end.max(next_offset + next_size); + // Look at the next element. + scan_right += 1; + } + // Update the element we grew. + v[idx] = (new_start, new_end - new_start); + // Remove the elements we absorbed (if any). + if scan_right > 1 { + drop(v.drain((idx + 1)..(idx + scan_right))); + } + } else { + // Insert new element. + v.insert(idx, (offset, size)); + } + } +}