From a50dc3f11f2431f45b92c3700a9071fd488990ed Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 25 May 2026 23:00:18 -0400 Subject: [PATCH 01/40] Implement basic triplication of rad protected calls --- .../src/rad_protected_analysis.rs | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index ebe33b2b1cc23..968d6a56b2c1a 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -3,10 +3,11 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir::find_attr; use rustc_middle::mir::{ - Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, + Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind }; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::mir::{BasicBlock, Terminator, BasicBlockData}; pub(super) struct RadProtectedAnalysis; @@ -18,6 +19,9 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let def_id = body.source.def_id(); + let protected_blocks = get_protected_blocks(tcx, body); + triplicate_protected_calls(body, protected_blocks); + // print_calls_to_protected_functions(tcx, body); if !find_attr!(tcx, def_id, RadProtected(_)) { @@ -422,3 +426,62 @@ fn resolve_pointer_source<'tcx>( } } } + +// Get basic blocks that have a rad protected terminator +fn get_protected_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> Vec { + let mut protected_blocks = Vec::new(); + + for (bb_idx, bb_data) in body.basic_blocks.iter_enumerated() { + if let Some(terminator) = &bb_data.terminator { + if let TerminatorKind::Call { func, .. } = &terminator.kind { + if let Some(callee_def_id) = called_def_id(func) { + if find_attr!(tcx, callee_def_id, RadProtected(_)) { + protected_blocks.push(bb_idx); + } + } + } + } + } + + protected_blocks +} + +// Given a list of rad protected basic blocks, triplicate the callsites +fn triplicate_protected_calls<'tcx>(body: &mut Body<'tcx>, protected_blocks: Vec) { + + // Helper to create a duplicated call block given the original terminator and its target + fn create_duplicated_call_block<'tcx>(call_1_terminator: &Terminator<'tcx>, new_target: BasicBlock) -> BasicBlockData<'tcx> { + let mut duplicated_terminator: rustc_middle::mir::Terminator<'_> = call_1_terminator.clone(); + + if let TerminatorKind::Call { target, .. } = &mut duplicated_terminator.kind { + *target = Some(new_target); + } + + BasicBlockData::new(Some(duplicated_terminator), false) + } + + for bb_idx in protected_blocks.into_iter() { + + let call_1_target = match &body.basic_blocks[bb_idx].terminator().kind { + TerminatorKind::Call { target: Some(t), .. } => *t, + _ => continue, + }; + + let call_1_terminator = body.basic_blocks[bb_idx].terminator().clone(); + + // Add call 3, where its target is call 1's target + let bb3_data = create_duplicated_call_block(&call_1_terminator, call_1_target); + let bb3 = body.basic_blocks_mut().push(bb3_data); + + // Add call 2, where its target is call 3 + let bb2_data = create_duplicated_call_block(&call_1_terminator, bb3); + let bb2 = body.basic_blocks_mut().push(bb2_data); + + // Change call 1's target to call 2 + if let Some(terminator) = &mut body.basic_blocks_mut()[bb_idx].terminator { + if let TerminatorKind::Call { target, .. } = &mut terminator.kind { + *target = Some(bb2); + } + } + } +} From 9db59c9c53662321cecb97fc2371649237cee7c3 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 28 May 2026 21:30:53 -0400 Subject: [PATCH 02/40] Rename rad_protected attr to rad_protected_mir - This frees the name to use for the AST triplication attribute macro --- compiler/rustc_attr_parsing/src/attributes/rad_protected.rs | 2 +- compiler/rustc_feature/src/builtin_attrs.rs | 5 +++-- compiler/rustc_hir/src/attrs/data_structures.rs | 2 +- compiler/rustc_span/src/symbol.rs | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/rad_protected.rs b/compiler/rustc_attr_parsing/src/attributes/rad_protected.rs index 7e2d545d61faf..53dae45c36b79 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rad_protected.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rad_protected.rs @@ -8,7 +8,7 @@ use crate::target_checking::{ALL_TARGETS, AllowedTargets}; pub(crate) struct RadProtectedParser; impl NoArgsAttributeParser for RadProtectedParser { - const PATH: &[Symbol] = &[sym::rad_protected]; + const PATH: &[Symbol] = &[sym::rad_protected_mir]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index fe2642d508ddd..404ed11152d9a 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -903,9 +903,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::Yes, pin_ergonomics, experimental!(pin_v2), ), - // Radshield protection attribute (ungated for easy testing) + // Radshield MIR pass protection attribute (ungated for easy testing) + // This attribute is used internally by rad_protected, and is not intended for use by the user ungated!( - rad_protected, Normal, template!(Word), WarnFollowing, + rad_protected_mir, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, ), diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 470d3816b9bae..3cce89b071950 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1234,7 +1234,7 @@ pub enum AttributeKind { /// Represents `#[profiler_runtime]` ProfilerRuntime, - /// Represents `#[rad_protected]` + /// Represents `#[rad_protected_mir]` RadProtected(Span), /// Represents [`#[recursion_limit]`](https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 28b3bb2824456..03944f0e3852f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1591,6 +1591,7 @@ symbols! { question_mark, quote, rad_protected, + rad_protected_mir, range_inclusive_new, raw_dash_dylib: "raw-dylib", raw_dylib, From eaee1c16ca6b240dc143bfba5690a6d0df205beb Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Fri, 29 May 2026 14:59:34 -0400 Subject: [PATCH 03/40] Add rad_protected attribute macro template --- compiler/rustc_builtin_macros/src/lib.rs | 2 ++ compiler/rustc_builtin_macros/src/rad_protected.rs | 13 +++++++++++++ library/core/src/macros/mod.rs | 8 ++++++++ library/core/src/prelude/v1.rs | 3 +++ 4 files changed, 26 insertions(+) create mode 100644 compiler/rustc_builtin_macros/src/rad_protected.rs diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index f671f59f983a3..36a40fe8fdeae 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -45,6 +45,7 @@ mod global_allocator; mod iter; mod log_syntax; mod pattern_type; +mod rad_protected; mod source_util; mod test; mod trace_macros; @@ -118,6 +119,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { eii_declaration: eii::eii_declaration, eii_shared_macro: eii::eii_shared_macro, global_allocator: global_allocator::expand, + rad_protected: rad_protected::triplicate, test: test::expand_test, test_case: test::expand_test_case, unsafe_eii: eii::unsafe_eii, diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected.rs new file mode 100644 index 0000000000000..4bc593b1e9a75 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/rad_protected.rs @@ -0,0 +1,13 @@ +use rustc_ast::{self as ast}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::Span; + +pub(crate) fn triplicate( + _cx: &mut ExtCtxt<'_>, + _span: Span, + _meta_item: &ast::MetaItem, + _item: Annotatable, +) -> Vec { + // TODO: + todo!() +} diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index c2d714d2b7877..bbbf8d842131e 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1789,6 +1789,14 @@ pub(crate) mod builtin { /* compiler built-in */ } + + /// Attribute macro that implements radiation hardening through triplication + #[stable(feature = "rad_protected", since = "1.95.0")] + #[rustc_builtin_macro] + pub macro rad_protected($item:item) { + /* compiler built-in */ + } + /// Attribute macro applied to a function to give it a post-condition. /// /// The attribute carries an argument token-tree which is diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index f2eb047d342bc..1f8eda726b7fb 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -166,3 +166,6 @@ pub use crate::macros::builtin::{eii, unsafe_eii}; #[unstable(feature = "eii_internals", issue = "none")] pub use crate::macros::builtin::eii_declaration; + +#[stable(feature = "rad_protected_builtin_macro", since = "1.95.0")] +pub use crate::macros::builtin::rad_protected; From d69697db501d243d49b8472b3a40f5d57a84502c Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Fri, 29 May 2026 19:57:18 -0400 Subject: [PATCH 04/40] Revert "Implement basic triplication of rad protected calls" This reverts commit a50dc3f11f2431f45b92c3700a9071fd488990ed. --- .../src/rad_protected_analysis.rs | 65 +------------------ 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index 968d6a56b2c1a..ebe33b2b1cc23 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -3,11 +3,10 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir::find_attr; use rustc_middle::mir::{ - Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind + Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, }; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, TyCtxt}; -use rustc_middle::mir::{BasicBlock, Terminator, BasicBlockData}; pub(super) struct RadProtectedAnalysis; @@ -19,9 +18,6 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let def_id = body.source.def_id(); - let protected_blocks = get_protected_blocks(tcx, body); - triplicate_protected_calls(body, protected_blocks); - // print_calls_to_protected_functions(tcx, body); if !find_attr!(tcx, def_id, RadProtected(_)) { @@ -426,62 +422,3 @@ fn resolve_pointer_source<'tcx>( } } } - -// Get basic blocks that have a rad protected terminator -fn get_protected_blocks<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> Vec { - let mut protected_blocks = Vec::new(); - - for (bb_idx, bb_data) in body.basic_blocks.iter_enumerated() { - if let Some(terminator) = &bb_data.terminator { - if let TerminatorKind::Call { func, .. } = &terminator.kind { - if let Some(callee_def_id) = called_def_id(func) { - if find_attr!(tcx, callee_def_id, RadProtected(_)) { - protected_blocks.push(bb_idx); - } - } - } - } - } - - protected_blocks -} - -// Given a list of rad protected basic blocks, triplicate the callsites -fn triplicate_protected_calls<'tcx>(body: &mut Body<'tcx>, protected_blocks: Vec) { - - // Helper to create a duplicated call block given the original terminator and its target - fn create_duplicated_call_block<'tcx>(call_1_terminator: &Terminator<'tcx>, new_target: BasicBlock) -> BasicBlockData<'tcx> { - let mut duplicated_terminator: rustc_middle::mir::Terminator<'_> = call_1_terminator.clone(); - - if let TerminatorKind::Call { target, .. } = &mut duplicated_terminator.kind { - *target = Some(new_target); - } - - BasicBlockData::new(Some(duplicated_terminator), false) - } - - for bb_idx in protected_blocks.into_iter() { - - let call_1_target = match &body.basic_blocks[bb_idx].terminator().kind { - TerminatorKind::Call { target: Some(t), .. } => *t, - _ => continue, - }; - - let call_1_terminator = body.basic_blocks[bb_idx].terminator().clone(); - - // Add call 3, where its target is call 1's target - let bb3_data = create_duplicated_call_block(&call_1_terminator, call_1_target); - let bb3 = body.basic_blocks_mut().push(bb3_data); - - // Add call 2, where its target is call 3 - let bb2_data = create_duplicated_call_block(&call_1_terminator, bb3); - let bb2 = body.basic_blocks_mut().push(bb2_data); - - // Change call 1's target to call 2 - if let Some(terminator) = &mut body.basic_blocks_mut()[bb_idx].terminator { - if let TerminatorKind::Call { target, .. } = &mut terminator.kind { - *target = Some(bb2); - } - } - } -} From 74545763a912eb437aae3a936ea3e9fd1ee597a3 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Fri, 29 May 2026 20:09:10 -0400 Subject: [PATCH 05/40] Implement triplication of both callsites and function bodies --- .../rustc_builtin_macros/src/rad_protected.rs | 103 ++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected.rs index 4bc593b1e9a75..af1fd2624add8 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected.rs @@ -1,13 +1,102 @@ -use rustc_ast::{self as ast}; +use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::Span; +use rustc_span::{Span, symbol::Ident, sym}; +use thin_vec::{thin_vec, ThinVec}; pub(crate) fn triplicate( - _cx: &mut ExtCtxt<'_>, - _span: Span, + cx: &mut ExtCtxt<'_>, + span: Span, _meta_item: &ast::MetaItem, - _item: Annotatable, + item: Annotatable, ) -> Vec { - // TODO: - todo!() + + let Annotatable::Item(box ast::Item { + kind: ast::ItemKind::Fn(box ref func), + ref vis, + .. + }) = item else { + cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions"); + return vec![item]; + }; + + let func_body = match &func.body { + Some(b) => b, + None => { + cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions with a body"); + return vec![item]; + } + }; + + let make_ident = |suffix_num: usize| { + Ident::from_str_and_span( + &format!("__{}_{}", func.ident.name, suffix_num), + span + ) + }; + + let make_inner_fn_stmt = |suffix_num: usize| { + + let inner_fn = ast::Fn { + defaultness: ast::Defaultness::Implicit, + ident: make_ident(suffix_num), + generics: func.generics.clone(), + sig: func.sig.clone(), + contract: None, + define_opaque: None, + body: Some(func_body.clone()), + eii_impls: thin_vec![] + }; + + let item = cx.item(span, ast::AttrVec::new(), ast::ItemKind::Fn(Box::new(inner_fn))); + cx.stmt_item(span, item) + }; + + let call_args: ThinVec<_> = func.sig.decl.inputs.iter().filter_map(|param| { + match ¶m.pat.kind { + ast::PatKind::Ident(_, ident, _) => Some(cx.expr_ident(span, *ident)), + _ => { + cx.dcx().span_err( + param.pat.span, + "unsupported parameter pattern in `#[rad_protected]`" + ); + None + } + } + }).collect(); + + let make_call_stmt = |suffix_num: usize| { + let call = cx.expr_call_ident(span, make_ident(suffix_num), call_args.clone()); + + cx.stmt_semi(call) + }; + + const NUM_DUPLICATES: usize = 3; + + let mut wrapper_stmts: ThinVec = thin_vec![]; + + wrapper_stmts.extend((1..=NUM_DUPLICATES).map(make_inner_fn_stmt)); + wrapper_stmts.extend((1..=NUM_DUPLICATES).map(make_call_stmt)); + + let wrapper_body = cx.block(span, wrapper_stmts); + + let mut wrapper_fn = ast::Fn { + defaultness: func.defaultness, + ident: func.ident, + sig: func.sig.clone(), + generics: func.generics.clone(), + body: Some(wrapper_body), + contract: func.contract.clone(), + define_opaque: func.define_opaque.clone(), + eii_impls: func.eii_impls.clone() + }; + + // The return type is temporarily the unit type until voting is implemented + wrapper_fn.sig.decl.output = ast::FnRetTy::Default(span); + + let mir_attr = cx.attr_word(sym::rad_protected_mir, span); + + let mut wrapper = cx.item(span, thin_vec![mir_attr], ast::ItemKind::Fn(Box::new(wrapper_fn))); + wrapper.vis = vis.clone(); + + vec![Annotatable::Item(wrapper)] } From ae31a8c6f58517b598ee5772f348b19e1d0daea2 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Sun, 31 May 2026 15:18:48 -0400 Subject: [PATCH 06/40] Implement voting for results of triplicated functions --- .../rustc_builtin_macros/src/rad_protected.rs | 29 +++++++++++++------ library/std/src/lib.rs | 4 +++ library/std/src/rad_protected.rs | 29 +++++++++++++++++++ 3 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 library/std/src/rad_protected.rs diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected.rs index af1fd2624add8..0d6990554ab0d 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected.rs @@ -64,10 +64,8 @@ pub(crate) fn triplicate( } }).collect(); - let make_call_stmt = |suffix_num: usize| { - let call = cx.expr_call_ident(span, make_ident(suffix_num), call_args.clone()); - - cx.stmt_semi(call) + let make_call_expr = |suffix_num: usize| { + cx.expr_call_ident(span, make_ident(suffix_num), call_args.clone()) }; const NUM_DUPLICATES: usize = 3; @@ -75,11 +73,27 @@ pub(crate) fn triplicate( let mut wrapper_stmts: ThinVec = thin_vec![]; wrapper_stmts.extend((1..=NUM_DUPLICATES).map(make_inner_fn_stmt)); - wrapper_stmts.extend((1..=NUM_DUPLICATES).map(make_call_stmt)); + + let vote_path = cx.path_global( + span, + vec![ + Ident::new(sym::std, span), + Ident::new(sym::rad_protected, span), + Ident::from_str_and_span("vote", span) + ], + ); + + let vote_args: ThinVec<_> = (1..=NUM_DUPLICATES).map(make_call_expr).collect(); + + let vote_expr = cx.expr_path(vote_path); + let vote_call = cx.expr_call(span, vote_expr, vote_args); + let vote_stmt = cx.stmt_expr(vote_call); + + wrapper_stmts.push(vote_stmt); let wrapper_body = cx.block(span, wrapper_stmts); - let mut wrapper_fn = ast::Fn { + let wrapper_fn = ast::Fn { defaultness: func.defaultness, ident: func.ident, sig: func.sig.clone(), @@ -90,9 +104,6 @@ pub(crate) fn triplicate( eii_impls: func.eii_impls.clone() }; - // The return type is temporarily the unit type until voting is implemented - wrapper_fn.sig.decl.output = ast::FnRetTy::Default(span); - let mir_attr = cx.attr_word(sym::rad_protected_mir, span); let mut wrapper = cx.item(span, thin_vec![mir_attr], ast::ItemKind::Fn(Box::new(wrapper_fn))); diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index b3425e4969ac0..2cf06c79956f4 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -611,6 +611,10 @@ pub mod random; pub mod sync; pub mod time; + +#[stable(feature = "rad_protected", since = "1.95.0")] +pub mod rad_protected; + // Pull in `std_float` crate into std. The contents of // `std_float` are in a different repository: rust-lang/portable-simd. #[path = "../../portable-simd/crates/std_float/src/lib.rs"] diff --git a/library/std/src/rad_protected.rs b/library/std/src/rad_protected.rs new file mode 100644 index 0000000000000..74f0edce36c87 --- /dev/null +++ b/library/std/src/rad_protected.rs @@ -0,0 +1,29 @@ +//! Standard library for runtime additions required by rad_protected + +use core::mem; + +fn bitwise_majority_vote(a: u8, b: u8, c: u8) -> u8 { + (a & b) | (b & c) | (a & c) +} + +/// Perform bitwise majority vote of the triplicated call results for rad_protected +#[stable(feature = "rad_protected", since = "1.95.0")] +pub fn vote(a: T, b: T, c: T) -> T { + let size = mem::size_of::(); + + unsafe { + let a_ptr = &a as *const T as *mut u8; + let b_ptr = &b as *const T as *const u8; + let c_ptr = &c as *const T as *const u8; + + for i in 0..size { + let a_byte = a_ptr.add(i); + let b_byte = b_ptr.add(i); + let c_byte = c_ptr.add(i); + + *a_byte = bitwise_majority_vote(*a_byte, *b_byte, *c_byte); + } + } + + a +} From c30b97b2b431a493290473356fd5788779312b41 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:42:37 -0400 Subject: [PATCH 07/40] Add support for a no_triplicate_body option for the rad_protected attr --- .../rustc_builtin_macros/src/rad_protected.rs | 29 +++++++++++++++++-- compiler/rustc_span/src/symbol.rs | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected.rs index 0d6990554ab0d..d1f4f898eecfe 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected.rs @@ -2,11 +2,12 @@ use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::{Span, symbol::Ident, sym}; use thin_vec::{thin_vec, ThinVec}; +use rustc_ast::MetaItemInner; pub(crate) fn triplicate( cx: &mut ExtCtxt<'_>, span: Span, - _meta_item: &ast::MetaItem, + meta_item: &ast::MetaItem, item: Annotatable, ) -> Vec { @@ -27,6 +28,15 @@ pub(crate) fn triplicate( } }; + let attr_opts: ThinVec = match meta_item.kind { + ast::MetaItemKind::List(ref vec) => vec.clone(), + ast::MetaItemKind::Word => thin_vec![], + _ => { + cx.dcx().span_err(meta_item.span, "unsupported options kind in `#[rad_protected]`"); + thin_vec![] + } + }; + let make_ident = |suffix_num: usize| { Ident::from_str_and_span( &format!("__{}_{}", func.ident.name, suffix_num), @@ -64,15 +74,28 @@ pub(crate) fn triplicate( } }).collect(); + let mut triplicate_body = true; + + for opt in attr_opts { + match opt { + MetaItemInner::MetaItem(opt) if opt.has_name(sym::no_triplicate_body) => { + triplicate_body = false; + } + _ => { + cx.dcx().span_err(meta_item.span, "unsupported option in `#[rad_protected]`"); + } + } + } + let make_call_expr = |suffix_num: usize| { - cx.expr_call_ident(span, make_ident(suffix_num), call_args.clone()) + cx.expr_call_ident(span, make_ident(if triplicate_body { suffix_num } else { 1 }), call_args.clone()) }; const NUM_DUPLICATES: usize = 3; let mut wrapper_stmts: ThinVec = thin_vec![]; - wrapper_stmts.extend((1..=NUM_DUPLICATES).map(make_inner_fn_stmt)); + wrapper_stmts.extend((1..=if triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); let vote_path = cx.path_global( span, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 03944f0e3852f..134b5557ec1f7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1592,6 +1592,7 @@ symbols! { quote, rad_protected, rad_protected_mir, + no_triplicate_body, range_inclusive_new, raw_dash_dylib: "raw-dylib", raw_dylib, From 38a7d02a6b3f4a2a86898665e4dba9240b76a597 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:04:59 -0400 Subject: [PATCH 08/40] Fix bug where triplicated function bodies are optimized out --- .../rustc_builtin_macros/src/rad_protected.rs | 43 +++++++++++++------ compiler/rustc_expand/src/build.rs | 15 +++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected.rs index d1f4f898eecfe..e59bbe0336130 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected.rs @@ -1,6 +1,6 @@ use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Span, symbol::Ident, sym}; +use rustc_span::{Span, symbol::Ident, sym, Symbol}; use thin_vec::{thin_vec, ThinVec}; use rustc_ast::MetaItemInner; @@ -36,6 +36,19 @@ pub(crate) fn triplicate( thin_vec![] } }; + + let mut triplicate_body = true; + + for opt in attr_opts { + match opt { + MetaItemInner::MetaItem(opt) if opt.has_name(sym::no_triplicate_body) => { + triplicate_body = false; + } + _ => { + cx.dcx().span_err(meta_item.span, "unsupported option in `#[rad_protected]`"); + } + } + } let make_ident = |suffix_num: usize| { Ident::from_str_and_span( @@ -57,7 +70,21 @@ pub(crate) fn triplicate( eii_impls: thin_vec![] }; - let item = cx.item(span, ast::AttrVec::new(), ast::ItemKind::Fn(Box::new(inner_fn))); + let inner_attrs = + if triplicate_body { + let inline_attr = cx.attr_nested_word(sym::inline, sym::never, span); + let link_section_attr = cx.attr_name_value_str_unsafe( + sym::link_section, + Symbol::intern(&format!(".text.{}_{}", func.ident.name, suffix_num)), + span + ); + thin_vec![inline_attr, link_section_attr] + + } else { + thin_vec![] + }; + + let item = cx.item(span, inner_attrs, ast::ItemKind::Fn(Box::new(inner_fn))); cx.stmt_item(span, item) }; @@ -74,18 +101,6 @@ pub(crate) fn triplicate( } }).collect(); - let mut triplicate_body = true; - - for opt in attr_opts { - match opt { - MetaItemInner::MetaItem(opt) if opt.has_name(sym::no_triplicate_body) => { - triplicate_body = false; - } - _ => { - cx.dcx().span_err(meta_item.span, "unsupported option in `#[rad_protected]`"); - } - } - } let make_call_expr = |suffix_num: usize| { cx.expr_call_ident(span, make_ident(if triplicate_body { suffix_num } else { 1 }), call_args.clone()) diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 19a2d65762e86..845d4016fae6d 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -769,6 +769,21 @@ impl<'a> ExtCtxt<'a> { ) } + // Builds `#[unsafe(name = val)]`. + // + // Note: `span` is used for both the identifier and the value. + pub fn attr_name_value_str_unsafe(&self, name: Symbol, val: Symbol, span: Span) -> ast::Attribute { + let g = &self.sess.psess.attr_id_generator; + attr::mk_attr_name_value_str( + g, + ast::AttrStyle::Outer, + ast::Safety::Unsafe(span), + name, + val, + span, + ) + } + // Builds `#[outer(inner)]`. pub fn attr_nested_word(&self, outer: Symbol, inner: Symbol, span: Span) -> ast::Attribute { let g = &self.sess.psess.attr_id_generator; From 96d2764f57dd3655f19dca460a02f658f311acb2 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:39:49 -0400 Subject: [PATCH 09/40] Add runtime support for rad_protected multithreading --- library/std/src/rad_protected.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/std/src/rad_protected.rs b/library/std/src/rad_protected.rs index 74f0edce36c87..3b5f933cfe065 100644 --- a/library/std/src/rad_protected.rs +++ b/library/std/src/rad_protected.rs @@ -27,3 +27,29 @@ pub fn vote(a: T, b: T, c: T) -> T { a } + + +/// Contains the logic for rad_protected multithreading +/// All valid rad_protected `Multithreading` types must implement this trait +#[stable(feature = "rad_protected", since = "1.95.0")] +pub trait Multithreading { + + /// Run the triplicated functions on new threads + #[stable(feature = "rad_protected", since = "1.95.0")] + fn run_triple(f1: F1, f2: F2, f3: F3) -> T + where + F1: FnOnce() -> T + Send + 'static, + F2: FnOnce() -> T + Send + 'static, + F3: FnOnce() -> T + Send + 'static, + T: Send + 'static; + + /// Synchronizes the threads at the start of the critical section + /// Returns `true` for the leader thread, and `false` for the non-leaders + /// The non-leaders continue, waiting at `exit_critical_section` + #[stable(feature = "rad_protected", since = "1.95.0")] + fn enter_critical_section(&self) -> bool; + + /// Non-leader threads wait here for the leader to complete the critical section + #[stable(feature = "rad_protected", since = "1.95.0")] + fn exit_critical_section(&self); +} From c7b12b561e1fc95c30676ff30774dae12c95d5af Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 20:18:38 -0400 Subject: [PATCH 10/40] Create an implementation of Multithreading using Std --- library/std/src/rad_protected.rs | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/library/std/src/rad_protected.rs b/library/std/src/rad_protected.rs index 3b5f933cfe065..ae6e8e83e5bfe 100644 --- a/library/std/src/rad_protected.rs +++ b/library/std/src/rad_protected.rs @@ -1,6 +1,8 @@ //! Standard library for runtime additions required by rad_protected use core::mem; +use crate::thread; +use crate::sync::{Arc, Barrier}; fn bitwise_majority_vote(a: u8, b: u8, c: u8) -> u8 { (a & b) | (b & c) | (a & c) @@ -53,3 +55,59 @@ pub trait Multithreading { #[stable(feature = "rad_protected", since = "1.95.0")] fn exit_critical_section(&self); } + +/// An implementation of rad_protected `Multithreading` using the Rust std library +#[stable(feature = "rad_protected", since = "1.95.0")] +#[derive(Clone, Debug)] +pub struct StdMultithreading { + inner_data: Arc +} + +#[derive(Debug)] +struct StdMultithreadingInnerData { + arrival_barrier: Barrier, + departure_barrier: Barrier, +} + +#[stable(feature = "rad_protected", since = "1.95.0")] +impl StdMultithreading { + + /// Create a new instance of `StdMultithreading` + #[stable(feature = "rad_protected", since = "1.95.0")] + pub fn new(num_threads: usize) -> Self { + Self { + inner_data: Arc::new(StdMultithreadingInnerData { + arrival_barrier: Barrier::new(num_threads), + departure_barrier: Barrier::new(num_threads), + }) + } + } +} + +#[stable(feature = "rad_protected", since = "1.95.0")] +impl Multithreading for StdMultithreading { + + fn run_triple(f1: F1, f2: F2, f3: F3) -> T + where + F1: FnOnce() -> T + Send + 'static, + F2: FnOnce() -> T + Send + 'static, + F3: FnOnce() -> T + Send + 'static, + T: Send + 'static, + { + let i1 = thread::spawn(f1); + let i2 = thread::spawn(f2); + let i3 = thread::spawn(f3); + + i1.join().unwrap(); + i2.join().unwrap(); + i3.join().unwrap() + } + + fn enter_critical_section(&self) -> bool { + self.inner_data.arrival_barrier.wait().is_leader() + } + + fn exit_critical_section(&self) { + self.inner_data.departure_barrier.wait(); + } +} From 6d290abe15c008707a94f8c8293ef33eee0496c0 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 20:28:01 -0400 Subject: [PATCH 11/40] Create multithreading context and pass to triplicated fns --- .../rustc_builtin_macros/src/rad_protected.rs | 171 ++++++++++++++++-- 1 file changed, 158 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected.rs index e59bbe0336130..e7a3c1233aef7 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected.rs @@ -1,8 +1,8 @@ use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Span, symbol::Ident, sym, Symbol}; +use rustc_span::{Span, symbol::Ident, sym, Symbol, DUMMY_SP}; use thin_vec::{thin_vec, ThinVec}; -use rustc_ast::MetaItemInner; +use rustc_ast::{MetaItemInner, FnSig}; pub(crate) fn triplicate( cx: &mut ExtCtxt<'_>, @@ -59,11 +59,14 @@ pub(crate) fn triplicate( let make_inner_fn_stmt = |suffix_num: usize| { + let mut sig = func.sig.clone(); + add_mutex_param(&mut sig); + let inner_fn = ast::Fn { defaultness: ast::Defaultness::Implicit, ident: make_ident(suffix_num), generics: func.generics.clone(), - sig: func.sig.clone(), + sig, contract: None, define_opaque: None, body: Some(func_body.clone()), @@ -102,32 +105,123 @@ pub(crate) fn triplicate( }).collect(); - let make_call_expr = |suffix_num: usize| { - cx.expr_call_ident(span, make_ident(if triplicate_body { suffix_num } else { 1 }), call_args.clone()) - }; +let make_call_expr = |suffix_num: usize| { + let fn_ident = make_ident(if triplicate_body { suffix_num } else { 1 }); + + let multithreading_clone_expr = cx.expr_method_call( + span, + cx.expr_ident(span, Ident::from_str_and_span("multithreading", span)), + Ident::new(sym::clone, span), + thin_vec![], + ); + + let m_ident = Ident::from_str_and_span("m", span); + let local_stmt = cx.stmt_let( + span, + false, + m_ident, + multithreading_clone_expr, + ); + + let mut call_args = call_args.clone(); + call_args.push(cx.expr_ident(span, m_ident)); + + let call = cx.expr_call_ident(span, fn_ident, call_args); + + let body_block = cx.expr_block(cx.block(span, thin_vec![ + cx.stmt_expr(call) + ])); + + let mut closure_expr = cx.lambda(span, vec![], body_block); + + if let ast::ExprKind::Closure(ref mut closure) = closure_expr.kind { + closure.capture_clause = ast::CaptureBy::Value { move_kw: span }; + } + cx.expr_block(cx.block(span, thin_vec![ + local_stmt, + cx.stmt_expr(closure_expr) + ])) + }; + const NUM_DUPLICATES: usize = 3; let mut wrapper_stmts: ThinVec = thin_vec![]; + + let multithreading_use_item = { + let path = ast::Path { + span, + segments: thin_vec![ + ast::PathSegment::from_ident(Ident::new(sym::std, span)), + ast::PathSegment::from_ident(Ident::new(sym::rad_protected, span)), + ast::PathSegment::from_ident(Ident::from_str_and_span("Multithreading", span)), + ], + tokens: None, + }; + + let use_tree = ast::UseTree { + prefix: path, + kind: ast::UseTreeKind::Simple(None), + span, + }; + + let use_item = cx.item( + span, + ThinVec::new(), + ast::ItemKind::Use(use_tree), + ); + + cx.stmt_item(span, use_item) + }; + wrapper_stmts.push(multithreading_use_item); + + + let multithreading_ident = Ident::from_str_and_span("multithreading", span); + + let multithreading_init = cx.expr_call( + span, + cx.expr_path(cx.path_global( + span, + vec![ + Ident::new(sym::std, span), + Ident::new(sym::rad_protected, span), + Ident::from_str_and_span("StdMultithreading", span), + Ident::new(sym::new, span), + ], + )), + thin_vec![ + cx.expr_usize(span, NUM_DUPLICATES) + ], + ); + + let multithreading_stmt = cx.stmt_let( + span, + false, + multithreading_ident, + multithreading_init, + ); + + wrapper_stmts.push(multithreading_stmt); wrapper_stmts.extend((1..=if triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); - let vote_path = cx.path_global( + let run_triple_path = cx.path_global( span, vec![ Ident::new(sym::std, span), Ident::new(sym::rad_protected, span), - Ident::from_str_and_span("vote", span) + Ident::from_str_and_span("StdMultithreading", span), + Ident::from_str_and_span("run_triple", span) ], ); - let vote_args: ThinVec<_> = (1..=NUM_DUPLICATES).map(make_call_expr).collect(); + let run_triple_args: ThinVec<_> = (1..=NUM_DUPLICATES).map(make_call_expr).collect(); - let vote_expr = cx.expr_path(vote_path); - let vote_call = cx.expr_call(span, vote_expr, vote_args); - let vote_stmt = cx.stmt_expr(vote_call); + let run_triple_expr = cx.expr_path(run_triple_path); + let run_triple_call = cx.expr_call(span, run_triple_expr, run_triple_args); + let run_triple_stmt = cx.stmt_expr(run_triple_call); - wrapper_stmts.push(vote_stmt); + wrapper_stmts.push(run_triple_stmt); let wrapper_body = cx.block(span, wrapper_stmts); @@ -149,3 +243,54 @@ pub(crate) fn triplicate( vec![Annotatable::Item(wrapper)] } + +fn add_mutex_param(sig: &mut FnSig) { + sig.decl.inputs.push(ast::Param { + attrs: Default::default(), + + pat: Box::new(ast::Pat { + id: ast::DUMMY_NODE_ID, + kind: ast::PatKind::Ident( + ast::BindingMode::NONE, + Ident::from_str_and_span("multithreading", DUMMY_SP), + None, + ), + span: DUMMY_SP, + tokens: None, + }), + + ty: Box::new(ast::Ty { + id: ast::DUMMY_NODE_ID, + kind: ast::TyKind::Path( + None, + ast::Path { + span: DUMMY_SP, + segments: thin_vec![ + ast::PathSegment { + ident: Ident::new(sym::std, DUMMY_SP), + id: ast::DUMMY_NODE_ID, + args: None, + }, + ast::PathSegment { + ident: Ident::new(sym::rad_protected, DUMMY_SP), + id: ast::DUMMY_NODE_ID, + args: None, + }, + ast::PathSegment { + ident: Ident::from_str_and_span("StdMultithreading", DUMMY_SP), + id: ast::DUMMY_NODE_ID, + args: None, + }, + ], + tokens: None, + }, + ), + span: DUMMY_SP, + tokens: None, + }), + + id: ast::DUMMY_NODE_ID, + span: DUMMY_SP, + is_placeholder: false, + }); +} From 1c073fc773a916f12bc9ffeb820e5cab3222e2ff Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 20:28:53 -0400 Subject: [PATCH 12/40] Move rad_protected AST transformation to its own mod --- compiler/rustc_builtin_macros/src/rad_protected/mod.rs | 4 ++++ .../rustc_builtin_macros/src/rad_protected/patch_unsafe.rs | 6 ++++++ .../src/{rad_protected.rs => rad_protected/triplicate.rs} | 7 +++++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 compiler/rustc_builtin_macros/src/rad_protected/mod.rs create mode 100644 compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs rename compiler/rustc_builtin_macros/src/{rad_protected.rs => rad_protected/triplicate.rs} (98%) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/mod.rs b/compiler/rustc_builtin_macros/src/rad_protected/mod.rs new file mode 100644 index 0000000000000..51094249e04b3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/rad_protected/mod.rs @@ -0,0 +1,4 @@ +mod triplicate; +mod patch_unsafe; + +pub(crate) use triplicate::triplicate; diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs new file mode 100644 index 0000000000000..37e7bda7fa2ea --- /dev/null +++ b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs @@ -0,0 +1,6 @@ +use rustc_ast as ast; +use rustc_expand::base::ExtCtxt; + +pub(crate) fn patch_unsafe_blocks(_cx: &ExtCtxt<'_>, _body: &mut ast::Block) { + todo!(); +} diff --git a/compiler/rustc_builtin_macros/src/rad_protected.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs similarity index 98% rename from compiler/rustc_builtin_macros/src/rad_protected.rs rename to compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index e7a3c1233aef7..e643f887e4537 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -3,6 +3,7 @@ use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::{Span, symbol::Ident, sym, Symbol, DUMMY_SP}; use thin_vec::{thin_vec, ThinVec}; use rustc_ast::{MetaItemInner, FnSig}; +use crate::rad_protected::patch_unsafe::patch_unsafe_blocks; pub(crate) fn triplicate( cx: &mut ExtCtxt<'_>, @@ -20,14 +21,16 @@ pub(crate) fn triplicate( return vec![item]; }; - let func_body = match &func.body { - Some(b) => b, + let mut func_body = match &func.body { + Some(b) => b.clone(), None => { cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions with a body"); return vec![item]; } }; + patch_unsafe_blocks(cx, &mut func_body); + let attr_opts: ThinVec = match meta_item.kind { ast::MetaItemKind::List(ref vec) => vec.clone(), ast::MetaItemKind::Word => thin_vec![], From 24ea2368c8112f44e3c73b1676ca52cf9df35310 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 22:03:54 -0400 Subject: [PATCH 13/40] Clean up AST triplication code --- .../src/rad_protected/triplicate.rs | 238 ++++++++---------- 1 file changed, 101 insertions(+), 137 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index e643f887e4537..0cb17626710da 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -52,22 +52,15 @@ pub(crate) fn triplicate( } } } - - let make_ident = |suffix_num: usize| { - Ident::from_str_and_span( - &format!("__{}_{}", func.ident.name, suffix_num), - span - ) - }; let make_inner_fn_stmt = |suffix_num: usize| { let mut sig = func.sig.clone(); - add_mutex_param(&mut sig); + add_mutex_param(cx, &mut sig); let inner_fn = ast::Fn { defaultness: ast::Defaultness::Implicit, - ident: make_ident(suffix_num), + ident: inner_fn_ident(func.ident.name, suffix_num), generics: func.generics.clone(), sig, contract: None, @@ -78,11 +71,11 @@ pub(crate) fn triplicate( let inner_attrs = if triplicate_body { - let inline_attr = cx.attr_nested_word(sym::inline, sym::never, span); + let inline_attr = cx.attr_nested_word(sym::inline, sym::never, DUMMY_SP); let link_section_attr = cx.attr_name_value_str_unsafe( sym::link_section, Symbol::intern(&format!(".text.{}_{}", func.ident.name, suffix_num)), - span + DUMMY_SP ); thin_vec![inline_attr, link_section_attr] @@ -90,13 +83,16 @@ pub(crate) fn triplicate( thin_vec![] }; - let item = cx.item(span, inner_attrs, ast::ItemKind::Fn(Box::new(inner_fn))); - cx.stmt_item(span, item) + cx.stmt_item(DUMMY_SP, cx.item( + DUMMY_SP, + inner_attrs, + ast::ItemKind::Fn(Box::new(inner_fn)) + )) }; let call_args: ThinVec<_> = func.sig.decl.inputs.iter().filter_map(|param| { match ¶m.pat.kind { - ast::PatKind::Ident(_, ident, _) => Some(cx.expr_ident(span, *ident)), + ast::PatKind::Ident(_, ident, _) => Some(cx.expr_ident(param.pat.span, *ident)), _ => { cx.dcx().span_err( param.pat.span, @@ -107,41 +103,40 @@ pub(crate) fn triplicate( } }).collect(); - -let make_call_expr = |suffix_num: usize| { - let fn_ident = make_ident(if triplicate_body { suffix_num } else { 1 }); + let make_call_expr = |suffix_num: usize| { + let fn_ident = inner_fn_ident(func.ident.name, if triplicate_body { suffix_num } else { 1 }); let multithreading_clone_expr = cx.expr_method_call( - span, - cx.expr_ident(span, Ident::from_str_and_span("multithreading", span)), - Ident::new(sym::clone, span), + DUMMY_SP, + cx.expr_ident(DUMMY_SP, multithreading_ident()), + Ident::new(sym::clone, DUMMY_SP), thin_vec![], ); - let m_ident = Ident::from_str_and_span("m", span); + let m_ident = Ident::from_str_and_span("m", DUMMY_SP); let local_stmt = cx.stmt_let( - span, + DUMMY_SP, false, m_ident, multithreading_clone_expr, ); let mut call_args = call_args.clone(); - call_args.push(cx.expr_ident(span, m_ident)); + call_args.push(cx.expr_ident(DUMMY_SP, m_ident)); - let call = cx.expr_call_ident(span, fn_ident, call_args); + let call = cx.expr_call_ident(DUMMY_SP, fn_ident, call_args); - let body_block = cx.expr_block(cx.block(span, thin_vec![ + let body_block = cx.expr_block(cx.block(DUMMY_SP, thin_vec![ cx.stmt_expr(call) ])); - let mut closure_expr = cx.lambda(span, vec![], body_block); + let mut closure_expr = cx.lambda(DUMMY_SP, vec![], body_block); if let ast::ExprKind::Closure(ref mut closure) = closure_expr.kind { - closure.capture_clause = ast::CaptureBy::Value { move_kw: span }; + closure.capture_clause = ast::CaptureBy::Value { move_kw: DUMMY_SP }; } - cx.expr_block(cx.block(span, thin_vec![ + cx.expr_block(cx.block(DUMMY_SP, thin_vec![ local_stmt, cx.stmt_expr(closure_expr) ])) @@ -150,83 +145,68 @@ let make_call_expr = |suffix_num: usize| { const NUM_DUPLICATES: usize = 3; let mut wrapper_stmts: ThinVec = thin_vec![]; + + wrapper_stmts.extend((1..=if triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); let multithreading_use_item = { - let path = ast::Path { - span, - segments: thin_vec![ - ast::PathSegment::from_ident(Ident::new(sym::std, span)), - ast::PathSegment::from_ident(Ident::new(sym::rad_protected, span)), - ast::PathSegment::from_ident(Ident::from_str_and_span("Multithreading", span)), - ], - tokens: None, - }; + let path = cx.path_global(DUMMY_SP, rad_protected_path(vec![ + Ident::from_str_and_span("Multithreading", DUMMY_SP), + ])); let use_tree = ast::UseTree { prefix: path, kind: ast::UseTreeKind::Simple(None), - span, + span: DUMMY_SP, }; - let use_item = cx.item( - span, - ThinVec::new(), + cx.stmt_item(DUMMY_SP, cx.item( + DUMMY_SP, + thin_vec![], ast::ItemKind::Use(use_tree), - ); - - cx.stmt_item(span, use_item) + )) }; wrapper_stmts.push(multithreading_use_item); - - let multithreading_ident = Ident::from_str_and_span("multithreading", span); - - let multithreading_init = cx.expr_call( - span, - cx.expr_path(cx.path_global( - span, - vec![ - Ident::new(sym::std, span), - Ident::new(sym::rad_protected, span), - Ident::from_str_and_span("StdMultithreading", span), - Ident::new(sym::new, span), + let multithreading_init_stmt = { + let multithreading_init = cx.expr_call_global( + DUMMY_SP, + rad_protected_path(vec![ + multithreading_ty_ident(), + Ident::new(sym::new, DUMMY_SP), + ]), + thin_vec![ + cx.expr_usize(DUMMY_SP, NUM_DUPLICATES) ], - )), - thin_vec![ - cx.expr_usize(span, NUM_DUPLICATES) - ], - ); - - let multithreading_stmt = cx.stmt_let( - span, - false, - multithreading_ident, - multithreading_init, - ); - - wrapper_stmts.push(multithreading_stmt); - - wrapper_stmts.extend((1..=if triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); - - let run_triple_path = cx.path_global( - span, - vec![ - Ident::new(sym::std, span), - Ident::new(sym::rad_protected, span), - Ident::from_str_and_span("StdMultithreading", span), - Ident::from_str_and_span("run_triple", span) - ], - ); + ); - let run_triple_args: ThinVec<_> = (1..=NUM_DUPLICATES).map(make_call_expr).collect(); + cx.stmt_let( + DUMMY_SP, + false, + multithreading_ident(), + multithreading_init, + ) + }; + wrapper_stmts.push(multithreading_init_stmt); + + let run_triple_stmt = { + let run_triple_path = cx.path_global( + DUMMY_SP, + rad_protected_path(vec![ + multithreading_ty_ident(), + Ident::from_str_and_span("run_triple", DUMMY_SP) + ]), + ); - let run_triple_expr = cx.expr_path(run_triple_path); - let run_triple_call = cx.expr_call(span, run_triple_expr, run_triple_args); - let run_triple_stmt = cx.stmt_expr(run_triple_call); + let run_triple_args: ThinVec<_> = (1..=NUM_DUPLICATES).map(make_call_expr).collect(); + cx.stmt_expr(cx.expr_call( + DUMMY_SP, + cx.expr_path(run_triple_path), run_triple_args + )) + }; wrapper_stmts.push(run_triple_stmt); - let wrapper_body = cx.block(span, wrapper_stmts); + let wrapper_body = cx.block(DUMMY_SP, wrapper_stmts); let wrapper_fn = ast::Fn { defaultness: func.defaultness, @@ -239,61 +219,45 @@ let make_call_expr = |suffix_num: usize| { eii_impls: func.eii_impls.clone() }; - let mir_attr = cx.attr_word(sym::rad_protected_mir, span); + let mir_attr = cx.attr_word(sym::rad_protected_mir, DUMMY_SP); - let mut wrapper = cx.item(span, thin_vec![mir_attr], ast::ItemKind::Fn(Box::new(wrapper_fn))); + let mut wrapper = cx.item(DUMMY_SP, thin_vec![mir_attr], ast::ItemKind::Fn(Box::new(wrapper_fn))); wrapper.vis = vis.clone(); vec![Annotatable::Item(wrapper)] } -fn add_mutex_param(sig: &mut FnSig) { - sig.decl.inputs.push(ast::Param { - attrs: Default::default(), - - pat: Box::new(ast::Pat { - id: ast::DUMMY_NODE_ID, - kind: ast::PatKind::Ident( - ast::BindingMode::NONE, - Ident::from_str_and_span("multithreading", DUMMY_SP), - None, - ), - span: DUMMY_SP, - tokens: None, - }), - - ty: Box::new(ast::Ty { - id: ast::DUMMY_NODE_ID, - kind: ast::TyKind::Path( - None, - ast::Path { - span: DUMMY_SP, - segments: thin_vec![ - ast::PathSegment { - ident: Ident::new(sym::std, DUMMY_SP), - id: ast::DUMMY_NODE_ID, - args: None, - }, - ast::PathSegment { - ident: Ident::new(sym::rad_protected, DUMMY_SP), - id: ast::DUMMY_NODE_ID, - args: None, - }, - ast::PathSegment { - ident: Ident::from_str_and_span("StdMultithreading", DUMMY_SP), - id: ast::DUMMY_NODE_ID, - args: None, - }, - ], - tokens: None, - }, - ), - span: DUMMY_SP, - tokens: None, - }), +fn add_mutex_param(cx: &ExtCtxt<'_>, sig: &mut FnSig) { + sig.decl.inputs.push(cx.param( + DUMMY_SP, + multithreading_ident(), + cx.ty_path(cx.path_global(DUMMY_SP, rad_protected_path(vec![ + multithreading_ty_ident(), + ]))) + )); +} + +fn rad_protected_path(tail: Vec) -> Vec { + let mut path = vec![ + Ident::new(sym::std, DUMMY_SP), + Ident::new(sym::rad_protected, DUMMY_SP), + ]; + + path.extend(tail); + path +} + +fn inner_fn_ident(name: Symbol, suffix_num: usize) -> Ident { + Ident::from_str_and_span( + &format!("__{}_{}", name, suffix_num), + DUMMY_SP + ) +} + +fn multithreading_ident() -> Ident { + Ident::from_str_and_span("multithreading", DUMMY_SP) +} - id: ast::DUMMY_NODE_ID, - span: DUMMY_SP, - is_placeholder: false, - }); +fn multithreading_ty_ident() -> Ident { + Ident::from_str_and_span("StdMultithreading", DUMMY_SP) } From 14f0e11df717b657b4f74402ea153d0f26f79825 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:12:47 -0400 Subject: [PATCH 14/40] Add MutVisitor to track unsafe blocks in AST --- .../src/rad_protected/patch_unsafe.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs index 37e7bda7fa2ea..d39b8053363cc 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs @@ -1,6 +1,32 @@ use rustc_ast as ast; +use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_expand::base::ExtCtxt; +use rustc_span::{symbol::Ident, DUMMY_SP}; +use thin_vec::thin_vec; -pub(crate) fn patch_unsafe_blocks(_cx: &ExtCtxt<'_>, _body: &mut ast::Block) { +pub(crate) fn patch_unsafe_blocks(cx: &ExtCtxt<'_>, body: &mut ast::Block) { + let mut visitor = UnsafeBlockRewriter { cx }; + visitor.visit_block(body); +} + +struct UnsafeBlockRewriter<'a, 'cx> { + cx: &'a ExtCtxt<'cx>, +} + +impl MutVisitor for UnsafeBlockRewriter<'_, '_> { + fn visit_expr(&mut self, expr: &mut ast::Expr) { + + if let ast::ExprKind::Block(block, _) = &mut expr.kind { + if matches!(block.rules, ast::BlockCheckMode::Unsafe(_)) { + patch_unsafe_block(self.cx, block); + return; + } + } + + mut_visit::walk_expr(self, expr); + } +} + +fn patch_unsafe_block(cx: &ExtCtxt<'_>, block: &mut ast::Block) { todo!(); } From eb5e12c928ba25321f8b8d9dd8b7850fd705f7e5 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:13:32 -0400 Subject: [PATCH 15/40] Implement patch_unsafe_block --- .../src/rad_protected/patch_unsafe.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs index d39b8053363cc..5da7ffb30ad45 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs @@ -28,5 +28,33 @@ impl MutVisitor for UnsafeBlockRewriter<'_, '_> { } fn patch_unsafe_block(cx: &ExtCtxt<'_>, block: &mut ast::Block) { - todo!(); + + let multithreading_method_call = |name: &str| { + let multithreading_ident = Ident::from_str_and_span("multithreading", DUMMY_SP); + let method_name_ident = Ident::from_str_and_span(name, DUMMY_SP); + + cx.expr_method_call( + DUMMY_SP, + cx.expr_ident(DUMMY_SP, multithreading_ident), + method_name_ident, + thin_vec![], + ) + }; + + let enter_call = multithreading_method_call("enter_critical_section"); + let exit_call = multithreading_method_call("exit_critical_section"); + + let if_stmt = cx.stmt_expr(cx.expr_if( + DUMMY_SP, + enter_call, + cx.expr_block(cx.block(block.span, block.stmts.clone())), + None, + )); + + let exit_call_stmt = cx.stmt_expr(exit_call); + + block.stmts = thin_vec![ + if_stmt, + exit_call_stmt + ]; } From 0f8e463cb53dbc4e274047011038fb025204f988 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Sun, 28 Jun 2026 22:59:15 -0400 Subject: [PATCH 16/40] Move Multithreading trait to Rust Core library --- .../src/rad_protected/triplicate.rs | 20 +++---- library/core/src/lib.rs | 4 ++ library/core/src/rad_protected.rs | 55 +++++++++++++++++++ library/std/src/rad_protected.rs | 54 +----------------- 4 files changed, 70 insertions(+), 63 deletions(-) create mode 100644 library/core/src/rad_protected.rs diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index 0cb17626710da..5957c40b887ce 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -149,7 +149,7 @@ pub(crate) fn triplicate( wrapper_stmts.extend((1..=if triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); let multithreading_use_item = { - let path = cx.path_global(DUMMY_SP, rad_protected_path(vec![ + let path = cx.path_global(DUMMY_SP, rad_protected_path(false, vec![ Ident::from_str_and_span("Multithreading", DUMMY_SP), ])); @@ -170,8 +170,8 @@ pub(crate) fn triplicate( let multithreading_init_stmt = { let multithreading_init = cx.expr_call_global( DUMMY_SP, - rad_protected_path(vec![ - multithreading_ty_ident(), + rad_protected_path(true, vec![ + multithreading_impl_ty_ident(), Ident::new(sym::new, DUMMY_SP), ]), thin_vec![ @@ -191,8 +191,8 @@ pub(crate) fn triplicate( let run_triple_stmt = { let run_triple_path = cx.path_global( DUMMY_SP, - rad_protected_path(vec![ - multithreading_ty_ident(), + rad_protected_path(true, vec![ + multithreading_impl_ty_ident(), Ident::from_str_and_span("run_triple", DUMMY_SP) ]), ); @@ -231,15 +231,15 @@ fn add_mutex_param(cx: &ExtCtxt<'_>, sig: &mut FnSig) { sig.decl.inputs.push(cx.param( DUMMY_SP, multithreading_ident(), - cx.ty_path(cx.path_global(DUMMY_SP, rad_protected_path(vec![ - multithreading_ty_ident(), + cx.ty_path(cx.path_global(DUMMY_SP, rad_protected_path(true, vec![ + multithreading_impl_ty_ident(), ]))) )); } -fn rad_protected_path(tail: Vec) -> Vec { +fn rad_protected_path(_impl_path: bool, tail: Vec) -> Vec { let mut path = vec![ - Ident::new(sym::std, DUMMY_SP), + Ident::new(if _impl_path { sym::std } else { sym::core }, DUMMY_SP), Ident::new(sym::rad_protected, DUMMY_SP), ]; @@ -258,6 +258,6 @@ fn multithreading_ident() -> Ident { Ident::from_str_and_span("multithreading", DUMMY_SP) } -fn multithreading_ty_ident() -> Ident { +fn multithreading_impl_ty_ident() -> Ident { Ident::from_str_and_span("StdMultithreading", DUMMY_SP) } diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index c95f3cacbda2c..889439e93f0ec 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -329,6 +329,10 @@ pub mod task; #[allow(missing_docs)] pub mod alloc; +/* Core library additions for `rad_protected` */ +#[stable(feature = "rad_protected", since = "1.95.0")] +pub mod rad_protected; + // note: does not need to be public mod bool; mod escape; diff --git a/library/core/src/rad_protected.rs b/library/core/src/rad_protected.rs new file mode 100644 index 0000000000000..6a19f53668a96 --- /dev/null +++ b/library/core/src/rad_protected.rs @@ -0,0 +1,55 @@ +//! Core library for runtime additions required by rad_protected + +use crate::mem; + +fn bitwise_majority_vote(a: u8, b: u8, c: u8) -> u8 { + (a & b) | (b & c) | (a & c) +} + +/// Perform bitwise majority vote of the triplicated call results for rad_protected +#[stable(feature = "rad_protected", since = "1.95.0")] +pub fn vote(a: T, b: T, c: T) -> T { + let size = mem::size_of::(); + + unsafe { + let a_ptr = &a as *const T as *mut u8; + let b_ptr = &b as *const T as *const u8; + let c_ptr = &c as *const T as *const u8; + + for i in 0..size { + let a_byte = a_ptr.add(i); + let b_byte = b_ptr.add(i); + let c_byte = c_ptr.add(i); + + *a_byte = bitwise_majority_vote(*a_byte, *b_byte, *c_byte); + } + } + + a +} + + +/// Contains the logic for rad_protected multithreading +/// All valid rad_protected `Multithreading` types must implement this trait +#[stable(feature = "rad_protected", since = "1.95.0")] +pub trait Multithreading { + + /// Run the triplicated functions on new threads + #[stable(feature = "rad_protected", since = "1.95.0")] + fn run_triple(f1: F1, f2: F2, f3: F3) -> T + where + F1: FnOnce() -> T + Send + 'static, + F2: FnOnce() -> T + Send + 'static, + F3: FnOnce() -> T + Send + 'static, + T: Send + 'static; + + /// Synchronizes the threads at the start of the critical section + /// Returns `true` for the leader thread, and `false` for the non-leaders + /// The non-leaders continue, waiting at `exit_critical_section` + #[stable(feature = "rad_protected", since = "1.95.0")] + fn enter_critical_section(&self) -> bool; + + /// Non-leader threads wait here for the leader to complete the critical section + #[stable(feature = "rad_protected", since = "1.95.0")] + fn exit_critical_section(&self); +} diff --git a/library/std/src/rad_protected.rs b/library/std/src/rad_protected.rs index ae6e8e83e5bfe..83f326e8a2714 100644 --- a/library/std/src/rad_protected.rs +++ b/library/std/src/rad_protected.rs @@ -1,60 +1,8 @@ //! Standard library for runtime additions required by rad_protected -use core::mem; use crate::thread; use crate::sync::{Arc, Barrier}; - -fn bitwise_majority_vote(a: u8, b: u8, c: u8) -> u8 { - (a & b) | (b & c) | (a & c) -} - -/// Perform bitwise majority vote of the triplicated call results for rad_protected -#[stable(feature = "rad_protected", since = "1.95.0")] -pub fn vote(a: T, b: T, c: T) -> T { - let size = mem::size_of::(); - - unsafe { - let a_ptr = &a as *const T as *mut u8; - let b_ptr = &b as *const T as *const u8; - let c_ptr = &c as *const T as *const u8; - - for i in 0..size { - let a_byte = a_ptr.add(i); - let b_byte = b_ptr.add(i); - let c_byte = c_ptr.add(i); - - *a_byte = bitwise_majority_vote(*a_byte, *b_byte, *c_byte); - } - } - - a -} - - -/// Contains the logic for rad_protected multithreading -/// All valid rad_protected `Multithreading` types must implement this trait -#[stable(feature = "rad_protected", since = "1.95.0")] -pub trait Multithreading { - - /// Run the triplicated functions on new threads - #[stable(feature = "rad_protected", since = "1.95.0")] - fn run_triple(f1: F1, f2: F2, f3: F3) -> T - where - F1: FnOnce() -> T + Send + 'static, - F2: FnOnce() -> T + Send + 'static, - F3: FnOnce() -> T + Send + 'static, - T: Send + 'static; - - /// Synchronizes the threads at the start of the critical section - /// Returns `true` for the leader thread, and `false` for the non-leaders - /// The non-leaders continue, waiting at `exit_critical_section` - #[stable(feature = "rad_protected", since = "1.95.0")] - fn enter_critical_section(&self) -> bool; - - /// Non-leader threads wait here for the leader to complete the critical section - #[stable(feature = "rad_protected", since = "1.95.0")] - fn exit_critical_section(&self); -} +use core::rad_protected::Multithreading; /// An implementation of rad_protected `Multithreading` using the Rust std library #[stable(feature = "rad_protected", since = "1.95.0")] From b910743b9f96832b2dcd125d62c336b047261cf2 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 29 Jun 2026 00:15:03 -0400 Subject: [PATCH 17/40] Bring back voting on return value temporarily --- library/std/src/rad_protected.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/std/src/rad_protected.rs b/library/std/src/rad_protected.rs index 83f326e8a2714..61707bdbd1adb 100644 --- a/library/std/src/rad_protected.rs +++ b/library/std/src/rad_protected.rs @@ -2,7 +2,7 @@ use crate::thread; use crate::sync::{Arc, Barrier}; -use core::rad_protected::Multithreading; +use core::rad_protected::{Multithreading, vote}; /// An implementation of rad_protected `Multithreading` using the Rust std library #[stable(feature = "rad_protected", since = "1.95.0")] @@ -46,9 +46,11 @@ impl Multithreading for StdMultithreading { let i2 = thread::spawn(f2); let i3 = thread::spawn(f3); - i1.join().unwrap(); - i2.join().unwrap(); - i3.join().unwrap() + vote( + i1.join().unwrap(), + i2.join().unwrap(), + i3.join().unwrap() + ) } fn enter_critical_section(&self) -> bool { From 90af0bd1bf749c4d670964bbdbc96e16ab22335e Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 29 Jun 2026 02:18:40 -0400 Subject: [PATCH 18/40] Prefix multithreading param with underscore to stop unused warnings --- compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs | 2 +- compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs index 5da7ffb30ad45..6d89c545a0305 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs @@ -30,7 +30,7 @@ impl MutVisitor for UnsafeBlockRewriter<'_, '_> { fn patch_unsafe_block(cx: &ExtCtxt<'_>, block: &mut ast::Block) { let multithreading_method_call = |name: &str| { - let multithreading_ident = Ident::from_str_and_span("multithreading", DUMMY_SP); + let multithreading_ident = Ident::from_str_and_span("_multithreading", DUMMY_SP); let method_name_ident = Ident::from_str_and_span(name, DUMMY_SP); cx.expr_method_call( diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index 5957c40b887ce..48fdc2cfc4f52 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -255,7 +255,7 @@ fn inner_fn_ident(name: Symbol, suffix_num: usize) -> Ident { } fn multithreading_ident() -> Ident { - Ident::from_str_and_span("multithreading", DUMMY_SP) + Ident::from_str_and_span("_multithreading", DUMMY_SP) } fn multithreading_impl_ty_ident() -> Ident { From 57430b89b0e46d3934a2501387ed32502ceaa590 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Mon, 29 Jun 2026 02:20:51 -0400 Subject: [PATCH 19/40] Add triplicate_unsafe option to rad_protected attr --- .../src/rad_protected/patch_unsafe.rs | 29 ++++++- .../src/rad_protected/triplicate.rs | 86 +++++++++++++------ compiler/rustc_span/src/symbol.rs | 1 + 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs index 6d89c545a0305..e90edfdbd8bdb 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs @@ -1,8 +1,9 @@ use rustc_ast as ast; use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_expand::base::ExtCtxt; -use rustc_span::{symbol::Ident, DUMMY_SP}; -use thin_vec::thin_vec; +use rustc_span::{symbol::Ident, sym, DUMMY_SP}; +use thin_vec::{ThinVec, thin_vec}; +use rustc_ast::MetaItemInner; pub(crate) fn patch_unsafe_blocks(cx: &ExtCtxt<'_>, body: &mut ast::Block) { let mut visitor = UnsafeBlockRewriter { cx }; @@ -18,7 +19,10 @@ impl MutVisitor for UnsafeBlockRewriter<'_, '_> { if let ast::ExprKind::Block(block, _) = &mut expr.kind { if matches!(block.rules, ast::BlockCheckMode::Unsafe(_)) { - patch_unsafe_block(self.cx, block); + + if !skip_patch(&expr.attrs) { + patch_unsafe_block(self.cx, block); + } return; } } @@ -58,3 +62,22 @@ fn patch_unsafe_block(cx: &ExtCtxt<'_>, block: &mut ast::Block) { exit_call_stmt ]; } + +fn skip_patch(attrs: &ThinVec) -> bool { + attrs.iter().any(|attr| { + let Some(meta) = attr.meta() else { + return false; + }; + + if !meta.has_name(sym::rad_protected) { + return false; + } + + match &meta.kind { + ast::MetaItemKind::List(items) => items.iter().any(|item| { + matches!(item, MetaItemInner::MetaItem(mi) if mi.has_name(sym::triplicate_unsafe)) + }), + _ => false, + } + }) +} diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index 48fdc2cfc4f52..a7d002382242c 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -12,6 +12,26 @@ pub(crate) fn triplicate( item: Annotatable, ) -> Vec { + let opts = match parse_attr_args(cx, meta_item) { + Some(o) => o, + None => return vec![item] + }; + + if opts.triplicate_unsafe { + let valid = matches!( + &item, + Annotatable::Expr(box ast::Expr { + kind: ast::ExprKind::Block(block, _), + .. + }) if matches!(block.rules, ast::BlockCheckMode::Unsafe(_)) + ); + + if !valid { + cx.dcx().span_err(span, "`#[rad_protected(triplicate_unsafe)]` can only be applied to `unsafe` blocks"); + } + return vec![item]; + } + let Annotatable::Item(box ast::Item { kind: ast::ItemKind::Fn(box ref func), ref vis, @@ -30,28 +50,6 @@ pub(crate) fn triplicate( }; patch_unsafe_blocks(cx, &mut func_body); - - let attr_opts: ThinVec = match meta_item.kind { - ast::MetaItemKind::List(ref vec) => vec.clone(), - ast::MetaItemKind::Word => thin_vec![], - _ => { - cx.dcx().span_err(meta_item.span, "unsupported options kind in `#[rad_protected]`"); - thin_vec![] - } - }; - - let mut triplicate_body = true; - - for opt in attr_opts { - match opt { - MetaItemInner::MetaItem(opt) if opt.has_name(sym::no_triplicate_body) => { - triplicate_body = false; - } - _ => { - cx.dcx().span_err(meta_item.span, "unsupported option in `#[rad_protected]`"); - } - } - } let make_inner_fn_stmt = |suffix_num: usize| { @@ -70,7 +68,7 @@ pub(crate) fn triplicate( }; let inner_attrs = - if triplicate_body { + if opts.triplicate_body { let inline_attr = cx.attr_nested_word(sym::inline, sym::never, DUMMY_SP); let link_section_attr = cx.attr_name_value_str_unsafe( sym::link_section, @@ -104,7 +102,7 @@ pub(crate) fn triplicate( }).collect(); let make_call_expr = |suffix_num: usize| { - let fn_ident = inner_fn_ident(func.ident.name, if triplicate_body { suffix_num } else { 1 }); + let fn_ident = inner_fn_ident(func.ident.name, if opts.triplicate_body { suffix_num } else { 1 }); let multithreading_clone_expr = cx.expr_method_call( DUMMY_SP, @@ -146,7 +144,7 @@ pub(crate) fn triplicate( let mut wrapper_stmts: ThinVec = thin_vec![]; - wrapper_stmts.extend((1..=if triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); + wrapper_stmts.extend((1..=if opts.triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); let multithreading_use_item = { let path = cx.path_global(DUMMY_SP, rad_protected_path(false, vec![ @@ -227,6 +225,44 @@ pub(crate) fn triplicate( vec![Annotatable::Item(wrapper)] } + +struct AttrOpts { + triplicate_body: bool, + triplicate_unsafe: bool, +} + +fn parse_attr_args(cx: &ExtCtxt<'_>, meta_item: &ast::MetaItem) -> Option { + + let attr_opts: ThinVec = match meta_item.kind { + ast::MetaItemKind::List(ref vec) => vec.clone(), + ast::MetaItemKind::Word => thin_vec![], + _ => { + cx.dcx().span_err(meta_item.span, "unsupported options kind in `#[rad_protected]`"); + return None; + } + }; + + let mut triplicate_body = true; + let mut triplicate_unsafe = false; + + for opt in attr_opts { + match opt { + MetaItemInner::MetaItem(opt) if opt.has_name(sym::no_triplicate_body) => { + triplicate_body = false; + } + MetaItemInner::MetaItem(opt) if opt.has_name(sym::triplicate_unsafe) => { + triplicate_unsafe = true; + } + _ => { + cx.dcx().span_err(opt.span(), "unsupported option in `#[rad_protected]`"); + return None; + } + } + } + + Some(AttrOpts { triplicate_body, triplicate_unsafe }) +} + fn add_mutex_param(cx: &ExtCtxt<'_>, sig: &mut FnSig) { sig.decl.inputs.push(cx.param( DUMMY_SP, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 134b5557ec1f7..3a2b1fbc2103a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1593,6 +1593,7 @@ symbols! { rad_protected, rad_protected_mir, no_triplicate_body, + triplicate_unsafe, range_inclusive_new, raw_dash_dylib: "raw-dylib", raw_dylib, From 5bb264d1005a0047469a7abd57e9b81b14392506 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:33:32 -0400 Subject: [PATCH 20/40] Rework the rad_protected runtime - Use a process-based design which triplicates execution by forking twice - After forking, memory for each process is copied upfront --- library/core/src/lib.rs | 4 - library/core/src/rad_protected.rs | 55 --------- library/std/src/lib.rs | 5 +- library/std/src/rad_protected.rs | 63 ---------- library/std/src/rad_protected/fork.rs | 90 +++++++++++++++ library/std/src/rad_protected/libc_helpers.rs | 75 ++++++++++++ library/std/src/rad_protected/mod.rs | 7 ++ library/std/src/rad_protected/role.rs | 97 ++++++++++++++++ library/std/src/rad_protected/runtime.rs | 109 ++++++++++++++++++ 9 files changed, 382 insertions(+), 123 deletions(-) delete mode 100644 library/core/src/rad_protected.rs delete mode 100644 library/std/src/rad_protected.rs create mode 100644 library/std/src/rad_protected/fork.rs create mode 100644 library/std/src/rad_protected/libc_helpers.rs create mode 100644 library/std/src/rad_protected/mod.rs create mode 100644 library/std/src/rad_protected/role.rs create mode 100644 library/std/src/rad_protected/runtime.rs diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 889439e93f0ec..c95f3cacbda2c 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -329,10 +329,6 @@ pub mod task; #[allow(missing_docs)] pub mod alloc; -/* Core library additions for `rad_protected` */ -#[stable(feature = "rad_protected", since = "1.95.0")] -pub mod rad_protected; - // note: does not need to be public mod bool; mod escape; diff --git a/library/core/src/rad_protected.rs b/library/core/src/rad_protected.rs deleted file mode 100644 index 6a19f53668a96..0000000000000 --- a/library/core/src/rad_protected.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Core library for runtime additions required by rad_protected - -use crate::mem; - -fn bitwise_majority_vote(a: u8, b: u8, c: u8) -> u8 { - (a & b) | (b & c) | (a & c) -} - -/// Perform bitwise majority vote of the triplicated call results for rad_protected -#[stable(feature = "rad_protected", since = "1.95.0")] -pub fn vote(a: T, b: T, c: T) -> T { - let size = mem::size_of::(); - - unsafe { - let a_ptr = &a as *const T as *mut u8; - let b_ptr = &b as *const T as *const u8; - let c_ptr = &c as *const T as *const u8; - - for i in 0..size { - let a_byte = a_ptr.add(i); - let b_byte = b_ptr.add(i); - let c_byte = c_ptr.add(i); - - *a_byte = bitwise_majority_vote(*a_byte, *b_byte, *c_byte); - } - } - - a -} - - -/// Contains the logic for rad_protected multithreading -/// All valid rad_protected `Multithreading` types must implement this trait -#[stable(feature = "rad_protected", since = "1.95.0")] -pub trait Multithreading { - - /// Run the triplicated functions on new threads - #[stable(feature = "rad_protected", since = "1.95.0")] - fn run_triple(f1: F1, f2: F2, f3: F3) -> T - where - F1: FnOnce() -> T + Send + 'static, - F2: FnOnce() -> T + Send + 'static, - F3: FnOnce() -> T + Send + 'static, - T: Send + 'static; - - /// Synchronizes the threads at the start of the critical section - /// Returns `true` for the leader thread, and `false` for the non-leaders - /// The non-leaders continue, waiting at `exit_critical_section` - #[stable(feature = "rad_protected", since = "1.95.0")] - fn enter_critical_section(&self) -> bool; - - /// Non-leader threads wait here for the leader to complete the critical section - #[stable(feature = "rad_protected", since = "1.95.0")] - fn exit_critical_section(&self); -} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 2cf06c79956f4..c78d99b2ad3ef 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -611,9 +611,12 @@ pub mod random; pub mod sync; pub mod time; - +/// Std library additions for `rad_protected` #[stable(feature = "rad_protected", since = "1.95.0")] pub mod rad_protected; +/// Re-export the runtime for easier usage +#[stable(feature = "rad_protected", since = "1.95.0")] +pub use rad_protected::runtime::Runtime as RadRustRuntime; // Pull in `std_float` crate into std. The contents of // `std_float` are in a different repository: rust-lang/portable-simd. diff --git a/library/std/src/rad_protected.rs b/library/std/src/rad_protected.rs deleted file mode 100644 index 61707bdbd1adb..0000000000000 --- a/library/std/src/rad_protected.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Standard library for runtime additions required by rad_protected - -use crate::thread; -use crate::sync::{Arc, Barrier}; -use core::rad_protected::{Multithreading, vote}; - -/// An implementation of rad_protected `Multithreading` using the Rust std library -#[stable(feature = "rad_protected", since = "1.95.0")] -#[derive(Clone, Debug)] -pub struct StdMultithreading { - inner_data: Arc -} - -#[derive(Debug)] -struct StdMultithreadingInnerData { - arrival_barrier: Barrier, - departure_barrier: Barrier, -} - -#[stable(feature = "rad_protected", since = "1.95.0")] -impl StdMultithreading { - - /// Create a new instance of `StdMultithreading` - #[stable(feature = "rad_protected", since = "1.95.0")] - pub fn new(num_threads: usize) -> Self { - Self { - inner_data: Arc::new(StdMultithreadingInnerData { - arrival_barrier: Barrier::new(num_threads), - departure_barrier: Barrier::new(num_threads), - }) - } - } -} - -#[stable(feature = "rad_protected", since = "1.95.0")] -impl Multithreading for StdMultithreading { - - fn run_triple(f1: F1, f2: F2, f3: F3) -> T - where - F1: FnOnce() -> T + Send + 'static, - F2: FnOnce() -> T + Send + 'static, - F3: FnOnce() -> T + Send + 'static, - T: Send + 'static, - { - let i1 = thread::spawn(f1); - let i2 = thread::spawn(f2); - let i3 = thread::spawn(f3); - - vote( - i1.join().unwrap(), - i2.join().unwrap(), - i3.join().unwrap() - ) - } - - fn enter_critical_section(&self) -> bool { - self.inner_data.arrival_barrier.wait().is_leader() - } - - fn exit_critical_section(&self) { - self.inner_data.departure_barrier.wait(); - } -} diff --git a/library/std/src/rad_protected/fork.rs b/library/std/src/rad_protected/fork.rs new file mode 100644 index 0000000000000..ead2a6265f33f --- /dev/null +++ b/library/std/src/rad_protected/fork.rs @@ -0,0 +1,90 @@ +use crate::{fs::File, io::{BufRead, BufReader}, ptr}; +use super::libc_helpers::{pipe, close, fork, sysconf_sc_pagesize}; +use super::role::{Child, ChildLink, SyncPipe}; + +pub(super) fn fork_copy() -> Option { + // Parent -> child + let (p2c_read, p2c_write) = pipe().ok()?; + // Child -> parent + let (c2p_read, c2p_write) = pipe().ok()?; + + match unsafe { fork() }.ok()? { + 0 => { + + close(p2c_write).ok()?; + close(c2p_read).ok()?; + + force_copy_pages(); + + Some(ForkOutcome::Child(Child::new( + SyncPipe::new(p2c_read, c2p_write) + ))) + }, + child_pid => { + + close(p2c_read).ok()?; + close(c2p_write).ok()?; + + Some(ForkOutcome::Parent(ChildLink::new( + child_pid, + SyncPipe::new(c2p_read, p2c_write) + ))) + }, + } +} + +pub(super) enum ForkOutcome { + Parent(ChildLink), + Child(Child), +} + +fn force_copy_pages() { + let page_size = sysconf_sc_pagesize().unwrap(); + + let file = File::open("/proc/self/maps").unwrap(); + let reader = BufReader::new(file); + + for line in reader.lines().map_while(Result::ok) { + let mut parts = line.split_whitespace(); + + let (Some(range), Some(perms)) = (parts.next(), parts.next()) else { + continue; + }; + + if !matches!(perms.as_bytes(), [_, b'w', _, b'p', ..]) { + continue; + } + + if line.contains("[vsyscall]") + || line.contains("[vvar]") + || line.contains("[vdso]") + { + continue; + } + + let Some((start, end)) = range.split_once('-') else { + continue; + }; + + let (Ok(start), Ok(end)) = ( + usize::from_str_radix(start, 16), + usize::from_str_radix(end, 16), + ) else { + continue; + }; + + unsafe { + let mut addr = start; + + while addr < end { + let p = ptr::without_provenance_mut::(addr); + + // Force a write so the kernel faults in a private copy of the COW page. + let v = ptr::read_volatile(p); + ptr::write_volatile(p, v); + + addr += page_size; + } + } + } +} diff --git a/library/std/src/rad_protected/libc_helpers.rs b/library/std/src/rad_protected/libc_helpers.rs new file mode 100644 index 0000000000000..0f2027b3779d6 --- /dev/null +++ b/library/std/src/rad_protected/libc_helpers.rs @@ -0,0 +1,75 @@ +use libc; +use crate::{io, ptr}; +use crate::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd}; + +pub(super) type Pid = libc::pid_t; + +pub(super) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> { + let mut fds: [libc::c_int; 2] = [0; 2]; + + if unsafe { libc::pipe(fds.as_mut_ptr()) } == -1 { + return Err(io::Error::last_os_error()); + } + // fds[0] is the read end, fds[1] is the write end + Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) }) +} + +pub(super) fn close(fd: OwnedFd) -> io::Result<()> { + if unsafe { libc::close(fd.into_raw_fd()) } == -1 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +pub(super) unsafe fn fork() -> io::Result { + let pid = unsafe { libc::fork() }; + + if pid == -1 { + return Err(io::Error::last_os_error()); + } + Ok(pid) +} + + +pub(super) fn read(fd: &OwnedFd, buf: &mut [u8]) -> io::Result { + let n = unsafe { libc::read(fd.as_raw_fd(), buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }; + + if n < 0 { + return Err(io::Error::last_os_error()); + } + Ok(n as isize) +} + +pub(super) fn write(fd: &OwnedFd, buf: &[u8]) -> io::Result { + let n = unsafe { libc::write(fd.as_raw_fd(), buf.as_ptr() as *const libc::c_void, buf.len()) }; + + if n < 0 { + return Err(io::Error::last_os_error()); + } + Ok(n as isize) +} + +pub(super) fn kill(pid: libc::pid_t) -> io::Result<()> { + if unsafe { libc::kill(pid, libc::SIGKILL) } == -1 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +pub(super) fn waitpid(pid: libc::pid_t) -> io::Result { + let pid = unsafe { libc::waitpid(pid, ptr::null_mut(), 0) }; + + if pid == -1 { + return Err(io::Error::last_os_error()); + } + Ok(pid) +} + +pub(super) fn sysconf_sc_pagesize() -> io::Result { + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; + + if page_size == -1 { + return Err(io::Error::last_os_error()); + } + Ok(page_size as usize) +} diff --git a/library/std/src/rad_protected/mod.rs b/library/std/src/rad_protected/mod.rs new file mode 100644 index 0000000000000..f0dfab1a9f267 --- /dev/null +++ b/library/std/src/rad_protected/mod.rs @@ -0,0 +1,7 @@ +/// `rad_protected` runtime module +#[stable(feature = "rad_protected", since = "1.95.0")] +pub mod runtime; + +mod fork; +mod libc_helpers; +mod role; diff --git a/library/std/src/rad_protected/role.rs b/library/std/src/rad_protected/role.rs new file mode 100644 index 0000000000000..d99a593adec42 --- /dev/null +++ b/library/std/src/rad_protected/role.rs @@ -0,0 +1,97 @@ +use crate::os::fd::OwnedFd; +use crate::sync::Mutex; +use super::libc_helpers::{write, read, kill, waitpid, Pid}; + +pub(super) static ROLE: Mutex> = Mutex::new(None); + +#[derive(Debug)] +pub(super) enum Role { + Parent(Parent), + Child(Child), +} + +impl Role { + pub(super) fn is_parent(&self) -> bool { + matches!(self, Role::Parent(_)) + } +} + +#[derive(Debug)] +pub struct Parent { + child1: ChildLink, + child2: ChildLink, +} + +impl Parent { + pub(super) fn new(child1: ChildLink, child2: ChildLink) -> Self { + Self { child1, child2 } + } + + pub(super) fn wait_for_children(&self) { + self.child1.pipe.wait_for_update(); + self.child2.pipe.wait_for_update(); + } + pub(super) fn update_children(&self) { + self.child1.pipe.send_update(); + self.child2.pipe.send_update(); + } + pub(super) fn kill_children(&self) { + self.child1.kill_child(); + self.child2.kill_child(); + } +} + +#[derive(Debug)] +pub struct Child { + pipe: SyncPipe, +} + +impl Child { + pub(super) fn new(pipe: SyncPipe) -> Self { + Self { pipe } + } + + pub(super) fn update_parent(&self) { + self.pipe.send_update(); + } + pub(super) fn wait_for_parent(&self) { + self.pipe.wait_for_update(); + } +} + +#[derive(Debug)] +pub(super) struct SyncPipe { + from_peer: OwnedFd, + to_peer: OwnedFd, +} + +impl SyncPipe { + pub(super) fn new(from_peer: OwnedFd, to_peer: OwnedFd) -> Self { + Self { from_peer, to_peer } + } + + pub(super) fn wait_for_update(&self) { + let mut buf = [0u8; 1]; + let _ = read(&self.from_peer, &mut buf); + } + pub(super) fn send_update(&self) { + let _ = write(&self.to_peer, &[0u8]); + } +} + +#[derive(Debug)] +pub(super) struct ChildLink { + pid: Pid, + pipe: SyncPipe, +} + +impl ChildLink { + pub(super) fn new(pid: Pid, pipe: SyncPipe) -> Self { + Self { pid, pipe } + } + + pub(super) fn kill_child(&self) { + let _ = kill(self.pid); + let _ = waitpid(self.pid); + } +} diff --git a/library/std/src/rad_protected/runtime.rs b/library/std/src/rad_protected/runtime.rs new file mode 100644 index 0000000000000..b54355a7420fd --- /dev/null +++ b/library/std/src/rad_protected/runtime.rs @@ -0,0 +1,109 @@ +use super::fork::{fork_copy, ForkOutcome}; +use super::role::{ROLE, Role, Parent}; + +/// Runtime for rad_protected +#[stable(feature = "rad_protected", since = "1.95.0")] +#[derive(Debug)] +pub struct Runtime; + +impl Runtime { + + /// Triplicate the running process over the current rad_protected function + /// Fork the running process and copy its memory to create 3 identical processes + #[stable(feature = "rad_protected", since = "1.95.0")] + pub fn triplicate_process() -> Result { + if ROLE.lock().unwrap().as_ref().is_some() { + return Err(()); + } + + let link1 = match fork_copy() { + Some(ForkOutcome::Parent(link1)) => link1, + Some(ForkOutcome::Child(child)) => { + ROLE.lock().unwrap().replace(Role::Child(child)); + return Ok(ProcessGuard{}); + } + None => { return Err(()); } + }; + + let link2 = match fork_copy() { + Some(ForkOutcome::Parent(link2)) => link2, + Some(ForkOutcome::Child(child)) => { + ROLE.lock().unwrap().replace(Role::Child(child)); + return Ok(ProcessGuard{}); + } + None => { + link1.kill_child(); + return Err(()); + } + }; + + ROLE.lock().unwrap().replace(Role::Parent(Parent::new( + link1, + link2, + ))); + + Ok(ProcessGuard{}) + } + + /// Enter a critical (unsafe) section of code, allowing only a single process through + /// Syncs the three processes. Returns `true` for the one leader (parent) process + #[stable(feature = "rad_protected", since = "1.95.0")] + pub fn enter_critical_section() -> bool { + let guard = ROLE.lock().unwrap(); + if let Some(role) = guard.as_ref() { + Self::sync(role); + return role.is_parent(); + } + true + } + + /// Exit a critical (unsafe) section of code + /// Syncs the three processes + #[stable(feature = "rad_protected", since = "1.95.0")] + pub fn exit_critical_section() { + let guard = ROLE.lock().unwrap(); + if let Some(role) = guard.as_ref() { + Self::sync(role); + } + } + + /// Close and clean up the triplicated processes at the end of rad_protected execution + #[stable(feature = "rad_protected", since = "1.95.0")] + pub fn close() { + let mut guard = ROLE.lock().unwrap(); + if let Some(role) = guard.as_ref() { + if let Role::Parent(parent) = role { + parent.kill_children(); + } + Self::sync(role); + } + guard.take(); + } + + fn sync(role: &Role) { + match role { + Role::Parent(parent) => { + parent.wait_for_children(); + parent.update_children(); + } + Role::Child(child) => { + child.update_parent(); + child.wait_for_parent(); + } + + } + } +} + +/// Guard to properly drop processes when done with the rad_protected function +#[stable(feature = "rad_protected", since = "1.95.0")] +#[derive(Debug)] +pub struct ProcessGuard; + +/// Drop method for `ProcessGuard`, close the child processes +#[stable(feature = "rad_protected", since = "1.95.0")] +impl Drop for ProcessGuard { + fn drop(&mut self) { + Runtime::close(); + } +} From 7a233380f09bb1e4d0b687a85b5a52341e602b9d Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:58:57 -0400 Subject: [PATCH 21/40] Update AST pass for new runtime API - In addition, remove function body triplication for now --- .../src/rad_protected/patch_unsafe.rs | 22 +- .../src/rad_protected/triplicate.rs | 291 ++---------------- compiler/rustc_span/src/symbol.rs | 8 +- 3 files changed, 37 insertions(+), 284 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs index e90edfdbd8bdb..d1e10c03844fe 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs @@ -1,7 +1,7 @@ use rustc_ast as ast; use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_expand::base::ExtCtxt; -use rustc_span::{symbol::Ident, sym, DUMMY_SP}; +use rustc_span::{symbol::Ident, Symbol, sym, DUMMY_SP}; use thin_vec::{ThinVec, thin_vec}; use rustc_ast::MetaItemInner; @@ -33,20 +33,20 @@ impl MutVisitor for UnsafeBlockRewriter<'_, '_> { fn patch_unsafe_block(cx: &ExtCtxt<'_>, block: &mut ast::Block) { - let multithreading_method_call = |name: &str| { - let multithreading_ident = Ident::from_str_and_span("_multithreading", DUMMY_SP); - let method_name_ident = Ident::from_str_and_span(name, DUMMY_SP); - - cx.expr_method_call( + let runtime_method_call = |name: Symbol| { + cx.expr_call_global( DUMMY_SP, - cx.expr_ident(DUMMY_SP, multithreading_ident), - method_name_ident, - thin_vec![], + vec![ + Ident::new(sym::std, DUMMY_SP), + Ident::new(sym::RadRustRuntime, DUMMY_SP), + Ident::new(name, DUMMY_SP), + ], + thin_vec![] ) }; - let enter_call = multithreading_method_call("enter_critical_section"); - let exit_call = multithreading_method_call("exit_critical_section"); + let enter_call = runtime_method_call(sym::enter_critical_section); + let exit_call = runtime_method_call(sym::exit_critical_section); let if_stmt = cx.stmt_expr(cx.expr_if( DUMMY_SP, diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index a7d002382242c..9794c965f86f3 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -1,299 +1,48 @@ use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Span, symbol::Ident, sym, Symbol, DUMMY_SP}; -use thin_vec::{thin_vec, ThinVec}; -use rustc_ast::{MetaItemInner, FnSig}; +use rustc_span::{Span, symbol::Ident, sym, DUMMY_SP}; +use thin_vec::thin_vec; use crate::rad_protected::patch_unsafe::patch_unsafe_blocks; pub(crate) fn triplicate( cx: &mut ExtCtxt<'_>, span: Span, - meta_item: &ast::MetaItem, - item: Annotatable, + _meta_item: &ast::MetaItem, + mut item: Annotatable, ) -> Vec { - let opts = match parse_attr_args(cx, meta_item) { - Some(o) => o, - None => return vec![item] - }; - - if opts.triplicate_unsafe { - let valid = matches!( - &item, - Annotatable::Expr(box ast::Expr { - kind: ast::ExprKind::Block(block, _), - .. - }) if matches!(block.rules, ast::BlockCheckMode::Unsafe(_)) - ); - - if !valid { - cx.dcx().span_err(span, "`#[rad_protected(triplicate_unsafe)]` can only be applied to `unsafe` blocks"); - } - return vec![item]; - } - let Annotatable::Item(box ast::Item { - kind: ast::ItemKind::Fn(box ref func), - ref vis, + kind: ast::ItemKind::Fn(box ref mut func), .. }) = item else { cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions"); return vec![item]; }; - let mut func_body = match &func.body { - Some(b) => b.clone(), + let mut func_body = match &mut func.body { + Some(b) => b, None => { cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions with a body"); return vec![item]; } }; - patch_unsafe_blocks(cx, &mut func_body); - - let make_inner_fn_stmt = |suffix_num: usize| { - - let mut sig = func.sig.clone(); - add_mutex_param(cx, &mut sig); - - let inner_fn = ast::Fn { - defaultness: ast::Defaultness::Implicit, - ident: inner_fn_ident(func.ident.name, suffix_num), - generics: func.generics.clone(), - sig, - contract: None, - define_opaque: None, - body: Some(func_body.clone()), - eii_impls: thin_vec![] - }; - - let inner_attrs = - if opts.triplicate_body { - let inline_attr = cx.attr_nested_word(sym::inline, sym::never, DUMMY_SP); - let link_section_attr = cx.attr_name_value_str_unsafe( - sym::link_section, - Symbol::intern(&format!(".text.{}_{}", func.ident.name, suffix_num)), - DUMMY_SP - ); - thin_vec![inline_attr, link_section_attr] - - } else { - thin_vec![] - }; - - cx.stmt_item(DUMMY_SP, cx.item( - DUMMY_SP, - inner_attrs, - ast::ItemKind::Fn(Box::new(inner_fn)) - )) - }; - - let call_args: ThinVec<_> = func.sig.decl.inputs.iter().filter_map(|param| { - match ¶m.pat.kind { - ast::PatKind::Ident(_, ident, _) => Some(cx.expr_ident(param.pat.span, *ident)), - _ => { - cx.dcx().span_err( - param.pat.span, - "unsupported parameter pattern in `#[rad_protected]`" - ); - None - } - } - }).collect(); - - let make_call_expr = |suffix_num: usize| { - let fn_ident = inner_fn_ident(func.ident.name, if opts.triplicate_body { suffix_num } else { 1 }); - - let multithreading_clone_expr = cx.expr_method_call( - DUMMY_SP, - cx.expr_ident(DUMMY_SP, multithreading_ident()), - Ident::new(sym::clone, DUMMY_SP), - thin_vec![], - ); - - let m_ident = Ident::from_str_and_span("m", DUMMY_SP); - let local_stmt = cx.stmt_let( - DUMMY_SP, - false, - m_ident, - multithreading_clone_expr, - ); - - let mut call_args = call_args.clone(); - call_args.push(cx.expr_ident(DUMMY_SP, m_ident)); - - let call = cx.expr_call_ident(DUMMY_SP, fn_ident, call_args); - - let body_block = cx.expr_block(cx.block(DUMMY_SP, thin_vec![ - cx.stmt_expr(call) - ])); - - let mut closure_expr = cx.lambda(DUMMY_SP, vec![], body_block); - - if let ast::ExprKind::Closure(ref mut closure) = closure_expr.kind { - closure.capture_clause = ast::CaptureBy::Value { move_kw: DUMMY_SP }; - } - - cx.expr_block(cx.block(DUMMY_SP, thin_vec![ - local_stmt, - cx.stmt_expr(closure_expr) - ])) - }; - - const NUM_DUPLICATES: usize = 3; - - let mut wrapper_stmts: ThinVec = thin_vec![]; - - wrapper_stmts.extend((1..=if opts.triplicate_body { NUM_DUPLICATES } else { 1 }).map(make_inner_fn_stmt)); - - let multithreading_use_item = { - let path = cx.path_global(DUMMY_SP, rad_protected_path(false, vec![ - Ident::from_str_and_span("Multithreading", DUMMY_SP), - ])); - - let use_tree = ast::UseTree { - prefix: path, - kind: ast::UseTreeKind::Simple(None), - span: DUMMY_SP, - }; - - cx.stmt_item(DUMMY_SP, cx.item( - DUMMY_SP, - thin_vec![], - ast::ItemKind::Use(use_tree), - )) - }; - wrapper_stmts.push(multithreading_use_item); - - let multithreading_init_stmt = { - let multithreading_init = cx.expr_call_global( - DUMMY_SP, - rad_protected_path(true, vec![ - multithreading_impl_ty_ident(), - Ident::new(sym::new, DUMMY_SP), - ]), - thin_vec![ - cx.expr_usize(DUMMY_SP, NUM_DUPLICATES) - ], - ); - - cx.stmt_let( + func_body.stmts.insert(0, cx.stmt_let( + DUMMY_SP, + false, + Ident::new(sym::__guard, DUMMY_SP), + cx.expr_call_global( DUMMY_SP, - false, - multithreading_ident(), - multithreading_init, + vec![ + Ident::new(sym::std, DUMMY_SP), + Ident::new(sym::RadRustRuntime, DUMMY_SP), + Ident::new(sym::triplicate_process, DUMMY_SP), + ], + thin_vec![] ) - }; - wrapper_stmts.push(multithreading_init_stmt); - - let run_triple_stmt = { - let run_triple_path = cx.path_global( - DUMMY_SP, - rad_protected_path(true, vec![ - multithreading_impl_ty_ident(), - Ident::from_str_and_span("run_triple", DUMMY_SP) - ]), - ); - - let run_triple_args: ThinVec<_> = (1..=NUM_DUPLICATES).map(make_call_expr).collect(); - - cx.stmt_expr(cx.expr_call( - DUMMY_SP, - cx.expr_path(run_triple_path), run_triple_args - )) - }; - wrapper_stmts.push(run_triple_stmt); - - let wrapper_body = cx.block(DUMMY_SP, wrapper_stmts); - - let wrapper_fn = ast::Fn { - defaultness: func.defaultness, - ident: func.ident, - sig: func.sig.clone(), - generics: func.generics.clone(), - body: Some(wrapper_body), - contract: func.contract.clone(), - define_opaque: func.define_opaque.clone(), - eii_impls: func.eii_impls.clone() - }; - - let mir_attr = cx.attr_word(sym::rad_protected_mir, DUMMY_SP); - - let mut wrapper = cx.item(DUMMY_SP, thin_vec![mir_attr], ast::ItemKind::Fn(Box::new(wrapper_fn))); - wrapper.vis = vis.clone(); - - vec![Annotatable::Item(wrapper)] -} - - -struct AttrOpts { - triplicate_body: bool, - triplicate_unsafe: bool, -} - -fn parse_attr_args(cx: &ExtCtxt<'_>, meta_item: &ast::MetaItem) -> Option { - - let attr_opts: ThinVec = match meta_item.kind { - ast::MetaItemKind::List(ref vec) => vec.clone(), - ast::MetaItemKind::Word => thin_vec![], - _ => { - cx.dcx().span_err(meta_item.span, "unsupported options kind in `#[rad_protected]`"); - return None; - } - }; - - let mut triplicate_body = true; - let mut triplicate_unsafe = false; - - for opt in attr_opts { - match opt { - MetaItemInner::MetaItem(opt) if opt.has_name(sym::no_triplicate_body) => { - triplicate_body = false; - } - MetaItemInner::MetaItem(opt) if opt.has_name(sym::triplicate_unsafe) => { - triplicate_unsafe = true; - } - _ => { - cx.dcx().span_err(opt.span(), "unsupported option in `#[rad_protected]`"); - return None; - } - } - } - - Some(AttrOpts { triplicate_body, triplicate_unsafe }) -} - -fn add_mutex_param(cx: &ExtCtxt<'_>, sig: &mut FnSig) { - sig.decl.inputs.push(cx.param( - DUMMY_SP, - multithreading_ident(), - cx.ty_path(cx.path_global(DUMMY_SP, rad_protected_path(true, vec![ - multithreading_impl_ty_ident(), - ]))) )); -} - -fn rad_protected_path(_impl_path: bool, tail: Vec) -> Vec { - let mut path = vec![ - Ident::new(if _impl_path { sym::std } else { sym::core }, DUMMY_SP), - Ident::new(sym::rad_protected, DUMMY_SP), - ]; - - path.extend(tail); - path -} -fn inner_fn_ident(name: Symbol, suffix_num: usize) -> Ident { - Ident::from_str_and_span( - &format!("__{}_{}", name, suffix_num), - DUMMY_SP - ) -} - -fn multithreading_ident() -> Ident { - Ident::from_str_and_span("_multithreading", DUMMY_SP) -} + patch_unsafe_blocks(cx, &mut func_body); -fn multithreading_impl_ty_ident() -> Ident { - Ident::from_str_and_span("StdMultithreading", DUMMY_SP) + vec![item] } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3a2b1fbc2103a..a994234ffaf74 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -283,6 +283,7 @@ symbols! { Pointer, Poll, ProcMacro, + RadRustRuntime, Range, RangeCopy, RangeFrom, @@ -347,6 +348,7 @@ symbols! { Vec, Wrapper, _DECLS, + __guard, __H, __S, __awaitee, @@ -878,6 +880,7 @@ symbols! { emscripten_wasm_eh, enable, end, + enter_critical_section, entry_nops, env, env_CFG_RELEASE: env!("CFG_RELEASE"), @@ -891,6 +894,7 @@ symbols! { exhaustive_integer_patterns, exhaustive_patterns, existential_type, + exit_critical_section, exp2f16, exp2f32, exp2f64, @@ -1592,8 +1596,6 @@ symbols! { quote, rad_protected, rad_protected_mir, - no_triplicate_body, - triplicate_unsafe, range_inclusive_new, raw_dash_dylib: "raw-dylib", raw_dylib, @@ -2043,6 +2045,8 @@ symbols! { transparent, transparent_enums, transparent_unions, + triplicate_unsafe, + triplicate_process, trivial_bounds, trivial_clone, truncf16, From 3752cb9048174dba1b66f9d1ea01d9fd201155a8 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:49:20 -0400 Subject: [PATCH 22/40] Track and patch unsafe blocks globally on the user's crate --- .../src/rad_protected/mod.rs | 1 - .../src/rad_protected/triplicate.rs | 5 +- compiler/rustc_expand/src/lib.rs | 2 + .../src}/patch_unsafe.rs | 64 +++++++++++++------ compiler/rustc_interface/src/passes.rs | 5 +- 5 files changed, 50 insertions(+), 27 deletions(-) rename compiler/{rustc_builtin_macros/src/rad_protected => rustc_expand/src}/patch_unsafe.rs (51%) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/mod.rs b/compiler/rustc_builtin_macros/src/rad_protected/mod.rs index 51094249e04b3..881356ab1d76a 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/mod.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/mod.rs @@ -1,4 +1,3 @@ mod triplicate; -mod patch_unsafe; pub(crate) use triplicate::triplicate; diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index 9794c965f86f3..f3fcb63489193 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -2,7 +2,6 @@ use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::{Span, symbol::Ident, sym, DUMMY_SP}; use thin_vec::thin_vec; -use crate::rad_protected::patch_unsafe::patch_unsafe_blocks; pub(crate) fn triplicate( cx: &mut ExtCtxt<'_>, @@ -19,7 +18,7 @@ pub(crate) fn triplicate( return vec![item]; }; - let mut func_body = match &mut func.body { + let func_body = match &mut func.body { Some(b) => b, None => { cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions with a body"); @@ -42,7 +41,5 @@ pub(crate) fn triplicate( ) )); - patch_unsafe_blocks(cx, &mut func_body); - vec![item] } diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 5914fee8315d4..4658ff3012acd 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -23,6 +23,8 @@ pub mod expand; pub mod module; pub mod proc_macro; +pub mod patch_unsafe; + pub fn provide(providers: &mut rustc_middle::query::Providers) { providers.derive_macro_expansion = proc_macro::provide_derive_macro_expansion; } diff --git a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs b/compiler/rustc_expand/src/patch_unsafe.rs similarity index 51% rename from compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs rename to compiler/rustc_expand/src/patch_unsafe.rs index d1e10c03844fe..7fe8fdedb2adf 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/patch_unsafe.rs +++ b/compiler/rustc_expand/src/patch_unsafe.rs @@ -1,17 +1,32 @@ use rustc_ast as ast; use rustc_ast::mut_visit::{self, MutVisitor}; -use rustc_expand::base::ExtCtxt; +use crate::base::ExtCtxt; use rustc_span::{symbol::Ident, Symbol, sym, DUMMY_SP}; use thin_vec::{ThinVec, thin_vec}; use rustc_ast::MetaItemInner; -pub(crate) fn patch_unsafe_blocks(cx: &ExtCtxt<'_>, body: &mut ast::Block) { +pub fn patch_unsafe_blocks(cx: &mut ExtCtxt<'_>, krate: &mut ast::Crate) { + if cx.sess.opts.unstable_opts.force_unstable_if_unmarked { + return; + } let mut visitor = UnsafeBlockRewriter { cx }; - visitor.visit_block(body); + visitor.visit_crate(krate); +} + +struct DummyIdAssigner<'a, 'cx> { + cx: &'a mut ExtCtxt<'cx>, +} + +impl MutVisitor for DummyIdAssigner<'_, '_> { + fn visit_id(&mut self, id: &mut ast::NodeId) { + if *id == ast::DUMMY_NODE_ID { + *id = self.cx.resolver.next_node_id(); + } + } } struct UnsafeBlockRewriter<'a, 'cx> { - cx: &'a ExtCtxt<'cx>, + cx: &'a mut ExtCtxt<'cx>, } impl MutVisitor for UnsafeBlockRewriter<'_, '_> { @@ -31,36 +46,35 @@ impl MutVisitor for UnsafeBlockRewriter<'_, '_> { } } -fn patch_unsafe_block(cx: &ExtCtxt<'_>, block: &mut ast::Block) { - - let runtime_method_call = |name: Symbol| { +fn patch_unsafe_block(cx: &mut ExtCtxt<'_>, block: &mut ast::Block) { + let runtime_method_call = |cx: &mut ExtCtxt<'_>, name: Symbol| { cx.expr_call_global( DUMMY_SP, vec![ Ident::new(sym::std, DUMMY_SP), Ident::new(sym::RadRustRuntime, DUMMY_SP), Ident::new(name, DUMMY_SP), - ], - thin_vec![] + ], + thin_vec![], ) }; - let enter_call = runtime_method_call(sym::enter_critical_section); - let exit_call = runtime_method_call(sym::exit_critical_section); + let enter_call = runtime_method_call(cx, sym::enter_critical_section); + let exit_call = runtime_method_call(cx, sym::exit_critical_section); - let if_stmt = cx.stmt_expr(cx.expr_if( - DUMMY_SP, - enter_call, - cx.expr_block(cx.block(block.span, block.stmts.clone())), - None, + let inner_block = cx.block(block.span, block.stmts.clone()); + let if_stmt = cx.stmt_expr(cx.expr_if(DUMMY_SP, + enter_call, + cx.expr_block(inner_block), + None )); - let exit_call_stmt = cx.stmt_expr(exit_call); - block.stmts = thin_vec![ - if_stmt, - exit_call_stmt - ]; + let mut assigner = DummyIdAssigner { cx }; + let if_stmt = assign_stmt(&mut assigner, if_stmt); + let exit_call_stmt = assign_stmt(&mut assigner, exit_call_stmt); + + block.stmts = thin_vec![if_stmt, exit_call_stmt]; } fn skip_patch(attrs: &ThinVec) -> bool { @@ -81,3 +95,11 @@ fn skip_patch(attrs: &ThinVec) -> bool { } }) } + +fn assign_stmt(assigner: &mut DummyIdAssigner<'_, '_>, stmt: ast::Stmt) -> ast::Stmt { + assigner + .flat_map_stmt(stmt) + .into_iter() + .next() + .expect("statement unexpectedly removed") +} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 15addd2407857..06dc7b61f959c 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -15,6 +15,7 @@ use rustc_data_structures::sync::{AppendOnlyIndexVec, FreezeLock, WorkerLocal, p use rustc_data_structures::thousands; use rustc_errors::timings::TimingSection; use rustc_expand::base::{ExtCtxt, LintStoreExpand}; +use rustc_expand::patch_unsafe::patch_unsafe_blocks; use rustc_feature::Features; use rustc_fs_util::try_canonicalize; use rustc_hir::attrs::AttributeKind; @@ -213,12 +214,14 @@ fn configure_and_expand( let mut ecx = ExtCtxt::new(sess, cfg, resolver, Some(&lint_store)); ecx.num_standard_library_imports = num_standard_library_imports; // Expand macros now! - let krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate)); + let mut krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate)); if ecx.nb_macro_errors > 0 { sess.dcx().abort_if_errors(); } + patch_unsafe_blocks(&mut ecx, &mut krate); + // The rest is error reporting and stats sess.psess.buffered_lints.with_lock(|buffered_lints: &mut Vec| { From 5cc724e46046e7fee6de11cbcb165fd74c59e2c2 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:38:21 -0400 Subject: [PATCH 23/40] Implement mini_std to remove dependency on std library --- library/std/src/rad_protected/fork.rs | 3 +- library/std/src/rad_protected/libc_helpers.rs | 4 +- library/std/src/rad_protected/mini_std/fs.rs | 36 ++++++++ library/std/src/rad_protected/mini_std/io.rs | 92 +++++++++++++++++++ library/std/src/rad_protected/mini_std/mod.rs | 20 ++++ library/std/src/rad_protected/mini_std/os.rs | 29 ++++++ .../std/src/rad_protected/mini_std/sync.rs | 62 +++++++++++++ library/std/src/rad_protected/mod.rs | 1 + library/std/src/rad_protected/role.rs | 3 +- 9 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 library/std/src/rad_protected/mini_std/fs.rs create mode 100644 library/std/src/rad_protected/mini_std/io.rs create mode 100644 library/std/src/rad_protected/mini_std/mod.rs create mode 100644 library/std/src/rad_protected/mini_std/os.rs create mode 100644 library/std/src/rad_protected/mini_std/sync.rs diff --git a/library/std/src/rad_protected/fork.rs b/library/std/src/rad_protected/fork.rs index ead2a6265f33f..e77bf2bcb0295 100644 --- a/library/std/src/rad_protected/fork.rs +++ b/library/std/src/rad_protected/fork.rs @@ -1,4 +1,5 @@ -use crate::{fs::File, io::{BufRead, BufReader}, ptr}; +use super::mini_std::{fs::File, io::BufReader}; +use core::ptr; use super::libc_helpers::{pipe, close, fork, sysconf_sc_pagesize}; use super::role::{Child, ChildLink, SyncPipe}; diff --git a/library/std/src/rad_protected/libc_helpers.rs b/library/std/src/rad_protected/libc_helpers.rs index 0f2027b3779d6..5c4e9a47f6796 100644 --- a/library/std/src/rad_protected/libc_helpers.rs +++ b/library/std/src/rad_protected/libc_helpers.rs @@ -1,6 +1,6 @@ +use super::mini_std::{io, os::fd::OwnedFd}; +use core::ptr; use libc; -use crate::{io, ptr}; -use crate::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd}; pub(super) type Pid = libc::pid_t; diff --git a/library/std/src/rad_protected/mini_std/fs.rs b/library/std/src/rad_protected/mini_std/fs.rs new file mode 100644 index 0000000000000..c082e964f061f --- /dev/null +++ b/library/std/src/rad_protected/mini_std/fs.rs @@ -0,0 +1,36 @@ +use alloc::ffi::CString; +use super::io; + +pub struct File { + fd: i32, +} + +impl File { + pub fn open(path: &str) -> io::Result { + let path = CString::new(path).unwrap(); + let fd = unsafe { libc::open(path.as_ptr(), libc::O_RDONLY) }; + + if fd == -1 { + return Err(io::Error::last_os_error()); + } + Ok(Self { fd }) + } + + pub fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = buf.len(); + let buf = buf.as_mut_ptr() as *mut libc::c_void; + + let bytes_read = unsafe { libc::read(self.fd, buf, len) }; + + if bytes_read == -1 { + return Err(io::Error::last_os_error()); + } + Ok(bytes_read) + } +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { libc::close(self.fd); } + } +} diff --git a/library/std/src/rad_protected/mini_std/io.rs b/library/std/src/rad_protected/mini_std/io.rs new file mode 100644 index 0000000000000..1eca92002ed64 --- /dev/null +++ b/library/std/src/rad_protected/mini_std/io.rs @@ -0,0 +1,92 @@ +use super::fs; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::str::Utf8Error; + +pub type Result = core::result::Result; + +#[derive(Debug)] +pub enum Error { + Os(i32), + Utf8(Utf8Error), +} + +impl Error { + #[cfg(target_os = "linux")] + pub fn last_os_error() -> Self { + Self::Os(unsafe { *libc::__errno_location() }) + } + + #[allow(unused)] + pub fn raw_os_error(&self) -> Option { + match self { + Error::Os(errno) => Some(*errno), + _ => None + } + } + + #[allow(unused)] + pub fn raw_utf8_error(&self) -> Option { + match self { + Error::Utf8(err) => Some(*err), + _ => None + } + } +} + +pub struct BufReader { + file: fs::File, +} + +impl BufReader { + pub fn new(file: fs::File) -> Self { + Self { file } + } + + fn read_line(&mut self) -> Option> { + let mut bytes = Vec::new(); + + loop { + let mut buf = [0u8; 1]; + + match self.file.read(&mut buf) { + Ok(0) => { + if bytes.is_empty() { + return None; + } + break; + } + Ok(_) => { + if buf[0] == b'\n' { + break; + } + bytes.push(buf[0]); + } + Err(err) => { + return Some(Err(err)); + } + } + } + + match String::from_utf8(bytes) { + Ok(str) => Some(Ok(str)), + Err(err) => Some(Err(Error::Utf8(err.utf8_error()))) + } + } + + pub fn lines(self) -> Lines { + Lines { reader: self } + } +} + +pub struct Lines { + reader: BufReader, +} + +impl Iterator for Lines { + type Item = Result; + + fn next(&mut self) -> Option { + self.reader.read_line() + } +} diff --git a/library/std/src/rad_protected/mini_std/mod.rs b/library/std/src/rad_protected/mini_std/mod.rs new file mode 100644 index 0000000000000..f18e5649fc1d8 --- /dev/null +++ b/library/std/src/rad_protected/mini_std/mod.rs @@ -0,0 +1,20 @@ +//! A near-faithful recreation of the minimal subset of `std` required by `rad_protected` +//! for `no_std` environments. +//! +//! This module provides a `std`-like API to isolate platform-specific functionality. +//! +//! The current implementation targets Linux, but it is intended to be reimplemented for +//! other platforms as needed. +//! +//! Because this module mirrors the `std` API, it can be removed entirely on platforms +//! where the standard library is available. +//! +//! Since some `rad_protected` functions are guaranteed to invoke certain `mini_std` +//! functions from a single thread, synchronization is not uniformly implemented throughout this module. +//! +//! Be careful when using these APIs, as they may not be thread-safe outside of their intended usage. + +pub mod io; +pub mod sync; +pub mod fs; +pub mod os; diff --git a/library/std/src/rad_protected/mini_std/os.rs b/library/std/src/rad_protected/mini_std/os.rs new file mode 100644 index 0000000000000..5c3ce49e6af68 --- /dev/null +++ b/library/std/src/rad_protected/mini_std/os.rs @@ -0,0 +1,29 @@ +pub mod fd { + use core::mem::ManuallyDrop; + + #[derive(Debug)] + pub struct OwnedFd { + fd: i32, + } + + impl OwnedFd { + pub unsafe fn from_raw_fd(fd: i32) -> Self { + Self { fd } + } + + pub fn into_raw_fd(self) -> i32 { + let this = ManuallyDrop::new(self); + this.fd + } + + pub fn as_raw_fd(&self) -> i32 { + self.fd + } + } + + impl Drop for OwnedFd { + fn drop(&mut self) { + unsafe { libc::close(self.fd); } + } + } +} diff --git a/library/std/src/rad_protected/mini_std/sync.rs b/library/std/src/rad_protected/mini_std/sync.rs new file mode 100644 index 0000000000000..a7f44dc552a26 --- /dev/null +++ b/library/std/src/rad_protected/mini_std/sync.rs @@ -0,0 +1,62 @@ +use libc; +use core::{cell::UnsafeCell, ops::{Deref, DerefMut}}; + +pub struct Mutex { + lock: libc::pthread_mutex_t, + data: UnsafeCell, +} + +pub struct MutexGuard<'a, T> { + mutex: &'a Mutex, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +type LockResult = Result; + +impl Mutex { + pub const fn new(data: T) -> Self { + Self { + lock: libc::PTHREAD_MUTEX_INITIALIZER, + data: UnsafeCell::new(data), + } + } + + pub fn lock(&self) -> LockResult> { + let res = unsafe { libc::pthread_mutex_lock(&self.lock as *const _ as *mut _) }; + + if res != 0 { + return Err(res); + } + Ok(MutexGuard { mutex: self }) + } +} + +impl Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.mutex.data.get() } + } +} + +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.mutex.data.get() } + } +} + +impl Drop for MutexGuard<'_, T> { + fn drop(&mut self) { + unsafe { libc::pthread_mutex_unlock(&self.mutex.lock as *const _ as *mut _); } + } +} + +impl Drop for Mutex { + fn drop(&mut self) { + unsafe { + libc::pthread_mutex_destroy(&mut self.lock); + } + } +} diff --git a/library/std/src/rad_protected/mod.rs b/library/std/src/rad_protected/mod.rs index f0dfab1a9f267..dc9d818d80c58 100644 --- a/library/std/src/rad_protected/mod.rs +++ b/library/std/src/rad_protected/mod.rs @@ -5,3 +5,4 @@ pub mod runtime; mod fork; mod libc_helpers; mod role; +mod mini_std; diff --git a/library/std/src/rad_protected/role.rs b/library/std/src/rad_protected/role.rs index d99a593adec42..3fa6cc500457d 100644 --- a/library/std/src/rad_protected/role.rs +++ b/library/std/src/rad_protected/role.rs @@ -1,5 +1,4 @@ -use crate::os::fd::OwnedFd; -use crate::sync::Mutex; +use super::mini_std::{os::fd::OwnedFd, sync::Mutex}; use super::libc_helpers::{write, read, kill, waitpid, Pid}; pub(super) static ROLE: Mutex> = Mutex::new(None); From 4e71a29b9d9a47032cfefde98ffebbd862fc8ea5 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:24:19 -0400 Subject: [PATCH 24/40] Move rad_protected to its own library, exported through std --- library/Cargo.lock | 11 +++++++++ library/rad_protected/Cargo.toml | 24 +++++++++++++++++++ .../src}/fork.rs | 0 library/rad_protected/src/lib.rs | 21 ++++++++++++++++ .../src}/libc_helpers.rs | 0 .../src}/mini_std/fs.rs | 0 .../src}/mini_std/io.rs | 0 .../src}/mini_std/mod.rs | 0 .../src}/mini_std/os.rs | 0 .../src}/mini_std/sync.rs | 0 .../src}/role.rs | 0 .../src}/runtime.rs | 0 library/std/Cargo.toml | 1 + library/std/src/lib.rs | 3 --- library/std/src/rad_protected/mod.rs | 8 ------- 15 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 library/rad_protected/Cargo.toml rename library/{std/src/rad_protected => rad_protected/src}/fork.rs (100%) create mode 100644 library/rad_protected/src/lib.rs rename library/{std/src/rad_protected => rad_protected/src}/libc_helpers.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/mini_std/fs.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/mini_std/io.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/mini_std/mod.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/mini_std/os.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/mini_std/sync.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/role.rs (100%) rename library/{std/src/rad_protected => rad_protected/src}/runtime.rs (100%) delete mode 100644 library/std/src/rad_protected/mod.rs diff --git a/library/Cargo.lock b/library/Cargo.lock index 4801f92c63e5a..87c47bebcaa24 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -248,6 +248,16 @@ dependencies = [ "rustc-std-workspace-core", ] +[[package]] +name = "rad_protected" +version = "0.0.0" +dependencies = [ + "alloc", + "compiler_builtins", + "core", + "libc", +] + [[package]] name = "rand" version = "0.9.2" @@ -338,6 +348,7 @@ dependencies = [ "panic_unwind", "r-efi", "r-efi-alloc", + "rad_protected", "rand", "rand_xorshift", "rustc-demangle", diff --git a/library/rad_protected/Cargo.toml b/library/rad_protected/Cargo.toml new file mode 100644 index 0000000000000..9a3956e5e7baf --- /dev/null +++ b/library/rad_protected/Cargo.toml @@ -0,0 +1,24 @@ +cargo-features = ["public-dependency"] + +[package] +name = "rad_protected" +version = "0.0.0" +edition = "2024" +repository = "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/rad-rust/rust.git" +description = "Runtime support for Rad-Rust Software TMR" + +[lib] +crate-type = ["rlib"] + +[dependencies] +core = { path = "../core", public = true } +alloc = { path = "../alloc", public = true } +compiler_builtins = { path = "../compiler-builtins/compiler-builtins", features = ["rustc-dep-of-std"] } + +[target.'cfg(not(all(windows, target_env = "msvc")))'.dependencies] +libc = { version = "0.2.178", default-features = false, features = [ + 'rustc-dep-of-std', +], public = true } + +[lints.rust.unexpected_cfgs] +level = "warn" diff --git a/library/std/src/rad_protected/fork.rs b/library/rad_protected/src/fork.rs similarity index 100% rename from library/std/src/rad_protected/fork.rs rename to library/rad_protected/src/fork.rs diff --git a/library/rad_protected/src/lib.rs b/library/rad_protected/src/lib.rs new file mode 100644 index 0000000000000..afde7283a4cfc --- /dev/null +++ b/library/rad_protected/src/lib.rs @@ -0,0 +1,21 @@ +//! The Rad-Rust `rad_protected` Runtime +//! +//! This library contains a static runtime for running software radiation-hardened programs through TMR. +//! +//! The runtime is intended to be used with a rustc fork to generate runtime calls, +//! and thus only meant to be referenced directly by the compiler itself. +//! +//! For porting this library to a new target, see mini_std/mod.rs + +#![feature(staged_api)] +#![allow(internal_features)] +#![no_std] + +/// `rad_protected` runtime module +#[stable(feature = "rad_protected", since = "1.95.0")] +pub mod runtime; + +mod fork; +mod libc_helpers; +mod role; +mod mini_std; diff --git a/library/std/src/rad_protected/libc_helpers.rs b/library/rad_protected/src/libc_helpers.rs similarity index 100% rename from library/std/src/rad_protected/libc_helpers.rs rename to library/rad_protected/src/libc_helpers.rs diff --git a/library/std/src/rad_protected/mini_std/fs.rs b/library/rad_protected/src/mini_std/fs.rs similarity index 100% rename from library/std/src/rad_protected/mini_std/fs.rs rename to library/rad_protected/src/mini_std/fs.rs diff --git a/library/std/src/rad_protected/mini_std/io.rs b/library/rad_protected/src/mini_std/io.rs similarity index 100% rename from library/std/src/rad_protected/mini_std/io.rs rename to library/rad_protected/src/mini_std/io.rs diff --git a/library/std/src/rad_protected/mini_std/mod.rs b/library/rad_protected/src/mini_std/mod.rs similarity index 100% rename from library/std/src/rad_protected/mini_std/mod.rs rename to library/rad_protected/src/mini_std/mod.rs diff --git a/library/std/src/rad_protected/mini_std/os.rs b/library/rad_protected/src/mini_std/os.rs similarity index 100% rename from library/std/src/rad_protected/mini_std/os.rs rename to library/rad_protected/src/mini_std/os.rs diff --git a/library/std/src/rad_protected/mini_std/sync.rs b/library/rad_protected/src/mini_std/sync.rs similarity index 100% rename from library/std/src/rad_protected/mini_std/sync.rs rename to library/rad_protected/src/mini_std/sync.rs diff --git a/library/std/src/rad_protected/role.rs b/library/rad_protected/src/role.rs similarity index 100% rename from library/std/src/rad_protected/role.rs rename to library/rad_protected/src/role.rs diff --git a/library/std/src/rad_protected/runtime.rs b/library/rad_protected/src/runtime.rs similarity index 100% rename from library/std/src/rad_protected/runtime.rs rename to library/rad_protected/src/runtime.rs diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 1b7a41d697367..8296ef9e78459 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["dylib", "rlib"] [dependencies] alloc = { path = "../alloc", public = true } +rad_protected = { path = "../rad_protected", public = true } # std no longer uses cfg-if directly, but the included copy of backtrace does. cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] } panic_unwind = { path = "../panic_unwind", optional = true } diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index c78d99b2ad3ef..a5a2d903fadcb 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -611,9 +611,6 @@ pub mod random; pub mod sync; pub mod time; -/// Std library additions for `rad_protected` -#[stable(feature = "rad_protected", since = "1.95.0")] -pub mod rad_protected; /// Re-export the runtime for easier usage #[stable(feature = "rad_protected", since = "1.95.0")] pub use rad_protected::runtime::Runtime as RadRustRuntime; diff --git a/library/std/src/rad_protected/mod.rs b/library/std/src/rad_protected/mod.rs deleted file mode 100644 index dc9d818d80c58..0000000000000 --- a/library/std/src/rad_protected/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// `rad_protected` runtime module -#[stable(feature = "rad_protected", since = "1.95.0")] -pub mod runtime; - -mod fork; -mod libc_helpers; -mod role; -mod mini_std; From 766d602880bb7875eaeabc5466d38efe665fece2 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:23:16 -0400 Subject: [PATCH 25/40] Use IPC via a shared mmap instead of pipes --- library/rad_protected/src/fork.rs | 28 ++----- library/rad_protected/src/lib.rs | 1 + library/rad_protected/src/libc_helpers.rs | 96 ++++++++++++++-------- library/rad_protected/src/mini_std/ipc.rs | 59 +++++++++++++ library/rad_protected/src/mini_std/mod.rs | 6 +- library/rad_protected/src/mini_std/os.rs | 29 ------- library/rad_protected/src/role.rs | 75 ++++++++--------- library/rad_protected/src/runtime.rs | 44 +++++----- library/rad_protected/src/shared_memory.rs | 60 ++++++++++++++ 9 files changed, 245 insertions(+), 153 deletions(-) create mode 100644 library/rad_protected/src/mini_std/ipc.rs delete mode 100644 library/rad_protected/src/mini_std/os.rs create mode 100644 library/rad_protected/src/shared_memory.rs diff --git a/library/rad_protected/src/fork.rs b/library/rad_protected/src/fork.rs index e77bf2bcb0295..c03b924d8ad22 100644 --- a/library/rad_protected/src/fork.rs +++ b/library/rad_protected/src/fork.rs @@ -1,42 +1,24 @@ use super::mini_std::{fs::File, io::BufReader}; use core::ptr; -use super::libc_helpers::{pipe, close, fork, sysconf_sc_pagesize}; -use super::role::{Child, ChildLink, SyncPipe}; +use super::libc_helpers::{fork, sysconf_sc_pagesize}; +use super::role::ChildLink; pub(super) fn fork_copy() -> Option { - // Parent -> child - let (p2c_read, p2c_write) = pipe().ok()?; - // Child -> parent - let (c2p_read, c2p_write) = pipe().ok()?; match unsafe { fork() }.ok()? { 0 => { - - close(p2c_write).ok()?; - close(c2p_read).ok()?; - force_copy_pages(); - - Some(ForkOutcome::Child(Child::new( - SyncPipe::new(p2c_read, c2p_write) - ))) + Some(ForkOutcome::Child) }, child_pid => { - - close(p2c_read).ok()?; - close(c2p_write).ok()?; - - Some(ForkOutcome::Parent(ChildLink::new( - child_pid, - SyncPipe::new(c2p_read, p2c_write) - ))) + Some(ForkOutcome::Parent(ChildLink::new(child_pid))) }, } } pub(super) enum ForkOutcome { Parent(ChildLink), - Child(Child), + Child, } fn force_copy_pages() { diff --git a/library/rad_protected/src/lib.rs b/library/rad_protected/src/lib.rs index afde7283a4cfc..029d7279c239c 100644 --- a/library/rad_protected/src/lib.rs +++ b/library/rad_protected/src/lib.rs @@ -19,3 +19,4 @@ mod fork; mod libc_helpers; mod role; mod mini_std; +mod shared_memory; diff --git a/library/rad_protected/src/libc_helpers.rs b/library/rad_protected/src/libc_helpers.rs index 5c4e9a47f6796..feb09ba249670 100644 --- a/library/rad_protected/src/libc_helpers.rs +++ b/library/rad_protected/src/libc_helpers.rs @@ -1,28 +1,27 @@ -use super::mini_std::{io, os::fd::OwnedFd}; -use core::ptr; +use super::mini_std::io; +use core::{ptr, sync::atomic::AtomicU32}; use libc; pub(super) type Pid = libc::pid_t; -pub(super) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> { - let mut fds: [libc::c_int; 2] = [0; 2]; +pub(super) unsafe fn fork() -> io::Result { + let pid = unsafe { libc::fork() }; - if unsafe { libc::pipe(fds.as_mut_ptr()) } == -1 { + if pid == -1 { return Err(io::Error::last_os_error()); } - // fds[0] is the read end, fds[1] is the write end - Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) }) + Ok(pid) } -pub(super) fn close(fd: OwnedFd) -> io::Result<()> { - if unsafe { libc::close(fd.into_raw_fd()) } == -1 { +pub(super) fn kill(pid: libc::pid_t) -> io::Result<()> { + if unsafe { libc::kill(pid, libc::SIGKILL) } == -1 { return Err(io::Error::last_os_error()); } Ok(()) } -pub(super) unsafe fn fork() -> io::Result { - let pid = unsafe { libc::fork() }; +pub(super) fn waitpid(pid: libc::pid_t) -> io::Result { + let pid = unsafe { libc::waitpid(pid, ptr::null_mut(), 0) }; if pid == -1 { return Err(io::Error::last_os_error()); @@ -30,46 +29,75 @@ pub(super) unsafe fn fork() -> io::Result { Ok(pid) } +pub(super) fn sysconf_sc_pagesize() -> io::Result { + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; -pub(super) fn read(fd: &OwnedFd, buf: &mut [u8]) -> io::Result { - let n = unsafe { libc::read(fd.as_raw_fd(), buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }; - - if n < 0 { + if page_size == -1 { return Err(io::Error::last_os_error()); } - Ok(n as isize) + Ok(page_size as usize) } -pub(super) fn write(fd: &OwnedFd, buf: &[u8]) -> io::Result { - let n = unsafe { libc::write(fd.as_raw_fd(), buf.as_ptr() as *const libc::c_void, buf.len()) }; - - if n < 0 { +pub(super) fn shared_mmap(size: usize) -> io::Result<*mut libc::c_void> { + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED | libc::MAP_ANON, + -1, + 0, + ) + }; + + if ptr == libc::MAP_FAILED { return Err(io::Error::last_os_error()); } - Ok(n as isize) + Ok(ptr) } -pub(super) fn kill(pid: libc::pid_t) -> io::Result<()> { - if unsafe { libc::kill(pid, libc::SIGKILL) } == -1 { - return Err(io::Error::last_os_error()); - } - Ok(()) +pub(super) fn munmap(ptr: *mut libc::c_void, size: usize) { + unsafe { libc::munmap(ptr, size); } } -pub(super) fn waitpid(pid: libc::pid_t) -> io::Result { - let pid = unsafe { libc::waitpid(pid, ptr::null_mut(), 0) }; +pub(super) fn futex_wait(addr: &AtomicU32, expected: u32) -> io::Result<()> { + let addr = addr as *const _ as *const u32; - if pid == -1 { + let res = unsafe { + libc::syscall( + libc::SYS_futex, + addr, + libc::FUTEX_WAIT, + expected, + ptr::null::(), + ptr::null::(), + 0 + ) + }; + + if res == -1 { return Err(io::Error::last_os_error()); } - Ok(pid) + Ok(()) } -pub(super) fn sysconf_sc_pagesize() -> io::Result { - let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +pub(super) fn futex_wake_all(addr: &AtomicU32) -> io::Result { + let addr = addr as *const _ as *const u32; + + let res = unsafe { + libc::syscall( + libc::SYS_futex, + addr, + libc::FUTEX_WAKE, + libc::INT_MAX, + ptr::null::(), + ptr::null::(), + 0 + ) + } as i32; - if page_size == -1 { + if res == -1 { return Err(io::Error::last_os_error()); } - Ok(page_size as usize) + Ok(res) } diff --git a/library/rad_protected/src/mini_std/ipc.rs b/library/rad_protected/src/mini_std/ipc.rs new file mode 100644 index 0000000000000..cdd4637e16061 --- /dev/null +++ b/library/rad_protected/src/mini_std/ipc.rs @@ -0,0 +1,59 @@ +use core::sync::atomic::{AtomicU32, Ordering}; +use super::super::libc_helpers::{futex_wait, futex_wake_all}; + +pub struct Barrier { + threshold: u32, + count: AtomicU32, + _gen: AtomicU32, +} + +impl Barrier { + pub fn new(threshold: u32) -> Self { + Self { + threshold, + count: AtomicU32::new(0), + _gen: AtomicU32::new(0) + } + } + + pub fn wait(&self) -> BarrierWaitResult { + let _gen = self._gen.load(Ordering::Acquire); + let arrived = self.count.fetch_add(1, Ordering::AcqRel) + 1; + + if arrived == self.threshold { + self.count.store(0, Ordering::Relaxed); + self._gen.fetch_add(1, Ordering::Release); + + let _ = futex_wake_all(&self._gen); + + return BarrierWaitResult(true); + } + + loop { + if self._gen.load(Ordering::Acquire) != _gen { + break; + } + + let res = futex_wait(&self._gen, _gen); + + if let Err(err) = res { + let err = err.raw_os_error().unwrap(); + + match err { + libc::EAGAIN | libc::EINTR => continue, + _ => panic!("Failed to acquire futex"), + } + } + } + + BarrierWaitResult(false) + } +} + +pub struct BarrierWaitResult(bool); + +impl BarrierWaitResult { + pub fn is_leader(&self) -> bool { + self.0 + } +} diff --git a/library/rad_protected/src/mini_std/mod.rs b/library/rad_protected/src/mini_std/mod.rs index f18e5649fc1d8..82937204db5aa 100644 --- a/library/rad_protected/src/mini_std/mod.rs +++ b/library/rad_protected/src/mini_std/mod.rs @@ -1,4 +1,4 @@ -//! A near-faithful recreation of the minimal subset of `std` required by `rad_protected` +//! A near-faithful recreation and extension of the minimal subset of `std` required by `rad_protected` //! for `no_std` environments. //! //! This module provides a `std`-like API to isolate platform-specific functionality. @@ -7,7 +7,7 @@ //! other platforms as needed. //! //! Because this module mirrors the `std` API, it can be removed entirely on platforms -//! where the standard library is available. +//! where the standard library is available, other than the IPC module. //! //! Since some `rad_protected` functions are guaranteed to invoke certain `mini_std` //! functions from a single thread, synchronization is not uniformly implemented throughout this module. @@ -17,4 +17,4 @@ pub mod io; pub mod sync; pub mod fs; -pub mod os; +pub mod ipc; diff --git a/library/rad_protected/src/mini_std/os.rs b/library/rad_protected/src/mini_std/os.rs deleted file mode 100644 index 5c3ce49e6af68..0000000000000 --- a/library/rad_protected/src/mini_std/os.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub mod fd { - use core::mem::ManuallyDrop; - - #[derive(Debug)] - pub struct OwnedFd { - fd: i32, - } - - impl OwnedFd { - pub unsafe fn from_raw_fd(fd: i32) -> Self { - Self { fd } - } - - pub fn into_raw_fd(self) -> i32 { - let this = ManuallyDrop::new(self); - this.fd - } - - pub fn as_raw_fd(&self) -> i32 { - self.fd - } - } - - impl Drop for OwnedFd { - fn drop(&mut self) { - unsafe { libc::close(self.fd); } - } - } -} diff --git a/library/rad_protected/src/role.rs b/library/rad_protected/src/role.rs index 3fa6cc500457d..33939baa7ab85 100644 --- a/library/rad_protected/src/role.rs +++ b/library/rad_protected/src/role.rs @@ -1,5 +1,6 @@ -use super::mini_std::{os::fd::OwnedFd, sync::Mutex}; -use super::libc_helpers::{write, read, kill, waitpid, Pid}; +use super::mini_std::sync::Mutex; +use super::libc_helpers::{kill, waitpid, Pid}; +use super::shared_memory::SharedMemory; pub(super) static ROLE: Mutex> = Mutex::new(None); @@ -9,84 +10,72 @@ pub(super) enum Role { Child(Child), } -impl Role { - pub(super) fn is_parent(&self) -> bool { - matches!(self, Role::Parent(_)) - } -} - #[derive(Debug)] pub struct Parent { + shared_memory: SharedMemory, child1: ChildLink, child2: ChildLink, } impl Parent { - pub(super) fn new(child1: ChildLink, child2: ChildLink) -> Self { - Self { child1, child2 } + pub(super) fn new(shared_memory: SharedMemory, child1: ChildLink, child2: ChildLink) -> Self { + Self { shared_memory, child1, child2 } } - pub(super) fn wait_for_children(&self) { - self.child1.pipe.wait_for_update(); - self.child2.pipe.wait_for_update(); - } - pub(super) fn update_children(&self) { - self.child1.pipe.send_update(); - self.child2.pipe.send_update(); - } pub(super) fn kill_children(&self) { self.child1.kill_child(); self.child2.kill_child(); } + + pub(super) fn close_shared_mem(&self) { + self.shared_memory.close(); + } } #[derive(Debug)] pub struct Child { - pipe: SyncPipe, + shared_memory: SharedMemory } impl Child { - pub(super) fn new(pipe: SyncPipe) -> Self { - Self { pipe } - } - - pub(super) fn update_parent(&self) { - self.pipe.send_update(); - } - pub(super) fn wait_for_parent(&self) { - self.pipe.wait_for_update(); + pub(super) fn new(shared_memory: SharedMemory) -> Self { + Self { shared_memory } } } -#[derive(Debug)] -pub(super) struct SyncPipe { - from_peer: OwnedFd, - to_peer: OwnedFd, +pub(super) trait Syncable { + fn sync(&self) -> bool; } -impl SyncPipe { - pub(super) fn new(from_peer: OwnedFd, to_peer: OwnedFd) -> Self { - Self { from_peer, to_peer } +impl Syncable for Parent { + fn sync(&self) -> bool { + self.shared_memory.sync() } +} - pub(super) fn wait_for_update(&self) { - let mut buf = [0u8; 1]; - let _ = read(&self.from_peer, &mut buf); +impl Syncable for Child { + fn sync(&self) -> bool { + self.shared_memory.sync() } - pub(super) fn send_update(&self) { - let _ = write(&self.to_peer, &[0u8]); +} + +impl Syncable for Role { + fn sync(&self) -> bool { + match self { + Role::Parent(parent) => parent.sync(), + Role::Child(child) => child.sync(), + } } } #[derive(Debug)] pub(super) struct ChildLink { pid: Pid, - pipe: SyncPipe, } impl ChildLink { - pub(super) fn new(pid: Pid, pipe: SyncPipe) -> Self { - Self { pid, pipe } + pub(super) fn new(pid: Pid) -> Self { + Self { pid } } pub(super) fn kill_child(&self) { diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index b54355a7420fd..041fecb207899 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -1,5 +1,6 @@ use super::fork::{fork_copy, ForkOutcome}; -use super::role::{ROLE, Role, Parent}; +use super::role::{ROLE, Role, Parent, Child, Syncable}; +use super::shared_memory::SharedMemory; /// Runtime for rad_protected #[stable(feature = "rad_protected", since = "1.95.0")] @@ -16,28 +17,37 @@ impl Runtime { return Err(()); } + let Ok(shared_memory) = SharedMemory::open() else { + return Err(()); + }; + let link1 = match fork_copy() { Some(ForkOutcome::Parent(link1)) => link1, - Some(ForkOutcome::Child(child)) => { - ROLE.lock().unwrap().replace(Role::Child(child)); + Some(ForkOutcome::Child) => { + ROLE.lock().unwrap().replace(Role::Child(Child::new(shared_memory))); return Ok(ProcessGuard{}); } - None => { return Err(()); } + None => { + shared_memory.close(); + return Err(()); + } }; let link2 = match fork_copy() { Some(ForkOutcome::Parent(link2)) => link2, - Some(ForkOutcome::Child(child)) => { - ROLE.lock().unwrap().replace(Role::Child(child)); + Some(ForkOutcome::Child) => { + ROLE.lock().unwrap().replace(Role::Child(Child::new(shared_memory))); return Ok(ProcessGuard{}); } None => { + shared_memory.close(); link1.kill_child(); return Err(()); } }; ROLE.lock().unwrap().replace(Role::Parent(Parent::new( + shared_memory, link1, link2, ))); @@ -51,8 +61,7 @@ impl Runtime { pub fn enter_critical_section() -> bool { let guard = ROLE.lock().unwrap(); if let Some(role) = guard.as_ref() { - Self::sync(role); - return role.is_parent(); + return Self::sync(role); } true } @@ -72,26 +81,19 @@ impl Runtime { pub fn close() { let mut guard = ROLE.lock().unwrap(); if let Some(role) = guard.as_ref() { + Self::sync(role); if let Role::Parent(parent) = role { parent.kill_children(); + parent.close_shared_mem(); + } else { + unsafe { libc::pause(); } } - Self::sync(role); } guard.take(); } - fn sync(role: &Role) { - match role { - Role::Parent(parent) => { - parent.wait_for_children(); - parent.update_children(); - } - Role::Child(child) => { - child.update_parent(); - child.wait_for_parent(); - } - - } + fn sync(role: &Role) -> bool { + role.sync() } } diff --git a/library/rad_protected/src/shared_memory.rs b/library/rad_protected/src/shared_memory.rs new file mode 100644 index 0000000000000..f47b448b16a65 --- /dev/null +++ b/library/rad_protected/src/shared_memory.rs @@ -0,0 +1,60 @@ +use super::libc_helpers::{shared_mmap, munmap}; +use super::mini_std::{io, ipc::Barrier}; +use core::{mem::size_of, ptr, ops::{Deref, DerefMut}}; + +pub(super) struct SharedMemoryData { + barrier: Barrier, +} + +impl SharedMemoryData { + pub(super) fn sync(&self) -> bool { + self.barrier.wait().is_leader() + } +} + +#[derive(Debug)] +pub(super) struct SharedMemory { + inner: *mut SharedMemoryData, +} + +impl SharedMemory { + pub(super) fn open() -> io::Result { + let size = size_of::(); + + let ptr = shared_mmap(size)?; + + let inner = ptr.cast::(); + + unsafe { + ptr::write(inner, + SharedMemoryData { + barrier: Barrier::new(3), + }, + ); + } + + Ok(Self { inner }) + } + + pub(super) fn close(&self) { + unsafe { ptr::drop_in_place(self.inner); } + munmap(self.inner as *mut _, size_of::()); + } +} + +unsafe impl Send for SharedMemory {} +unsafe impl Sync for SharedMemory {} + +impl Deref for SharedMemory { + type Target = SharedMemoryData; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.inner } + } +} + +impl DerefMut for SharedMemory { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.inner } + } +} From 622289c9c2e9f2a99d37906798389a98cd4f23dd Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:20:04 -0400 Subject: [PATCH 26/40] Fix bug where nested critical sections causes a Barrier desync --- library/rad_protected/src/role.rs | 70 +++++++++++++++++++++------- library/rad_protected/src/runtime.rs | 20 ++++---- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/library/rad_protected/src/role.rs b/library/rad_protected/src/role.rs index 33939baa7ab85..e5c5c7caeffd9 100644 --- a/library/rad_protected/src/role.rs +++ b/library/rad_protected/src/role.rs @@ -12,14 +12,18 @@ pub(super) enum Role { #[derive(Debug)] pub struct Parent { - shared_memory: SharedMemory, + shared_mem_ctx: SharedMemoryContext, child1: ChildLink, child2: ChildLink, } impl Parent { pub(super) fn new(shared_memory: SharedMemory, child1: ChildLink, child2: ChildLink) -> Self { - Self { shared_memory, child1, child2 } + Self { + shared_mem_ctx: SharedMemoryContext::new(shared_memory), + child1, + child2 + } } pub(super) fn kill_children(&self) { @@ -28,42 +32,74 @@ impl Parent { } pub(super) fn close_shared_mem(&self) { - self.shared_memory.close(); + self.shared_mem_ctx.shared_memory.close(); } } #[derive(Debug)] pub struct Child { - shared_memory: SharedMemory + shared_mem_ctx: SharedMemoryContext, } impl Child { pub(super) fn new(shared_memory: SharedMemory) -> Self { - Self { shared_memory } + Self { + shared_mem_ctx: SharedMemoryContext::new(shared_memory), + } } } -pub(super) trait Syncable { - fn sync(&self) -> bool; +#[derive(Debug)] +pub(super) struct SharedMemoryContext { + shared_memory: SharedMemory, + leader_depth: u32, } -impl Syncable for Parent { - fn sync(&self) -> bool { - self.shared_memory.sync() +impl SharedMemoryContext { + pub(super) fn new(shared_memory: SharedMemory) -> Self { + Self { shared_memory, leader_depth: 0 } } -} -impl Syncable for Child { - fn sync(&self) -> bool { + pub(super) fn sync(&self) -> bool { self.shared_memory.sync() } + + pub(super) fn enter_critical_section(&mut self) -> bool { + let leader = self.is_leader() || self.shared_memory.sync(); + + if leader { + self.leader_depth += 1; + } + + leader + } + + pub(super) fn exit_critical_section(&mut self) { + if self.is_leader() { + self.leader_depth -= 1; + } + + if !self.is_leader() { + self.shared_memory.sync(); + } + } + + fn is_leader(&self) -> bool { + self.leader_depth > 0 + } } -impl Syncable for Role { - fn sync(&self) -> bool { +impl Role { + pub(super) fn ctx(&self) -> &SharedMemoryContext { + match self { + Role::Parent(parent) => &parent.shared_mem_ctx, + Role::Child(child) => &child.shared_mem_ctx, + } + } + pub(super) fn ctx_mut(&mut self) -> &mut SharedMemoryContext { match self { - Role::Parent(parent) => parent.sync(), - Role::Child(child) => child.sync(), + Role::Parent(parent) => &mut parent.shared_mem_ctx, + Role::Child(child) => &mut child.shared_mem_ctx, } } } diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 041fecb207899..cdf79c9a28a58 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -1,5 +1,5 @@ use super::fork::{fork_copy, ForkOutcome}; -use super::role::{ROLE, Role, Parent, Child, Syncable}; +use super::role::{ROLE, Role, Parent, Child}; use super::shared_memory::SharedMemory; /// Runtime for rad_protected @@ -59,9 +59,9 @@ impl Runtime { /// Syncs the three processes. Returns `true` for the one leader (parent) process #[stable(feature = "rad_protected", since = "1.95.0")] pub fn enter_critical_section() -> bool { - let guard = ROLE.lock().unwrap(); - if let Some(role) = guard.as_ref() { - return Self::sync(role); + let mut guard = ROLE.lock().unwrap(); + if let Some(role) = guard.as_mut() { + return role.ctx_mut().enter_critical_section(); } true } @@ -70,9 +70,9 @@ impl Runtime { /// Syncs the three processes #[stable(feature = "rad_protected", since = "1.95.0")] pub fn exit_critical_section() { - let guard = ROLE.lock().unwrap(); - if let Some(role) = guard.as_ref() { - Self::sync(role); + let mut guard = ROLE.lock().unwrap(); + if let Some(role) = guard.as_mut() { + return role.ctx_mut().exit_critical_section(); } } @@ -81,7 +81,7 @@ impl Runtime { pub fn close() { let mut guard = ROLE.lock().unwrap(); if let Some(role) = guard.as_ref() { - Self::sync(role); + role.ctx().sync(); if let Role::Parent(parent) = role { parent.kill_children(); parent.close_shared_mem(); @@ -91,10 +91,6 @@ impl Runtime { } guard.take(); } - - fn sync(role: &Role) -> bool { - role.sync() - } } /// Guard to properly drop processes when done with the rad_protected function From 503347e8aedbf94951f745beec4894500214c9f0 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:10:05 -0400 Subject: [PATCH 27/40] Fix bug with and reimplement triplicate_unsafe option --- .../src/rad_protected/mod.rs | 1 + .../src/rad_protected/parse_attr_opts.rs | 43 +++++++++++++++++++ .../src/rad_protected/triplicate.rs | 33 +++++++++++++- compiler/rustc_expand/src/patch_unsafe.rs | 38 ++++++++++------ 4 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 compiler/rustc_builtin_macros/src/rad_protected/parse_attr_opts.rs diff --git a/compiler/rustc_builtin_macros/src/rad_protected/mod.rs b/compiler/rustc_builtin_macros/src/rad_protected/mod.rs index 881356ab1d76a..dcc313066936e 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/mod.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/mod.rs @@ -1,3 +1,4 @@ mod triplicate; +mod parse_attr_opts; pub(crate) use triplicate::triplicate; diff --git a/compiler/rustc_builtin_macros/src/rad_protected/parse_attr_opts.rs b/compiler/rustc_builtin_macros/src/rad_protected/parse_attr_opts.rs new file mode 100644 index 0000000000000..f90829d7f84b1 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/rad_protected/parse_attr_opts.rs @@ -0,0 +1,43 @@ +use rustc_ast as ast; +use rustc_ast::MetaItemInner; +use rustc_expand::base::ExtCtxt; +use thin_vec::{thin_vec, ThinVec}; +use rustc_span::sym; + +pub(super) struct AttrOpts { + triplicate_unsafe: bool, +} + +impl AttrOpts { + pub(super) fn triplicate_unsafe(&self) -> bool { + self.triplicate_unsafe + } +} + +pub(super) fn parse_attr_opts(cx: &ExtCtxt<'_>, meta_item: &ast::MetaItem) -> Option { + + let attr_opts: ThinVec = match meta_item.kind { + ast::MetaItemKind::List(ref vec) => vec.clone(), + ast::MetaItemKind::Word => thin_vec![], + _ => { + cx.dcx().span_err(meta_item.span, "unsupported options kind in `#[rad_protected]`"); + return None; + } + }; + + let mut triplicate_unsafe = false; + + for opt in attr_opts { + match opt { + MetaItemInner::MetaItem(opt) if opt.has_name(sym::triplicate_unsafe) => { + triplicate_unsafe = true; + } + _ => { + cx.dcx().span_err(opt.span(), "unsupported option in `#[rad_protected]`"); + return None; + } + } + } + + Some(AttrOpts { triplicate_unsafe }) +} diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index f3fcb63489193..03d331a4b92be 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -2,14 +2,45 @@ use rustc_ast as ast; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::{Span, symbol::Ident, sym, DUMMY_SP}; use thin_vec::thin_vec; +use super::parse_attr_opts::parse_attr_opts; pub(crate) fn triplicate( cx: &mut ExtCtxt<'_>, span: Span, - _meta_item: &ast::MetaItem, + meta_item: &ast::MetaItem, mut item: Annotatable, ) -> Vec { + let Some(opts) = parse_attr_opts(cx, meta_item) else { + return vec![item]; + }; + + if opts.triplicate_unsafe() { + let valid = match &mut item { + Annotatable::Expr(expr) + if matches!(&expr.kind, ast::ExprKind::Block(block, _) + if matches!(block.rules, ast::BlockCheckMode::Unsafe(_)) + ) => { + expr.attrs.push(cx.attr_nested_word( + sym::rad_protected_mir, + sym::triplicate_unsafe, + DUMMY_SP, + )); + true + } + _ => false, + }; + + if !valid { + cx.dcx().span_err( + span, + "`#[rad_protected(triplicate_unsafe)]` can only be applied to `unsafe` blocks", + ); + } + + return vec![item]; + } + let Annotatable::Item(box ast::Item { kind: ast::ItemKind::Fn(box ref mut func), .. diff --git a/compiler/rustc_expand/src/patch_unsafe.rs b/compiler/rustc_expand/src/patch_unsafe.rs index 7fe8fdedb2adf..3304fb197f6de 100644 --- a/compiler/rustc_expand/src/patch_unsafe.rs +++ b/compiler/rustc_expand/src/patch_unsafe.rs @@ -35,7 +35,7 @@ impl MutVisitor for UnsafeBlockRewriter<'_, '_> { if let ast::ExprKind::Block(block, _) = &mut expr.kind { if matches!(block.rules, ast::BlockCheckMode::Unsafe(_)) { - if !skip_patch(&expr.attrs) { + if !skip_patch(&mut expr.attrs) { patch_unsafe_block(self.cx, block); } return; @@ -77,22 +77,32 @@ fn patch_unsafe_block(cx: &mut ExtCtxt<'_>, block: &mut ast::Block) { block.stmts = thin_vec![if_stmt, exit_call_stmt]; } -fn skip_patch(attrs: &ThinVec) -> bool { - attrs.iter().any(|attr| { - let Some(meta) = attr.meta() else { - return false; - }; +fn skip_patch(attrs: &mut ThinVec) -> bool { + let mut removed = false; - if !meta.has_name(sym::rad_protected) { - return false; - } + attrs.retain(|attr| { + let keep = !attr.meta().is_some_and(is_skip_attr); - match &meta.kind { - ast::MetaItemKind::List(items) => items.iter().any(|item| { - matches!(item, MetaItemInner::MetaItem(mi) if mi.has_name(sym::triplicate_unsafe)) - }), - _ => false, + if !keep { + removed = true; } + keep + }); + + removed +} + +fn is_skip_attr(meta: ast::MetaItem) -> bool { + if !meta.has_name(sym::rad_protected_mir) { + return false; + } + + let ast::MetaItemKind::List(items) = &meta.kind else { + return false; + }; + + items.iter().any(|item| { + matches!(item, MetaItemInner::MetaItem(mi) if mi.has_name(sym::triplicate_unsafe)) }) } From 6dfdd0addba8695d7ea891d082daead127da9a8b Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:40:27 -0400 Subject: [PATCH 28/40] Re-enable MIR analysis pass --- .../src/rad_protected/triplicate.rs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index 03d331a4b92be..b62b588a2cc2b 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -41,19 +41,25 @@ pub(crate) fn triplicate( return vec![item]; } - let Annotatable::Item(box ast::Item { - kind: ast::ItemKind::Fn(box ref mut func), - .. - }) = item else { + let Annotatable::Item(mut item) = item else { cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions"); return vec![item]; }; + let ast::Item { + kind: ast::ItemKind::Fn(func), + .. + } = &mut *item + else { + cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions"); + return vec![Annotatable::Item(item)]; + }; + let func_body = match &mut func.body { Some(b) => b, None => { cx.dcx().span_err(span, "`#[rad_protected]` can only be applied to functions with a body"); - return vec![item]; + return vec![Annotatable::Item(item)]; } }; @@ -72,5 +78,8 @@ pub(crate) fn triplicate( ) )); - vec![item] + let mir_attr = cx.attr_word(sym::rad_protected_mir, DUMMY_SP); + item.attrs.push(mir_attr); + + vec![Annotatable::Item(item)] } From 89d18a7612633e1158f3804ad7e6409a15ca18fd Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Wed, 5 Aug 2026 03:41:48 -0400 Subject: [PATCH 29/40] Implement Liveness analysis MIR tracking --- compiler/rustc_mir_transform/src/lib.rs | 1 + .../src/rad_protected_liveness_analysis.rs | 195 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 125bc5decf119..850539412a8a8 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -51,6 +51,7 @@ mod lint; mod lint_tail_expr_drop_order; mod liveness; mod patch; +mod rad_protected_liveness_analysis; mod shim; mod ssa; mod trivial_const; diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs new file mode 100644 index 0000000000000..dd889b663ff8a --- /dev/null +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -0,0 +1,195 @@ +use rustc_middle::mir::{BasicBlock, Location, visit::{PlaceContext, Visitor}}; +use rustc_middle::mir::{ + Body, BasicBlocks, TerminatorKind, Local, Place, Rvalue +}; +use std::ops::{Deref, DerefMut}; +use std::collections::VecDeque; +use rustc_data_structures::{fx::FxHashSet, graph::Successors}; +use rustc_index::IndexVec; + +pub(super) struct LivenessAnalysis { + liveness: IndexVec, +} + +// Liveness Analysis Pass (based on: https://en.wikipedia.org/wiki/Live-variable_analysis) +impl LivenessAnalysis { + pub(super) fn analyze<'tcx>(body: &Body<'tcx>) -> Self { + let gk_analysis = Self::generate_gk_analysis(&body.basic_blocks); + Self::calculate_liveness(gk_analysis, &body.basic_blocks) + } + + fn calculate_liveness<'tcx>(gk_analysis: GenKillAnalysis, basic_blocks: &BasicBlocks<'tcx>) -> Self { + let mut liveness = IndexVec::from_fn_n( + |_| Liveness::new(), + basic_blocks.len() + ); + + let mut work_queue: VecDeque = VecDeque::from([gk_analysis.exit_block]); + + while !work_queue.is_empty() { + let bb_idx = work_queue.pop_front().unwrap(); + + let old_in = liveness[bb_idx]._in.clone(); + liveness[bb_idx].out.clear(); + + for s in basic_blocks.successors(bb_idx) { + let successor_in = liveness[s]._in.clone(); + // Order independent since target is unordered (HashSet) + #[allow(rustc::potential_query_instability)] + liveness[bb_idx].out.extend(successor_in); + } + + let mut live_in = gk_analysis.gen_kill[bb_idx]._gen.clone(); + // Order independent since target is unordered (HashSet) + #[allow(rustc::potential_query_instability)] + live_in.extend(liveness[bb_idx].out.difference(&gk_analysis.gen_kill[bb_idx].kill)); + liveness[bb_idx]._in = live_in; + + if liveness[bb_idx]._in != old_in { + for p in basic_blocks.predecessors()[bb_idx].clone() { + work_queue.push_back(p); + } + } + } + + Self { liveness } + } + + fn generate_gk_analysis<'tcx>(basic_blocks: &BasicBlocks<'tcx>) -> GenKillAnalysis { + let mut gen_kill = IndexVec::::from_fn_n( + |_| GenKill::new(), + basic_blocks.len() + ); + let mut exit_block: Option = None; + + for (bb_idx, bb_data) in basic_blocks.iter_enumerated() { + let mut collector = GenKillCollector::new(); + + for (stmt_idx, stmt) in bb_data.statements.iter().enumerate() { + let location = Location { + block: bb_idx, + statement_index: stmt_idx, + }; + + collector.visit_statement(stmt, location); + } + + let terminator_location = Location { + block: bb_idx, + statement_index: bb_data.statements.len(), + }; + + collector.visit_terminator(bb_data.terminator(), terminator_location); + + gen_kill[bb_idx] = collector.take(); + + if matches!(bb_data.terminator().kind, TerminatorKind::Return) { + exit_block = Some(bb_idx); + } + } + + GenKillAnalysis { gen_kill, exit_block: exit_block.unwrap() } + } + +} + +impl Deref for LivenessAnalysis { + type Target = IndexVec; + + fn deref(&self) -> &Self::Target { + &self.liveness + } +} + +impl DerefMut for LivenessAnalysis { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.liveness + } +} + +pub(super) struct Liveness { + _in: FxHashSet, + out: FxHashSet, +} + +impl Liveness { + fn new() -> Self { + Self { + _in: FxHashSet::default(), + out: FxHashSet::default(), + } + } + + pub(super) fn _in(&self) -> &FxHashSet { + &self._in + } + pub(super) fn out(&self) -> &FxHashSet { + &self.out + } +} + +struct GenKillAnalysis { + gen_kill: IndexVec, + exit_block: BasicBlock, +} + + +struct GenKillCollector { + gen_kill: GenKill, +} + +impl GenKillCollector { + fn new() -> Self { + Self { gen_kill: GenKill::new() } + } + + fn take(self) -> GenKill { + self.gen_kill + } +} + +impl<'tcx> Visitor<'tcx> for GenKillCollector { + fn visit_local(&mut self, local: Local, context: PlaceContext, _location: Location) { + + if context.is_place_assignment() { + self.gen_kill.push_kill(local); + } + else if context.is_mutating_use() { + self.gen_kill.push_gen(local); + // The push_kill is not strictly necessary here, but it follows the convention + // of kill marking all writes (not just writes before a use) + self.gen_kill.push_kill(local); + + } + else if context.is_use() { + self.gen_kill.push_gen(local); + } + } + + fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { + self.visit_rvalue(rvalue, location); + self.gen_kill.push_kill(place.local); + } +} + +struct GenKill { + _gen: FxHashSet, + kill: FxHashSet, +} + +impl GenKill { + fn push_kill(&mut self, local: Local) { + self.kill.insert(local); + } + fn push_gen(&mut self, local: Local) { + if !self.kill.contains(&local) { + self._gen.insert(local); + } + } + fn new() -> Self { + Self { + _gen: FxHashSet::default(), + kill: FxHashSet::default(), + } + } +} From 69d514a9209588944399484bf1ba8c41a82ceb54 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Wed, 5 Aug 2026 03:42:28 -0400 Subject: [PATCH 30/40] Add debug print for liveness analysis results --- .../src/rad_protected_analysis.rs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index ebe33b2b1cc23..d0f4af9d9b3ab 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -7,6 +7,7 @@ use rustc_middle::mir::{ }; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, TyCtxt}; +use super::rad_protected_liveness_analysis::LivenessAnalysis; pub(super) struct RadProtectedAnalysis; @@ -24,6 +25,35 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { return; } + let liveness = LivenessAnalysis::analyze(body); + + eprintln!("=== Liveness analysis for {:?} ===", def_id); + for (bb, live) in liveness.iter_enumerated() { + eprintln!("BasicBlock {:?}", bb); + + eprintln!(" IN:"); + for local in sorted_locals(&live._in()) { + let decl = &body.local_decls[local]; + eprintln!( + " {:?}: {:?}", + local, + decl.ty + ); + } + + eprintln!(" OUT:"); + for local in sorted_locals(&live.out()) { + let decl = &body.local_decls[local]; + eprintln!( + " {:?}: {:?}", + local, + decl.ty + ); + } + } + + eprintln!("================================"); + let sources = build_pointer_sources(body); with_no_trimmed_paths!({ @@ -422,3 +452,13 @@ fn resolve_pointer_source<'tcx>( } } } + +fn sorted_locals(set: &FxHashSet) -> Vec { + // Values are sorted after becoming an iter + // Additionally, this method is only used to print debugging info + #[allow(rustc::potential_query_instability)] + let mut locals: Vec = set.iter().copied().collect(); + + locals.sort(); + locals +} From 6e5389f5c05bc5886c29830ec4a8a93780ed02fd Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:31:57 -0400 Subject: [PATCH 31/40] Fix bug in liveness algorithm --- .../src/rad_protected_liveness_analysis.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs index dd889b663ff8a..f36fac5fe4b45 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -1,6 +1,6 @@ use rustc_middle::mir::{BasicBlock, Location, visit::{PlaceContext, Visitor}}; use rustc_middle::mir::{ - Body, BasicBlocks, TerminatorKind, Local, Place, Rvalue + Body, BasicBlocks, Local, Place, Rvalue }; use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; @@ -24,7 +24,7 @@ impl LivenessAnalysis { basic_blocks.len() ); - let mut work_queue: VecDeque = VecDeque::from([gk_analysis.exit_block]); + let mut work_queue: VecDeque = basic_blocks.indices().collect(); while !work_queue.is_empty() { let bb_idx = work_queue.pop_front().unwrap(); @@ -60,7 +60,6 @@ impl LivenessAnalysis { |_| GenKill::new(), basic_blocks.len() ); - let mut exit_block: Option = None; for (bb_idx, bb_data) in basic_blocks.iter_enumerated() { let mut collector = GenKillCollector::new(); @@ -82,13 +81,9 @@ impl LivenessAnalysis { collector.visit_terminator(bb_data.terminator(), terminator_location); gen_kill[bb_idx] = collector.take(); - - if matches!(bb_data.terminator().kind, TerminatorKind::Return) { - exit_block = Some(bb_idx); - } } - GenKillAnalysis { gen_kill, exit_block: exit_block.unwrap() } + GenKillAnalysis { gen_kill } } } @@ -130,7 +125,6 @@ impl Liveness { struct GenKillAnalysis { gen_kill: IndexVec, - exit_block: BasicBlock, } From 2a3d49770c68408eb45a13acf46dd5096813b170 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:57:03 -0400 Subject: [PATCH 32/40] Optimize liveness algorithm --- .../src/rad_protected_liveness_analysis.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs index f36fac5fe4b45..6d35fbe11312a 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -5,7 +5,7 @@ use rustc_middle::mir::{ use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; use rustc_data_structures::{fx::FxHashSet, graph::Successors}; -use rustc_index::IndexVec; +use rustc_index::{bit_set::DenseBitSet, IndexVec}; pub(super) struct LivenessAnalysis { liveness: IndexVec, @@ -24,10 +24,11 @@ impl LivenessAnalysis { basic_blocks.len() ); - let mut work_queue: VecDeque = basic_blocks.indices().collect(); + let mut work_queue: VecDeque = basic_blocks.indices().rev().collect(); + let mut in_queue = DenseBitSet::new_filled(basic_blocks.len()); - while !work_queue.is_empty() { - let bb_idx = work_queue.pop_front().unwrap(); + while let Some(bb_idx) = work_queue.pop_front() { + in_queue.remove(bb_idx); let old_in = liveness[bb_idx]._in.clone(); liveness[bb_idx].out.clear(); @@ -46,8 +47,10 @@ impl LivenessAnalysis { liveness[bb_idx]._in = live_in; if liveness[bb_idx]._in != old_in { - for p in basic_blocks.predecessors()[bb_idx].clone() { - work_queue.push_back(p); + for &p in basic_blocks.predecessors()[bb_idx].iter() { + if in_queue.insert(p) { + work_queue.push_back(p); + } } } } From d0bf7e6266dd8e7e8ec6674d55fb69335b72df26 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 6 Aug 2026 21:45:15 -0400 Subject: [PATCH 33/40] Modify liveness analysis to track Checkpoint sync vars --- .../src/rad_protected_analysis.rs | 30 ++---------- .../src/rad_protected_liveness_analysis.rs | 49 ++++++++++++++++--- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index d0f4af9d9b3ab..f3d7d7702636f 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -7,7 +7,7 @@ use rustc_middle::mir::{ }; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, TyCtxt}; -use super::rad_protected_liveness_analysis::LivenessAnalysis; +use super::rad_protected_liveness_analysis::CheckpointAnalysis; pub(super) struct RadProtectedAnalysis; @@ -25,33 +25,13 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { return; } - let liveness = LivenessAnalysis::analyze(body); + let checkpoint_analysis = CheckpointAnalysis::analyze(body); eprintln!("=== Liveness analysis for {:?} ===", def_id); - for (bb, live) in liveness.iter_enumerated() { - eprintln!("BasicBlock {:?}", bb); - - eprintln!(" IN:"); - for local in sorted_locals(&live._in()) { - let decl = &body.local_decls[local]; - eprintln!( - " {:?}: {:?}", - local, - decl.ty - ); - } - - eprintln!(" OUT:"); - for local in sorted_locals(&live.out()) { - let decl = &body.local_decls[local]; - eprintln!( - " {:?}: {:?}", - local, - decl.ty - ); - } + for (bb_idx, live) in checkpoint_analysis.checkpoints { + eprintln!("Checkpoint {:?}", bb_idx); + eprintln!("\tSync: {:?}\n", sorted_locals(&live.out())); } - eprintln!("================================"); let sources = build_pointer_sources(body); diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs index 6d35fbe11312a..25ed5c90e7494 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -1,20 +1,50 @@ use rustc_middle::mir::{BasicBlock, Location, visit::{PlaceContext, Visitor}}; use rustc_middle::mir::{ - Body, BasicBlocks, Local, Place, Rvalue + Body, BasicBlocks, Local, Place, Rvalue, TerminatorKind, BasicBlockData }; use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; use rustc_data_structures::{fx::FxHashSet, graph::Successors}; use rustc_index::{bit_set::DenseBitSet, IndexVec}; -pub(super) struct LivenessAnalysis { +pub(super) struct CheckpointAnalysis { + pub checkpoints: Vec<(BasicBlock, Liveness)>, +} + +impl CheckpointAnalysis { + pub(super) fn analyze<'tcx>(body: &Body<'tcx>) -> Self { + Self { + checkpoints: + LivenessAnalysis::analyze(body) + .liveness + .into_iter_enumerated() + .filter(|(bb_idx, _)| Self::is_checkpoint(&body.basic_blocks[*bb_idx])) + .collect() + } + } + + fn postprocess_gen_kill<'tcx>(body: &Body<'tcx>, bb_data: &BasicBlockData<'_>, gen_kill: &mut GenKill) { + if Self::is_checkpoint(&bb_data) { + // Order independent since target is unordered (HashSet) + #[allow(rustc::potential_query_instability)] + gen_kill.kill.extend(body.local_decls.indices()); + } + } + + pub(super) fn is_checkpoint(bb_data: &BasicBlockData<'_>) -> bool { + matches!(bb_data.terminator().kind, TerminatorKind::Call{..} | TerminatorKind::TailCall{..}) + } +} + + +struct LivenessAnalysis { liveness: IndexVec, } // Liveness Analysis Pass (based on: https://en.wikipedia.org/wiki/Live-variable_analysis) impl LivenessAnalysis { - pub(super) fn analyze<'tcx>(body: &Body<'tcx>) -> Self { - let gk_analysis = Self::generate_gk_analysis(&body.basic_blocks); + fn analyze<'tcx>(body: &Body<'tcx>) -> Self { + let gk_analysis = Self::generate_gk_analysis(&body); Self::calculate_liveness(gk_analysis, &body.basic_blocks) } @@ -58,13 +88,13 @@ impl LivenessAnalysis { Self { liveness } } - fn generate_gk_analysis<'tcx>(basic_blocks: &BasicBlocks<'tcx>) -> GenKillAnalysis { + fn generate_gk_analysis<'tcx>(body: &Body<'tcx>) -> GenKillAnalysis { let mut gen_kill = IndexVec::::from_fn_n( |_| GenKill::new(), - basic_blocks.len() + body.basic_blocks.len() ); - for (bb_idx, bb_data) in basic_blocks.iter_enumerated() { + for (bb_idx, bb_data) in body.basic_blocks.iter_enumerated() { let mut collector = GenKillCollector::new(); for (stmt_idx, stmt) in bb_data.statements.iter().enumerate() { @@ -84,11 +114,14 @@ impl LivenessAnalysis { collector.visit_terminator(bb_data.terminator(), terminator_location); gen_kill[bb_idx] = collector.take(); + + // Special GenKill postprocessing step to calculate liveness for checkpoints + // Not found in typical liveness analysis + CheckpointAnalysis::postprocess_gen_kill(body, &bb_data, &mut gen_kill[bb_idx]); } GenKillAnalysis { gen_kill } } - } impl Deref for LivenessAnalysis { From f5f4a35c41c3c2d5f74f2f8abaafbcdbacf02699 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:26:34 -0400 Subject: [PATCH 34/40] Inject call to empty checkpoint runtime library function --- .../src/rad_protected_analysis.rs | 42 +++++++++++++++++++ compiler/rustc_span/src/symbol.rs | 1 + library/rad_protected/src/lib.rs | 1 + library/rad_protected/src/runtime.rs | 10 +++++ 4 files changed, 54 insertions(+) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index f3d7d7702636f..d6442ba160b1d 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -4,10 +4,12 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir::find_attr; use rustc_middle::mir::{ Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, + BasicBlock, BasicBlockData, Terminator, SourceInfo, LocalDecl, UnwindAction, CallSource, }; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, TyCtxt}; use super::rad_protected_liveness_analysis::CheckpointAnalysis; +use rustc_span::sym; pub(super) struct RadProtectedAnalysis; @@ -33,6 +35,12 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { eprintln!("\tSync: {:?}\n", sorted_locals(&live.out())); } eprintln!("================================"); + + eprintln!("=== Injecting checkpoints ==="); + let bb = inject_checkpoint_call(tcx, body, BasicBlock::from_usize(0)); + let terminator = body.basic_blocks[bb].terminator(); + assert!(matches!(terminator.kind, TerminatorKind::Call { .. })); + eprintln!("================================"); let sources = build_pointer_sources(body); @@ -442,3 +450,37 @@ fn sorted_locals(set: &FxHashSet) -> Vec { locals.sort(); locals } + +fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, after: BasicBlock) -> BasicBlock { + let checkpoint_def_id = tcx.get_diagnostic_item(sym::checkpoint).unwrap(); + let source_info = SourceInfo::outermost(body.span); + + let func = Operand::function_handle( + tcx, + checkpoint_def_id, + [], + body.span, + ); + + let dest_local = body.local_decls.push(LocalDecl::new(tcx.types.unit, body.span)); + + let old_data = body.basic_blocks_mut()[after].clone(); + let continuation = body.basic_blocks_mut().push(old_data); + + let terminator = Terminator { + source_info, + kind: TerminatorKind::Call { + func, + args: Box::new([]), + destination: Place::from(dest_local), + target: Some(continuation), + unwind: UnwindAction::Continue, + call_source: CallSource::Misc, + fn_span: body.span, + }, + }; + + let new_block = BasicBlockData::new(Some(terminator), false); + body.basic_blocks_mut()[after] = new_block; + after +} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a994234ffaf74..871dc59ed8fe2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -613,6 +613,7 @@ symbols! { cfi, cfi_encoding, char, + checkpoint, client, clippy, clobber_abi, diff --git a/library/rad_protected/src/lib.rs b/library/rad_protected/src/lib.rs index 029d7279c239c..23c90f2cfff65 100644 --- a/library/rad_protected/src/lib.rs +++ b/library/rad_protected/src/lib.rs @@ -7,6 +7,7 @@ //! //! For porting this library to a new target, see mini_std/mod.rs +#![feature(rustc_attrs)] #![feature(staged_api)] #![allow(internal_features)] #![no_std] diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index cdf79c9a28a58..3204be645ca00 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -91,6 +91,16 @@ impl Runtime { } guard.take(); } + + #[stable(feature = "rad_protected", since = "1.95.0")] + #[rustc_diagnostic_item = "checkpoint"] + pub fn checkpoint() { + let fd: libc::c_int = 1; + let message = b"Hello, World!\n"; + let buf = message.as_ptr() as *const libc::c_void; + let count = message.len() as libc::size_t; + unsafe { libc::write(fd, buf, count); } + } } /// Guard to properly drop processes when done with the rad_protected function From ceaa8fc29c4446bb020fd8bddac2966ec21ba72a Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:24:05 -0400 Subject: [PATCH 35/40] Simplify CheckpointAnalysis API --- .../src/rad_protected_analysis.rs | 12 +---- .../src/rad_protected_liveness_analysis.rs | 49 ++++++++++--------- library/rad_protected/src/runtime.rs | 1 + 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index d6442ba160b1d..ec06b81d0eb22 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -32,7 +32,7 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { eprintln!("=== Liveness analysis for {:?} ===", def_id); for (bb_idx, live) in checkpoint_analysis.checkpoints { eprintln!("Checkpoint {:?}", bb_idx); - eprintln!("\tSync: {:?}\n", sorted_locals(&live.out())); + eprintln!("\tSync: {:?}\n", live.locals()); } eprintln!("================================"); @@ -441,16 +441,6 @@ fn resolve_pointer_source<'tcx>( } } -fn sorted_locals(set: &FxHashSet) -> Vec { - // Values are sorted after becoming an iter - // Additionally, this method is only used to print debugging info - #[allow(rustc::potential_query_instability)] - let mut locals: Vec = set.iter().copied().collect(); - - locals.sort(); - locals -} - fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, after: BasicBlock) -> BasicBlock { let checkpoint_def_id = tcx.get_diagnostic_item(sym::checkpoint).unwrap(); let source_info = SourceInfo::outermost(body.span); diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs index 25ed5c90e7494..f7b07f81b5e5e 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -2,13 +2,12 @@ use rustc_middle::mir::{BasicBlock, Location, visit::{PlaceContext, Visitor}}; use rustc_middle::mir::{ Body, BasicBlocks, Local, Place, Rvalue, TerminatorKind, BasicBlockData }; -use std::ops::{Deref, DerefMut}; use std::collections::VecDeque; use rustc_data_structures::{fx::FxHashSet, graph::Successors}; use rustc_index::{bit_set::DenseBitSet, IndexVec}; pub(super) struct CheckpointAnalysis { - pub checkpoints: Vec<(BasicBlock, Liveness)>, + pub checkpoints: Vec<(BasicBlock, LiveLocals)>, } impl CheckpointAnalysis { @@ -19,6 +18,7 @@ impl CheckpointAnalysis { .liveness .into_iter_enumerated() .filter(|(bb_idx, _)| Self::is_checkpoint(&body.basic_blocks[*bb_idx])) + .map(|(bb_idx, liveness)| (bb_idx, LiveLocals::new(liveness.out))) .collect() } } @@ -36,6 +36,30 @@ impl CheckpointAnalysis { } } +pub(super) struct LiveLocals { + locals: Vec, +} + +impl LiveLocals { + fn new(locals: FxHashSet) -> Self { + Self { + locals: Self::sort_locals(locals) + } + } + + fn sort_locals(set: FxHashSet) -> Vec { + // Values are sorted after being collected into a Vec + #[allow(rustc::potential_query_instability)] + let mut locals: Vec = set.into_iter().collect(); + + locals.sort(); + locals + } + + pub(super) fn locals(&self) -> &Vec { + &self.locals + } +} struct LivenessAnalysis { liveness: IndexVec, @@ -124,20 +148,6 @@ impl LivenessAnalysis { } } -impl Deref for LivenessAnalysis { - type Target = IndexVec; - - fn deref(&self) -> &Self::Target { - &self.liveness - } -} - -impl DerefMut for LivenessAnalysis { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.liveness - } -} - pub(super) struct Liveness { _in: FxHashSet, out: FxHashSet, @@ -150,13 +160,6 @@ impl Liveness { out: FxHashSet::default(), } } - - pub(super) fn _in(&self) -> &FxHashSet { - &self._in - } - pub(super) fn out(&self) -> &FxHashSet { - &self.out - } } struct GenKillAnalysis { diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 3204be645ca00..676dd87101cfc 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -92,6 +92,7 @@ impl Runtime { guard.take(); } + // Checkpoint given locals via a majority vote over the triplicated threads #[stable(feature = "rad_protected", since = "1.95.0")] #[rustc_diagnostic_item = "checkpoint"] pub fn checkpoint() { From 6b407f58f8db4e2ab8596398cabb5acf4d0a2b77 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:55:04 -0400 Subject: [PATCH 36/40] Generate MIR to build args for checkpoint runtime call --- .../src/rad_protected_analysis.rs | 144 +++++++++++++++--- library/rad_protected/src/runtime.rs | 28 +++- 2 files changed, 145 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index ec06b81d0eb22..1761ad3b0bdae 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -1,15 +1,18 @@ //! This pass performs an analysis to determine reference, raw pointer, and unsafe function call accesses that are protected by `#[rad_protected]` use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; -use rustc_hir::find_attr; +use rustc_hir::{find_attr, Mutability, LangItem}; use rustc_middle::mir::{ Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, BasicBlock, BasicBlockData, Terminator, SourceInfo, LocalDecl, UnwindAction, CallSource, + Statement, CastKind, AggregateKind, ProjectionElem, ConstOperand, Const, ConstValue, interpret::Scalar, + RawPtrKind }; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, TyCtxt}; -use super::rad_protected_liveness_analysis::CheckpointAnalysis; -use rustc_span::sym; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use super::rad_protected_liveness_analysis::{CheckpointAnalysis, LiveLocals}; +use rustc_span::{sym, source_map::Spanned}; +use rustc_index::IndexVec; pub(super) struct RadProtectedAnalysis; @@ -30,16 +33,17 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { let checkpoint_analysis = CheckpointAnalysis::analyze(body); eprintln!("=== Liveness analysis for {:?} ===", def_id); - for (bb_idx, live) in checkpoint_analysis.checkpoints { + for (bb_idx, live) in &checkpoint_analysis.checkpoints { eprintln!("Checkpoint {:?}", bb_idx); eprintln!("\tSync: {:?}\n", live.locals()); } eprintln!("================================"); eprintln!("=== Injecting checkpoints ==="); - let bb = inject_checkpoint_call(tcx, body, BasicBlock::from_usize(0)); - let terminator = body.basic_blocks[bb].terminator(); - assert!(matches!(terminator.kind, TerminatorKind::Call { .. })); + for (bb_idx, live) in checkpoint_analysis.checkpoints { + inject_checkpoint_call(tcx, body, live, bb_idx); + eprintln!("Successfully injected checkpoint call at {:?}", bb_idx); + } eprintln!("================================"); let sources = build_pointer_sources(body); @@ -441,36 +445,134 @@ fn resolve_pointer_source<'tcx>( } } -fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, after: BasicBlock) -> BasicBlock { +fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: LiveLocals, next: BasicBlock) -> BasicBlock { let checkpoint_def_id = tcx.get_diagnostic_item(sym::checkpoint).unwrap(); let source_info = SourceInfo::outermost(body.span); + let span = body.span; + let num_locals = live.locals().len() as u64; + + let mut stmts: Vec> = Vec::new(); + let mut push_stmt = |kind: StatementKind<'tcx>| { + stmts.push(Statement::new(source_info, kind)); + }; + + let push_local = |body: &mut Body<'tcx>, ty| { + body.local_decls.push(LocalDecl::new(ty, span)) + }; - let func = Operand::function_handle( - tcx, - checkpoint_def_id, - [], - body.span, + let mut push_assign = |place, rvalue| { + push_stmt(StatementKind::Assign(Box::new((place, rvalue)))); + }; + + let u8_ptr_ty = Ty::new_ptr(tcx, tcx.types.u8, Mutability::Mut); + let slot_ty = Ty::new_tup(tcx, &[u8_ptr_ty, tcx.types.usize]); + + // let array: [(*mut u8, usize); usize]; + let array_ty = Ty::new_array(tcx, slot_ty, num_locals); + let array_local = push_local(body, array_ty); + + let size_of_def_id = tcx.require_lang_item(LangItem::SizeOf, span); + + for (i, &local) in live.locals().iter().enumerate() { + let local_ty = body.local_decls[local].ty; + + // _1 = &raw mut local; + let raw_ptr_ty = Ty::new_ptr(tcx, local_ty, Mutability::Mut); + let raw_ptr = push_local(body, raw_ptr_ty); + push_assign( + Place::from(raw_ptr), + Rvalue::RawPtr(RawPtrKind::Mut, Place::from(local)), + ); + + // _2 = _1 as *mut u8; + let u8_ptr = push_local(body, u8_ptr_ty); + push_assign( + Place::from(u8_ptr), + Rvalue::Cast( + CastKind::PtrToPtr, + Operand::Move(Place::from(raw_ptr)), + u8_ptr_ty + ), + ); + + // size_of::() + let size_operand = + Operand::unevaluated_constant(tcx, size_of_def_id, &[local_ty.into()], span); + + // _3 = (_2, size_of::()); + let slot_local = push_local(body, slot_ty); + push_assign( + Place::from(slot_local), + Rvalue::Aggregate( + Box::new(AggregateKind::Tuple), + IndexVec::from_raw(vec![Operand::Move(Place::from(u8_ptr)), size_operand]), + ), + ); + + // array[i] = move _3; + let elem_place = Place::from(array_local).project_deeper( + &[ProjectionElem::ConstantIndex { + offset: i as u64, + min_length: num_locals, + from_end: false + }], + tcx, + ); + push_assign( + elem_place, + Rvalue::Use(Operand::Move(Place::from(slot_local))), + ); + } + + // _1 = &raw const array; + let array_ptr_ty = Ty::new_ptr(tcx, array_ty, Mutability::Not); + let array_ptr = push_local(body, array_ptr_ty); + push_assign( + Place::from(array_ptr), + Rvalue::RawPtr(RawPtrKind::Const, Place::from(array_local)), ); - let dest_local = body.local_decls.push(LocalDecl::new(tcx.types.unit, body.span)); + // _2 = _1 as *const (*mut u8, usize) + let slot_ptr_ty = Ty::new_ptr(tcx, slot_ty, Mutability::Not); + let slot_ptr = push_local(body, slot_ptr_ty); + push_assign( + Place::from(slot_ptr), + Rvalue::Cast(CastKind::PtrToPtr, Operand::Move(Place::from(array_ptr)), slot_ptr_ty), + ); + + // num_locals + let count_operand = Operand::Constant(Box::new(ConstOperand { + span, + user_ty: None, + const_: Const::Val( + ConstValue::Scalar(Scalar::from_target_usize(num_locals, &tcx)), + tcx.types.usize, + ), + })); + + let func = Operand::function_handle(tcx, checkpoint_def_id, [], span); + let dest_local = push_local(body, tcx.types.unit); - let old_data = body.basic_blocks_mut()[after].clone(); + let old_data = body.basic_blocks_mut()[next].clone(); let continuation = body.basic_blocks_mut().push(old_data); let terminator = Terminator { source_info, kind: TerminatorKind::Call { func, - args: Box::new([]), + args: Box::new([ + Spanned { node: Operand::Move(Place::from(slot_ptr)), span }, + Spanned { node: count_operand, span }, + ]), destination: Place::from(dest_local), target: Some(continuation), unwind: UnwindAction::Continue, call_source: CallSource::Misc, - fn_span: body.span, + fn_span: span, }, }; - let new_block = BasicBlockData::new(Some(terminator), false); - body.basic_blocks_mut()[after] = new_block; - after + let new_block = BasicBlockData::new_stmts(stmts, Some(terminator), false); + body.basic_blocks_mut()[next] = new_block; + next } diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 676dd87101cfc..6eee3cdf1fe3e 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -95,12 +95,28 @@ impl Runtime { // Checkpoint given locals via a majority vote over the triplicated threads #[stable(feature = "rad_protected", since = "1.95.0")] #[rustc_diagnostic_item = "checkpoint"] - pub fn checkpoint() { - let fd: libc::c_int = 1; - let message = b"Hello, World!\n"; - let buf = message.as_ptr() as *const libc::c_void; - let count = message.len() as libc::size_t; - unsafe { libc::write(fd, buf, count); } + pub fn checkpoint(locals: *const (*mut u8, usize), count: usize) { + if locals.is_null() { + return; + } + + let hex_fmt = b"%02x \0".as_ptr() as *const libc::c_char; + let nl_fmt = b"\n\0".as_ptr() as *const libc::c_char; + + for i in 0..count { + let pair_ptr = unsafe { locals.add(i) }; + let (buf_ptr, buf_len) = unsafe { *pair_ptr }; + + if buf_ptr.is_null() || buf_len == 0 { + continue; + } + + for j in 0..buf_len { + let byte = unsafe { *buf_ptr.add(j) }; + unsafe { libc::printf(hex_fmt, byte as libc::c_int); } + } + unsafe { libc::printf(nl_fmt); } + } } } From 6536d1e7545f5ef689a89210cf436da498e123db Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:46:53 -0400 Subject: [PATCH 37/40] Improve checkpoint runtime fn signature and update MIR generation --- .../src/rad_protected_analysis.rs | 47 +++++++++---------- library/rad_protected/src/runtime.rs | 9 ++-- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index 1761ad3b0bdae..5b4332552fee7 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -5,9 +5,9 @@ use rustc_hir::{find_attr, Mutability, LangItem}; use rustc_middle::mir::{ Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, BasicBlock, BasicBlockData, Terminator, SourceInfo, LocalDecl, UnwindAction, CallSource, - Statement, CastKind, AggregateKind, ProjectionElem, ConstOperand, Const, ConstValue, interpret::Scalar, - RawPtrKind + Statement, CastKind, AggregateKind, ProjectionElem, RawPtrKind, CoercionSource, BorrowKind, }; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Ty, TyCtxt}; use super::rad_protected_liveness_analysis::{CheckpointAnalysis, LiveLocals}; @@ -484,7 +484,7 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: Rvalue::RawPtr(RawPtrKind::Mut, Place::from(local)), ); - // _2 = _1 as *mut u8; + // _2 = _1 as *mut u8 (PtrToPtr); let u8_ptr = push_local(body, u8_ptr_ty); push_assign( Place::from(u8_ptr), @@ -524,31 +524,29 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: ); } - // _1 = &raw const array; - let array_ptr_ty = Ty::new_ptr(tcx, array_ty, Mutability::Not); - let array_ptr = push_local(body, array_ptr_ty); + // _1 = &array; + let array_ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, array_ty); + let array_ref = push_local(body, array_ref_ty); push_assign( - Place::from(array_ptr), - Rvalue::RawPtr(RawPtrKind::Const, Place::from(array_local)), + Place::from(array_ref), + Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, Place::from(array_local)), ); - // _2 = _1 as *const (*mut u8, usize) - let slot_ptr_ty = Ty::new_ptr(tcx, slot_ty, Mutability::Not); - let slot_ptr = push_local(body, slot_ptr_ty); + // _2 = move _1 as &[(*mut u8, usize)] (PointerCoercion(Unsize, Implicit)); + let slice_ty = Ty::new_slice(tcx, slot_ty); + let slice_ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, slice_ty); + let slice_ref = push_local(body, slice_ref_ty); push_assign( - Place::from(slot_ptr), - Rvalue::Cast(CastKind::PtrToPtr, Operand::Move(Place::from(array_ptr)), slot_ptr_ty), - ); - - // num_locals - let count_operand = Operand::Constant(Box::new(ConstOperand { - span, - user_ty: None, - const_: Const::Val( - ConstValue::Scalar(Scalar::from_target_usize(num_locals, &tcx)), - tcx.types.usize, + Place::from(slice_ref), + Rvalue::Cast( + CastKind::PointerCoercion( + PointerCoercion::Unsize, + CoercionSource::Implicit + ), + Operand::Move(Place::from(array_ref)), + slice_ref_ty, ), - })); + ); let func = Operand::function_handle(tcx, checkpoint_def_id, [], span); let dest_local = push_local(body, tcx.types.unit); @@ -561,8 +559,7 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: kind: TerminatorKind::Call { func, args: Box::new([ - Spanned { node: Operand::Move(Place::from(slot_ptr)), span }, - Spanned { node: count_operand, span }, + Spanned { node: Operand::Move(Place::from(slice_ref)), span }, ]), destination: Place::from(dest_local), target: Some(continuation), diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 6eee3cdf1fe3e..113591cc5da85 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -95,17 +95,16 @@ impl Runtime { // Checkpoint given locals via a majority vote over the triplicated threads #[stable(feature = "rad_protected", since = "1.95.0")] #[rustc_diagnostic_item = "checkpoint"] - pub fn checkpoint(locals: *const (*mut u8, usize), count: usize) { - if locals.is_null() { + pub fn checkpoint(locals: &[(*mut u8, usize)]) { + if locals.is_empty() { return; } let hex_fmt = b"%02x \0".as_ptr() as *const libc::c_char; let nl_fmt = b"\n\0".as_ptr() as *const libc::c_char; - for i in 0..count { - let pair_ptr = unsafe { locals.add(i) }; - let (buf_ptr, buf_len) = unsafe { *pair_ptr }; + for i in 0..locals.len() { + let (buf_ptr, buf_len) = locals[i]; if buf_ptr.is_null() || buf_len == 0 { continue; From 8fdb2b7153c1a8e98c20cd2be52b64b1814f997d Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:55:05 -0400 Subject: [PATCH 38/40] Fix bug with checkpoint injection causing use before init and false checkpointing of call expr temporaries --- compiler/rustc_expand/src/lib.rs | 1 + .../rustc_expand/src/patch_checkpoints.rs | 100 ++++++++++++++++++ compiler/rustc_interface/src/passes.rs | 2 + .../src/rad_protected_analysis.rs | 45 ++++---- .../src/rad_protected_liveness_analysis.rs | 34 ++++-- compiler/rustc_span/src/symbol.rs | 1 + library/rad_protected/src/runtime.rs | 8 ++ 7 files changed, 155 insertions(+), 36 deletions(-) create mode 100644 compiler/rustc_expand/src/patch_checkpoints.rs diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 4658ff3012acd..176f1a7a283b9 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -24,6 +24,7 @@ pub mod module; pub mod proc_macro; pub mod patch_unsafe; +pub mod patch_checkpoints; pub fn provide(providers: &mut rustc_middle::query::Providers) { providers.derive_macro_expansion = proc_macro::provide_derive_macro_expansion; diff --git a/compiler/rustc_expand/src/patch_checkpoints.rs b/compiler/rustc_expand/src/patch_checkpoints.rs new file mode 100644 index 0000000000000..feb67a3aa16a6 --- /dev/null +++ b/compiler/rustc_expand/src/patch_checkpoints.rs @@ -0,0 +1,100 @@ +use rustc_ast as ast; +use rustc_ast::mut_visit::{self, MutVisitor}; +use crate::base::ExtCtxt; +use rustc_span::{symbol::Ident, sym, DUMMY_SP}; +use thin_vec::thin_vec; +use rustc_ast::visit::AssocCtxt; + +pub fn patch_checkpoints(cx: &mut ExtCtxt<'_>, krate: &mut ast::Crate) { + if cx.sess.opts.unstable_opts.force_unstable_if_unmarked { + return; + } + let mut visitor = CheckpointRewriter { cx, active: false }; + visitor.visit_crate(krate); +} + +struct DummyIdAssigner<'a, 'cx> { + cx: &'a mut ExtCtxt<'cx>, +} + +impl MutVisitor for DummyIdAssigner<'_, '_> { + fn visit_id(&mut self, id: &mut ast::NodeId) { + if *id == ast::DUMMY_NODE_ID { + *id = self.cx.resolver.next_node_id(); + } + } +} + +struct CheckpointRewriter<'a, 'cx> { + cx: &'a mut ExtCtxt<'cx>, + active: bool, +} + +impl MutVisitor for CheckpointRewriter<'_, '_> { + fn visit_item(&mut self, item: &mut ast::Item) { + let is_fn = matches!(item.kind, ast::ItemKind::Fn(_)); + let prev = self.active; + + if is_fn { + self.active = has_rad_protected_mir(&item.attrs); + } + + mut_visit::walk_item(self, item); + + self.active = prev; + } + + fn visit_assoc_item(&mut self, item: &mut ast::AssocItem, ctxt: AssocCtxt) { + let is_fn = matches!(item.kind, ast::AssocItemKind::Fn(_)); + let prev = self.active; + + if is_fn { + self.active = has_rad_protected_mir(&item.attrs); + } + + mut_visit::walk_assoc_item(self, item, ctxt); + + self.active = prev; + } + + fn visit_expr(&mut self, expr: &mut ast::Expr) { + mut_visit::walk_expr(self, expr); + + if self.active && matches!(expr.kind, ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..)) { + wrap_call_with_checkpoint(self.cx, expr); + } + } +} + +fn has_rad_protected_mir(attrs: &[ast::Attribute]) -> bool { + attrs.iter().any(|attr| attr.has_name(sym::rad_protected_mir)) +} + +fn wrap_call_with_checkpoint(cx: &mut ExtCtxt<'_>, expr: &mut ast::Expr) { + let span = expr.span; + + let checkpoint_call = cx.expr_call_global( + DUMMY_SP, + vec![ + Ident::new(sym::std, DUMMY_SP), + Ident::new(sym::RadRustRuntime, DUMMY_SP), + Ident::new(sym::__checkpoint, DUMMY_SP), + ], + thin_vec![], + ); + + let original = std::mem::replace(expr, *cx.expr_bool(DUMMY_SP, false)); + + let checkpoint_stmt = cx.stmt_expr(checkpoint_call); + let tail_stmt = cx.stmt_expr(Box::new(original)); + + let mut block = cx.block(span, thin_vec![checkpoint_stmt, tail_stmt]); + + let mut assigner = DummyIdAssigner { cx }; + assigner.visit_block(&mut block); + + expr.id = ast::DUMMY_NODE_ID; + expr.span = span; + expr.kind = ast::ExprKind::Block(block, None); + assigner.visit_id(&mut expr.id); +} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 06dc7b61f959c..fbc9d860100ad 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -16,6 +16,7 @@ use rustc_data_structures::thousands; use rustc_errors::timings::TimingSection; use rustc_expand::base::{ExtCtxt, LintStoreExpand}; use rustc_expand::patch_unsafe::patch_unsafe_blocks; +use rustc_expand::patch_checkpoints::patch_checkpoints; use rustc_feature::Features; use rustc_fs_util::try_canonicalize; use rustc_hir::attrs::AttributeKind; @@ -221,6 +222,7 @@ fn configure_and_expand( } patch_unsafe_blocks(&mut ecx, &mut krate); + patch_checkpoints(&mut ecx, &mut krate); // The rest is error reporting and stats diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index 5b4332552fee7..74279b6bf39a5 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -4,8 +4,8 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir::{find_attr, Mutability, LangItem}; use rustc_middle::mir::{ Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, - BasicBlock, BasicBlockData, Terminator, SourceInfo, LocalDecl, UnwindAction, CallSource, - Statement, CastKind, AggregateKind, ProjectionElem, RawPtrKind, CoercionSource, BorrowKind, + BasicBlock, SourceInfo, LocalDecl, Statement, CastKind, AggregateKind, ProjectionElem, + RawPtrKind, CoercionSource, BorrowKind, }; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::print::with_no_trimmed_paths; @@ -30,7 +30,7 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { return; } - let checkpoint_analysis = CheckpointAnalysis::analyze(body); + let checkpoint_analysis = CheckpointAnalysis::analyze(tcx, body); eprintln!("=== Liveness analysis for {:?} ===", def_id); for (bb_idx, live) in &checkpoint_analysis.checkpoints { @@ -511,7 +511,7 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: // array[i] = move _3; let elem_place = Place::from(array_local).project_deeper( - &[ProjectionElem::ConstantIndex { + &[ProjectionElem::ConstantIndex { offset: i as u64, min_length: num_locals, from_end: false @@ -549,27 +549,22 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: ); let func = Operand::function_handle(tcx, checkpoint_def_id, [], span); - let dest_local = push_local(body, tcx.types.unit); - - let old_data = body.basic_blocks_mut()[next].clone(); - let continuation = body.basic_blocks_mut().push(old_data); - - let terminator = Terminator { - source_info, - kind: TerminatorKind::Call { - func, - args: Box::new([ - Spanned { node: Operand::Move(Place::from(slice_ref)), span }, - ]), - destination: Place::from(dest_local), - target: Some(continuation), - unwind: UnwindAction::Continue, - call_source: CallSource::Misc, - fn_span: span, - }, - }; + let block_data = &mut body.basic_blocks_mut()[next]; + block_data.statements.extend(stmts); + + match &mut block_data.terminator_mut().kind { + TerminatorKind::Call { func: callee, args, fn_span, .. } => { + *callee = func; + *args = Box::new([Spanned { node: Operand::Move(Place::from(slice_ref)), span }]); + *fn_span = span; + } + TerminatorKind::TailCall { func: callee, args, fn_span, .. } => { + *callee = func; + *args = Box::new([Spanned { node: Operand::Move(Place::from(slice_ref)), span }]); + *fn_span = span; + } + _ => unreachable!(), + } - let new_block = BasicBlockData::new_stmts(stmts, Some(terminator), false); - body.basic_blocks_mut()[next] = new_block; next } diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs index f7b07f81b5e5e..d48324cc4fc6c 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -5,34 +5,46 @@ use rustc_middle::mir::{ use std::collections::VecDeque; use rustc_data_structures::{fx::FxHashSet, graph::Successors}; use rustc_index::{bit_set::DenseBitSet, IndexVec}; +use rustc_span::def_id::DefId; +use rustc_span::sym; +use rustc_middle::ty::TyCtxt; pub(super) struct CheckpointAnalysis { pub checkpoints: Vec<(BasicBlock, LiveLocals)>, } impl CheckpointAnalysis { - pub(super) fn analyze<'tcx>(body: &Body<'tcx>) -> Self { + pub(super) fn analyze<'tcx>(tcx: TyCtxt<'_>, body: &Body<'tcx>) -> Self { Self { checkpoints: - LivenessAnalysis::analyze(body) + LivenessAnalysis::analyze(tcx, body) .liveness .into_iter_enumerated() - .filter(|(bb_idx, _)| Self::is_checkpoint(&body.basic_blocks[*bb_idx])) + .filter(|(bb_idx, _)| Self::is_checkpoint(tcx, &body.basic_blocks[*bb_idx])) .map(|(bb_idx, liveness)| (bb_idx, LiveLocals::new(liveness.out))) .collect() } } - fn postprocess_gen_kill<'tcx>(body: &Body<'tcx>, bb_data: &BasicBlockData<'_>, gen_kill: &mut GenKill) { - if Self::is_checkpoint(&bb_data) { + fn postprocess_gen_kill<'tcx>(tcx: TyCtxt<'_>, body: &Body<'tcx>, bb_data: &BasicBlockData<'_>, gen_kill: &mut GenKill) { + if Self::is_checkpoint(tcx, &bb_data) { // Order independent since target is unordered (HashSet) #[allow(rustc::potential_query_instability)] gen_kill.kill.extend(body.local_decls.indices()); } } - pub(super) fn is_checkpoint(bb_data: &BasicBlockData<'_>) -> bool { - matches!(bb_data.terminator().kind, TerminatorKind::Call{..} | TerminatorKind::TailCall{..}) + pub(super) fn is_checkpoint(tcx: TyCtxt<'_>, bb_data: &BasicBlockData<'_>) -> bool { + Self::callee_def_id(bb_data).is_some_and(|def_id| tcx.is_diagnostic_item(sym::__checkpoint, def_id)) + } + + fn callee_def_id<'tcx>(bb_data: &BasicBlockData<'tcx>) -> Option { + let func = match &bb_data.terminator().kind { + TerminatorKind::Call { func, .. } => func, + TerminatorKind::TailCall { func, .. } => func, + _ => return None, + }; + func.const_fn_def().map(|(def_id, _)| def_id) } } @@ -67,8 +79,8 @@ struct LivenessAnalysis { // Liveness Analysis Pass (based on: https://en.wikipedia.org/wiki/Live-variable_analysis) impl LivenessAnalysis { - fn analyze<'tcx>(body: &Body<'tcx>) -> Self { - let gk_analysis = Self::generate_gk_analysis(&body); + fn analyze<'tcx>(tcx: TyCtxt<'_>, body: &Body<'tcx>) -> Self { + let gk_analysis = Self::generate_gk_analysis(tcx, &body); Self::calculate_liveness(gk_analysis, &body.basic_blocks) } @@ -112,7 +124,7 @@ impl LivenessAnalysis { Self { liveness } } - fn generate_gk_analysis<'tcx>(body: &Body<'tcx>) -> GenKillAnalysis { + fn generate_gk_analysis<'tcx>(tcx: TyCtxt<'_>, body: &Body<'tcx>) -> GenKillAnalysis { let mut gen_kill = IndexVec::::from_fn_n( |_| GenKill::new(), body.basic_blocks.len() @@ -141,7 +153,7 @@ impl LivenessAnalysis { // Special GenKill postprocessing step to calculate liveness for checkpoints // Not found in typical liveness analysis - CheckpointAnalysis::postprocess_gen_kill(body, &bb_data, &mut gen_kill[bb_idx]); + CheckpointAnalysis::postprocess_gen_kill(tcx, body, &bb_data, &mut gen_kill[bb_idx]); } GenKillAnalysis { gen_kill } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 871dc59ed8fe2..d50595092cc45 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -348,6 +348,7 @@ symbols! { Vec, Wrapper, _DECLS, + __checkpoint, __guard, __H, __S, diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 113591cc5da85..65ac50a0d5cbe 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -117,6 +117,14 @@ impl Runtime { unsafe { libc::printf(nl_fmt); } } } + + + // Internal checkpoint marker inserted by AST + // Indicates the MIR pass should rewrite the terminator to a `checkpoint` call + #[stable(feature = "rad_protected", since = "1.95.0")] + #[rustc_diagnostic_item = "__checkpoint"] + pub fn __checkpoint() { + } } /// Guard to properly drop processes when done with the rad_protected function From 79ba924e10e4bd9b503c51e31bf6df3eb7f7ef64 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:30:28 -0400 Subject: [PATCH 39/40] Move checkpoint marker injection to MIR building rather than AST --- compiler/rustc_expand/src/lib.rs | 1 - .../rustc_expand/src/patch_checkpoints.rs | 100 ------------------ compiler/rustc_interface/src/passes.rs | 2 - .../rustc_mir_build/src/builder/checkpoint.rs | 44 ++++++++ .../rustc_mir_build/src/builder/expr/into.rs | 4 + compiler/rustc_mir_build/src/builder/mod.rs | 1 + library/rad_protected/src/runtime.rs | 2 +- 7 files changed, 50 insertions(+), 104 deletions(-) delete mode 100644 compiler/rustc_expand/src/patch_checkpoints.rs create mode 100644 compiler/rustc_mir_build/src/builder/checkpoint.rs diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 176f1a7a283b9..4658ff3012acd 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -24,7 +24,6 @@ pub mod module; pub mod proc_macro; pub mod patch_unsafe; -pub mod patch_checkpoints; pub fn provide(providers: &mut rustc_middle::query::Providers) { providers.derive_macro_expansion = proc_macro::provide_derive_macro_expansion; diff --git a/compiler/rustc_expand/src/patch_checkpoints.rs b/compiler/rustc_expand/src/patch_checkpoints.rs deleted file mode 100644 index feb67a3aa16a6..0000000000000 --- a/compiler/rustc_expand/src/patch_checkpoints.rs +++ /dev/null @@ -1,100 +0,0 @@ -use rustc_ast as ast; -use rustc_ast::mut_visit::{self, MutVisitor}; -use crate::base::ExtCtxt; -use rustc_span::{symbol::Ident, sym, DUMMY_SP}; -use thin_vec::thin_vec; -use rustc_ast::visit::AssocCtxt; - -pub fn patch_checkpoints(cx: &mut ExtCtxt<'_>, krate: &mut ast::Crate) { - if cx.sess.opts.unstable_opts.force_unstable_if_unmarked { - return; - } - let mut visitor = CheckpointRewriter { cx, active: false }; - visitor.visit_crate(krate); -} - -struct DummyIdAssigner<'a, 'cx> { - cx: &'a mut ExtCtxt<'cx>, -} - -impl MutVisitor for DummyIdAssigner<'_, '_> { - fn visit_id(&mut self, id: &mut ast::NodeId) { - if *id == ast::DUMMY_NODE_ID { - *id = self.cx.resolver.next_node_id(); - } - } -} - -struct CheckpointRewriter<'a, 'cx> { - cx: &'a mut ExtCtxt<'cx>, - active: bool, -} - -impl MutVisitor for CheckpointRewriter<'_, '_> { - fn visit_item(&mut self, item: &mut ast::Item) { - let is_fn = matches!(item.kind, ast::ItemKind::Fn(_)); - let prev = self.active; - - if is_fn { - self.active = has_rad_protected_mir(&item.attrs); - } - - mut_visit::walk_item(self, item); - - self.active = prev; - } - - fn visit_assoc_item(&mut self, item: &mut ast::AssocItem, ctxt: AssocCtxt) { - let is_fn = matches!(item.kind, ast::AssocItemKind::Fn(_)); - let prev = self.active; - - if is_fn { - self.active = has_rad_protected_mir(&item.attrs); - } - - mut_visit::walk_assoc_item(self, item, ctxt); - - self.active = prev; - } - - fn visit_expr(&mut self, expr: &mut ast::Expr) { - mut_visit::walk_expr(self, expr); - - if self.active && matches!(expr.kind, ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..)) { - wrap_call_with_checkpoint(self.cx, expr); - } - } -} - -fn has_rad_protected_mir(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| attr.has_name(sym::rad_protected_mir)) -} - -fn wrap_call_with_checkpoint(cx: &mut ExtCtxt<'_>, expr: &mut ast::Expr) { - let span = expr.span; - - let checkpoint_call = cx.expr_call_global( - DUMMY_SP, - vec![ - Ident::new(sym::std, DUMMY_SP), - Ident::new(sym::RadRustRuntime, DUMMY_SP), - Ident::new(sym::__checkpoint, DUMMY_SP), - ], - thin_vec![], - ); - - let original = std::mem::replace(expr, *cx.expr_bool(DUMMY_SP, false)); - - let checkpoint_stmt = cx.stmt_expr(checkpoint_call); - let tail_stmt = cx.stmt_expr(Box::new(original)); - - let mut block = cx.block(span, thin_vec![checkpoint_stmt, tail_stmt]); - - let mut assigner = DummyIdAssigner { cx }; - assigner.visit_block(&mut block); - - expr.id = ast::DUMMY_NODE_ID; - expr.span = span; - expr.kind = ast::ExprKind::Block(block, None); - assigner.visit_id(&mut expr.id); -} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index fbc9d860100ad..06dc7b61f959c 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -16,7 +16,6 @@ use rustc_data_structures::thousands; use rustc_errors::timings::TimingSection; use rustc_expand::base::{ExtCtxt, LintStoreExpand}; use rustc_expand::patch_unsafe::patch_unsafe_blocks; -use rustc_expand::patch_checkpoints::patch_checkpoints; use rustc_feature::Features; use rustc_fs_util::try_canonicalize; use rustc_hir::attrs::AttributeKind; @@ -222,7 +221,6 @@ fn configure_and_expand( } patch_unsafe_blocks(&mut ecx, &mut krate); - patch_checkpoints(&mut ecx, &mut krate); // The rest is error reporting and stats diff --git a/compiler/rustc_mir_build/src/builder/checkpoint.rs b/compiler/rustc_mir_build/src/builder/checkpoint.rs new file mode 100644 index 0000000000000..744ddcedaf06c --- /dev/null +++ b/compiler/rustc_mir_build/src/builder/checkpoint.rs @@ -0,0 +1,44 @@ +use rustc_hir::def_id::LocalDefId; +use rustc_middle::mir::{TerminatorKind, Operand, Place, UnwindAction, CallSource, BasicBlock}; +use rustc_middle::ty::TyCtxt; +use rustc_span::{sym, Span}; +use rustc_hir::find_attr; + +use super::Builder; + +impl<'a, 'tcx> Builder<'a, 'tcx> { + pub(super) fn inject_checkpoint_marker(&mut self, block: BasicBlock, span: Span) -> BasicBlock { + if !Self::is_checkpoint(self.tcx, self.def_id) { + return block; + } + + let Some(marker_def_id) = self.tcx.get_diagnostic_item(sym::__checkpoint) else { + return block; + }; + + let next = self.cfg.start_new_block(); + let source_info = self.source_info(span); + let func = Operand::function_handle(self.tcx, marker_def_id, [], span); + let destination = Place::from(self.temp(self.tcx.types.unit, span)); + + self.cfg.terminate( + block, + source_info, + TerminatorKind::Call { + func, + args: Box::new([]), + destination, + target: Some(next), + unwind: UnwindAction::Continue, + call_source: CallSource::Misc, + fn_span: span, + }, + ); + + next + } + + fn is_checkpoint(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + find_attr!(tcx, def_id, RadProtected(_)) + } +} diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 24d184121ffd0..52cc2f8d608f4 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -449,6 +449,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => { + if from_hir_call { + block = this.inject_checkpoint_marker(block, fn_span); + } + let fun = unpack!(block = this.as_local_operand(block, fun)); let args: Box<[_]> = args .into_iter() diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index c8ca7bfccc07c..ccea1bbb9493e 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -1276,5 +1276,6 @@ mod expr; mod matches; mod misc; mod scope; +mod checkpoint; pub(crate) use expr::category::Category as ExprCategory; diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 65ac50a0d5cbe..4a2da40c61e08 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -119,7 +119,7 @@ impl Runtime { } - // Internal checkpoint marker inserted by AST + // Internal checkpoint marker inserted during MIR building // Indicates the MIR pass should rewrite the terminator to a `checkpoint` call #[stable(feature = "rad_protected", since = "1.95.0")] #[rustc_diagnostic_item = "__checkpoint"] From 30b7cd666b1db8f573979a8a684a32493f0112d7 Mon Sep 17 00:00:00 2001 From: Ezlanding1 <113404035+Ezlanding1@users.noreply.github.com> Date: Sun, 6 Sep 2026 06:57:26 -0400 Subject: [PATCH 40/40] Create payload in shared mem, calculate and inject payload size in MIR --- .../src/rad_protected/triplicate.rs | 2 +- .../src/rad_protected_analysis.rs | 84 ++++++++++++++----- .../src/rad_protected_liveness_analysis.rs | 12 ++- library/rad_protected/src/runtime.rs | 5 +- library/rad_protected/src/shared_memory.rs | 34 ++++---- 5 files changed, 94 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs index b62b588a2cc2b..928f3f19ca14b 100644 --- a/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs +++ b/compiler/rustc_builtin_macros/src/rad_protected/triplicate.rs @@ -74,7 +74,7 @@ pub(crate) fn triplicate( Ident::new(sym::RadRustRuntime, DUMMY_SP), Ident::new(sym::triplicate_process, DUMMY_SP), ], - thin_vec![] + thin_vec![cx.expr_usize(DUMMY_SP, 0usize)] ) )); diff --git a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs index 74279b6bf39a5..5da48963c3651 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_analysis.rs @@ -1,7 +1,7 @@ //! This pass performs an analysis to determine reference, raw pointer, and unsafe function call accesses that are protected by `#[rad_protected]` use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; -use rustc_hir::{find_attr, Mutability, LangItem}; +use rustc_hir::{find_attr, Mutability}; use rustc_middle::mir::{ Body, Local, LocalKind, Operand, Place, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind, BasicBlock, SourceInfo, LocalDecl, Statement, CastKind, AggregateKind, ProjectionElem, @@ -11,8 +11,9 @@ use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Ty, TyCtxt}; use super::rad_protected_liveness_analysis::{CheckpointAnalysis, LiveLocals}; -use rustc_span::{sym, source_map::Spanned}; +use rustc_span::{sym, Span, source_map::Spanned}; use rustc_index::IndexVec; +use rustc_middle::mir::interpret::Scalar; pub(super) struct RadProtectedAnalysis; @@ -40,10 +41,19 @@ impl<'tcx> crate::MirPass<'tcx> for RadProtectedAnalysis { eprintln!("================================"); eprintln!("=== Injecting checkpoints ==="); + + let mut max_payload_size: u64 = 0; + for (bb_idx, live) in checkpoint_analysis.checkpoints { - inject_checkpoint_call(tcx, body, live, bb_idx); + let payload_size = inject_checkpoint_call(tcx, body, live, bb_idx); + max_payload_size = max_payload_size.max(payload_size); eprintln!("Successfully injected checkpoint call at {:?}", bb_idx); } + + inject_payload_size_arg(tcx, body, max_payload_size) + .expect("Failed to find a triplicate_process call to inject payload size"); + eprintln!("Checkpoint injection complete with payload size of {} bytes", max_payload_size); + eprintln!("================================"); let sources = build_pointer_sources(body); @@ -445,12 +455,14 @@ fn resolve_pointer_source<'tcx>( } } -fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: LiveLocals, next: BasicBlock) -> BasicBlock { +fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: LiveLocals, next: BasicBlock) -> u64 { let checkpoint_def_id = tcx.get_diagnostic_item(sym::checkpoint).unwrap(); let source_info = SourceInfo::outermost(body.span); let span = body.span; let num_locals = live.locals().len() as u64; + let mut payload_size: u64 = 0; + let mut stmts: Vec> = Vec::new(); let mut push_stmt = |kind: StatementKind<'tcx>| { stmts.push(Statement::new(source_info, kind)); @@ -471,7 +483,7 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: let array_ty = Ty::new_array(tcx, slot_ty, num_locals); let array_local = push_local(body, array_ty); - let size_of_def_id = tcx.require_lang_item(LangItem::SizeOf, span); + let typing_env = body.typing_env(tcx); for (i, &local) in live.locals().iter().enumerate() { let local_ty = body.local_decls[local].ty; @@ -495,11 +507,21 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: ), ); - // size_of::() - let size_operand = - Operand::unevaluated_constant(tcx, size_of_def_id, &[local_ty.into()], span); + let local_size = tcx + .layout_of(typing_env.as_query_input(local_ty)) + .unwrap() + .size + .bytes(); + payload_size += local_size; + + let size_operand = Operand::const_from_scalar( + tcx, + tcx.types.usize, + Scalar::from_target_usize(local_size.try_into().unwrap(), &tcx), + span, + ); - // _3 = (_2, size_of::()); + // _3 = (_2, local_size); let slot_local = push_local(body, slot_ty); push_assign( Place::from(slot_local), @@ -552,19 +574,39 @@ fn inject_checkpoint_call<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, live: let block_data = &mut body.basic_blocks_mut()[next]; block_data.statements.extend(stmts); - match &mut block_data.terminator_mut().kind { - TerminatorKind::Call { func: callee, args, fn_span, .. } => { - *callee = func; - *args = Box::new([Spanned { node: Operand::Move(Place::from(slice_ref)), span }]); - *fn_span = span; - } - TerminatorKind::TailCall { func: callee, args, fn_span, .. } => { - *callee = func; - *args = Box::new([Spanned { node: Operand::Move(Place::from(slice_ref)), span }]); - *fn_span = span; + let (callee, args, term_fn_span) = call_terminator_parts_mut(&mut block_data.terminator_mut().kind).unwrap(); + *callee = func; + *args = Box::new([Spanned { node: Operand::Move(Place::from(slice_ref)), span }]); + *term_fn_span = span; + + payload_size +} + +fn inject_payload_size_arg<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, max_payload_size: u64) -> Option<()> { + + for bb_data in body.basic_blocks.as_mut().iter_mut() { + if CheckpointAnalysis::is_call_to(tcx, bb_data, sym::triplicate_process) { + let span = bb_data.terminator().source_info.span; + let size_operand = Operand::const_from_scalar( + tcx, + tcx.types.usize, + Scalar::from_target_usize(max_payload_size, &tcx), + span, + ); + + let (_, args, _) = call_terminator_parts_mut(&mut bb_data.terminator_mut().kind).unwrap(); + *args = Box::new([Spanned { node: size_operand, span }]); + return Some(()); } - _ => unreachable!(), } - next + None +} + +fn call_terminator_parts_mut<'a, 'tcx>(kind: &'a mut TerminatorKind<'tcx>) -> Option<(&'a mut Operand<'tcx>, &'a mut Box<[Spanned>]>, &'a mut Span)> { + match kind { + TerminatorKind::Call { func, args, fn_span, .. } + | TerminatorKind::TailCall { func, args, fn_span, .. } => Some((func, args, fn_span)), + _ => None, + } } diff --git a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs index d48324cc4fc6c..9c07b304d551a 100644 --- a/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs +++ b/compiler/rustc_mir_transform/src/rad_protected_liveness_analysis.rs @@ -6,7 +6,7 @@ use std::collections::VecDeque; use rustc_data_structures::{fx::FxHashSet, graph::Successors}; use rustc_index::{bit_set::DenseBitSet, IndexVec}; use rustc_span::def_id::DefId; -use rustc_span::sym; +use rustc_span::{sym, Symbol}; use rustc_middle::ty::TyCtxt; pub(super) struct CheckpointAnalysis { @@ -35,13 +35,17 @@ impl CheckpointAnalysis { } pub(super) fn is_checkpoint(tcx: TyCtxt<'_>, bb_data: &BasicBlockData<'_>) -> bool { - Self::callee_def_id(bb_data).is_some_and(|def_id| tcx.is_diagnostic_item(sym::__checkpoint, def_id)) + Self::is_call_to(tcx, bb_data, sym::__checkpoint) + } + + pub(super) fn is_call_to(tcx: TyCtxt<'_>, bb_data: &BasicBlockData<'_>, symbol: Symbol) -> bool { + Self::callee_def_id(bb_data).is_some_and(|def_id| tcx.is_diagnostic_item(symbol, def_id)) } fn callee_def_id<'tcx>(bb_data: &BasicBlockData<'tcx>) -> Option { let func = match &bb_data.terminator().kind { - TerminatorKind::Call { func, .. } => func, - TerminatorKind::TailCall { func, .. } => func, + TerminatorKind::Call { func, .. } + | TerminatorKind::TailCall { func, .. } => func, _ => return None, }; func.const_fn_def().map(|(def_id, _)| def_id) diff --git a/library/rad_protected/src/runtime.rs b/library/rad_protected/src/runtime.rs index 4a2da40c61e08..4a32a96a35bde 100644 --- a/library/rad_protected/src/runtime.rs +++ b/library/rad_protected/src/runtime.rs @@ -12,12 +12,13 @@ impl Runtime { /// Triplicate the running process over the current rad_protected function /// Fork the running process and copy its memory to create 3 identical processes #[stable(feature = "rad_protected", since = "1.95.0")] - pub fn triplicate_process() -> Result { + #[rustc_diagnostic_item = "triplicate_process"] + pub fn triplicate_process(payload_size: usize) -> Result { if ROLE.lock().unwrap().as_ref().is_some() { return Err(()); } - let Ok(shared_memory) = SharedMemory::open() else { + let Ok(shared_memory) = SharedMemory::open(payload_size) else { return Err(()); }; diff --git a/library/rad_protected/src/shared_memory.rs b/library/rad_protected/src/shared_memory.rs index f47b448b16a65..240f6080d7035 100644 --- a/library/rad_protected/src/shared_memory.rs +++ b/library/rad_protected/src/shared_memory.rs @@ -2,11 +2,11 @@ use super::libc_helpers::{shared_mmap, munmap}; use super::mini_std::{io, ipc::Barrier}; use core::{mem::size_of, ptr, ops::{Deref, DerefMut}}; -pub(super) struct SharedMemoryData { +pub(super) struct SharedMemoryHeader { barrier: Barrier, } -impl SharedMemoryData { +impl SharedMemoryHeader { pub(super) fn sync(&self) -> bool { self.barrier.wait().is_leader() } @@ -14,31 +14,35 @@ impl SharedMemoryData { #[derive(Debug)] pub(super) struct SharedMemory { - inner: *mut SharedMemoryData, + header: *mut SharedMemoryHeader, + _payload: *mut u8, + size: usize, } impl SharedMemory { - pub(super) fn open() -> io::Result { - let size = size_of::(); + pub(super) fn open(payload_size: usize) -> io::Result { + let header_size = size_of::(); + let size = header_size + (payload_size * 3); - let ptr = shared_mmap(size)?; + let ptr = shared_mmap(size)? as *mut u8; - let inner = ptr.cast::(); + let header = ptr.cast::(); + let payload = unsafe { ptr.add(header_size) }; unsafe { - ptr::write(inner, - SharedMemoryData { + ptr::write(header, + SharedMemoryHeader { barrier: Barrier::new(3), }, ); } - Ok(Self { inner }) + Ok(Self { header, _payload: payload, size }) } pub(super) fn close(&self) { - unsafe { ptr::drop_in_place(self.inner); } - munmap(self.inner as *mut _, size_of::()); + unsafe { ptr::drop_in_place(self.header); } + munmap(self.header as *mut _, self.size); } } @@ -46,15 +50,15 @@ unsafe impl Send for SharedMemory {} unsafe impl Sync for SharedMemory {} impl Deref for SharedMemory { - type Target = SharedMemoryData; + type Target = SharedMemoryHeader; fn deref(&self) -> &Self::Target { - unsafe { &*self.inner } + unsafe { &*self.header } } } impl DerefMut for SharedMemory { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.inner } + unsafe { &mut *self.header } } }