Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8fad3a1
tidy: allow stdlib to depend on moto-rt
lasiotus Sep 24, 2025
a828ffc
Add Motor OS std library port
lasiotus Oct 8, 2025
e4ead0e
Guard HIR lowered contracts with contract_checks
dawidl022 Jul 25, 2025
2d87527
Guard HIR contracts based on compiler flag rather than lang_item
dawidl022 Aug 16, 2025
2a5dac7
Remove no longer used contract_checks intrinsic
dawidl022 Sep 4, 2025
a172a66
Wrap contract clauses in brances instead of parenthesis
dawidl022 Sep 4, 2025
4d23c41
Test cross-crate runtime contract checks
dawidl022 Oct 12, 2025
12c0d69
use check_nondet helper in a few more places
RalfJung Oct 13, 2025
42eb21c
Merge pull request #4629 from RalfJung/check-nondet
RalfJung Oct 13, 2025
fe91820
native-lib: support all types with Scalar layout
RalfJung Oct 14, 2025
2feb8bb
better error when native code tries to execute Rust function
RalfJung Oct 14, 2025
0317934
Merge pull request #4628 from RalfJung/native-lib
RalfJung Oct 14, 2025
75b60f1
Prepare for merging from rust-lang/rust
Oct 15, 2025
bcef7d5
Merge ref '235a4c083eb2' from rust-lang/rust
Oct 15, 2025
a9fc0ca
fmt
Oct 15, 2025
1819f5e
don't debug-print an Any
RalfJung Oct 15, 2025
afa2315
Merge pull request #4631 from rust-lang/rustup-2025-10-15
RalfJung Oct 15, 2025
a6705ea
Merge pull request #4632 from RalfJung/supervisor-panic
RalfJung Oct 15, 2025
7c419e8
native-lib: bump libffi
nia-e Oct 15, 2025
51cab7c
avoid some Vector allocations
RalfJung Oct 15, 2025
a084f45
Merge pull request #4634 from nia-e/new-libffi
RalfJung Oct 15, 2025
1de9b49
remove duplicate inline macro
h3nryc0ding Oct 15, 2025
0ecee8c
Prepare for merging from rust-lang/rust
RalfJung Oct 15, 2025
7c28d96
Merge ref '28d0a4a205f9' from rust-lang/rust
RalfJung Oct 15, 2025
984542c
Don't highlight `let` expressions as having type `bool`
pommicket Oct 15, 2025
952be8e
fix genmc build
RalfJung Oct 15, 2025
d458363
Merge pull request #4635 from RalfJung/rustup
RalfJung Oct 15, 2025
2f04473
update lockfile
RalfJung Oct 15, 2025
a8c79b8
Fix ICE on offsetted ZST pointer
Mark-Simulacrum Oct 11, 2025
8787c0b
Use `bit_set::Word` in a couple more places.
nnethercote Oct 16, 2025
74ac3ec
style-guide: fix typo for empty struct advice
fee1-dead Oct 16, 2025
7a11c72
is_ascii on an empty string or slice returns true
timvisee Oct 16, 2025
334b3af
Rollup merge of #144438 - dawidl022:contracts/guarded-lowering, r=oli…
matthiaskrgr Oct 16, 2025
91f48d8
Rollup merge of #147000 - moturus:motor-os_stdlib_pr, r=tgross35
matthiaskrgr Oct 16, 2025
5408ec3
Rollup merge of #147576 - Mark-Simulacrum:fix-offset-zst, r=nnetherco…
matthiaskrgr Oct 16, 2025
42cf0bd
Rollup merge of #147732 - h3nryc0ding:master, r=chenyukang
matthiaskrgr Oct 16, 2025
d8dd468
Rollup merge of #147738 - pommicket:issue-147665, r=madsmtm
matthiaskrgr Oct 16, 2025
6f66f3b
Rollup merge of #147744 - RalfJung:miri, r=RalfJung
matthiaskrgr Oct 16, 2025
7604d12
Rollup merge of #147751 - nnethercote:bit_set-Word, r=Zalathar
matthiaskrgr Oct 16, 2025
3a993f7
Rollup merge of #147752 - fee1-dead-contrib:sgtypo, r=chenyukang
matthiaskrgr Oct 16, 2025
f337e28
Rollup merge of #147773 - timvisee:docs-empty-is-ascii, r=Noratrieb
matthiaskrgr Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]

[[package]]
Expand Down Expand Up @@ -2106,19 +2106,19 @@ dependencies = [

[[package]]
name = "libffi"
version = "4.1.1"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7681c6fab541f799a829e44a445a0666cf8d8a6cfebf89419e6aed52c604e87"
checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5"
dependencies = [
"libc",
"libffi-sys",
]

[[package]]
name = "libffi-sys"
version = "3.3.2"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0d828d367b4450ed08e7d510dc46636cd660055f50d67ac943bfe788767c29"
checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7"
dependencies = [
"cc",
]
Expand Down Expand Up @@ -2374,7 +2374,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]

[[package]]
Expand Down
324 changes: 324 additions & 0 deletions compiler/rustc_ast_lowering/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
use thin_vec::thin_vec;

use crate::LoweringContext;

impl<'a, 'hir> LoweringContext<'a, 'hir> {
/// Lowered contracts are guarded with the `contract_checks` compiler flag,
/// i.e. the flag turns into a boolean guard in the lowered HIR. The reason
/// for not eliminating the contract code entirely when the `contract_checks`
/// flag is disabled is so that contracts can be type checked, even when
/// they are disabled, which avoids them becoming stale (i.e. out of sync
/// with the codebase) over time.
///
/// The optimiser should be able to eliminate all contract code guarded
/// by `if false`, leaving the original body intact when runtime contract
/// checks are disabled.
pub(super) fn lower_contract(
&mut self,
body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
contract: &rustc_ast::FnContract,
) -> rustc_hir::Expr<'hir> {
match (&contract.requires, &contract.ensures) {
(Some(req), Some(ens)) => {
// Lower the fn contract, which turns:
//
// { body }
//
// into:
//
// let __postcond = if contract_checks {
// contract_check_requires(PRECOND);
// Some(|ret_val| POSTCOND)
// } else {
// None
// };
// {
// let ret = { body };
//
// if contract_checks {
// contract_check_ensures(__postcond, ret)
// } else {
// ret
// }
// }

let precond = self.lower_precond(req);
let postcond_checker = self.lower_postcond_checker(ens);

let contract_check =
self.lower_contract_check_with_postcond(Some(precond), postcond_checker);

let wrapped_body =
self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
self.expr_block(wrapped_body)
}
(None, Some(ens)) => {
// Lower the fn contract, which turns:
//
// { body }
//
// into:
//
// let __postcond = if contract_checks {
// Some(|ret_val| POSTCOND)
// } else {
// None
// };
// {
// let ret = { body };
//
// if contract_checks {
// contract_check_ensures(__postcond, ret)
// } else {
// ret
// }
// }

let postcond_checker = self.lower_postcond_checker(ens);
let contract_check =
self.lower_contract_check_with_postcond(None, postcond_checker);

let wrapped_body =
self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
self.expr_block(wrapped_body)
}
(Some(req), None) => {
// Lower the fn contract, which turns:
//
// { body }
//
// into:
//
// {
// if contracts_checks {
// contract_requires(PRECOND);
// }
// body
// }
let precond = self.lower_precond(req);
let precond_check = self.lower_contract_check_just_precond(precond);

let body = self.arena.alloc(body(self));

// Flatten the body into precond check, then body.
let wrapped_body = self.block_all(
body.span,
self.arena.alloc_from_iter([precond_check].into_iter()),
Some(body),
);
self.expr_block(wrapped_body)
}
(None, None) => body(self),
}
}

/// Lower the precondition check intrinsic.
fn lower_precond(&mut self, req: &Box<rustc_ast::Expr>) -> rustc_hir::Stmt<'hir> {
let lowered_req = self.lower_expr_mut(&req);
let req_span = self.mark_span_with_reason(
rustc_span::DesugaringKind::Contract,
lowered_req.span,
None,
);
let precond = self.expr_call_lang_item_fn_mut(
req_span,
rustc_hir::LangItem::ContractCheckRequires,
&*arena_vec![self; lowered_req],
);
self.stmt_expr(req.span, precond)
}

fn lower_postcond_checker(
&mut self,
ens: &Box<rustc_ast::Expr>,
) -> &'hir rustc_hir::Expr<'hir> {
let ens_span = self.lower_span(ens.span);
let ens_span =
self.mark_span_with_reason(rustc_span::DesugaringKind::Contract, ens_span, None);
let lowered_ens = self.lower_expr_mut(&ens);
self.expr_call_lang_item_fn(
ens_span,
rustc_hir::LangItem::ContractBuildCheckEnsures,
&*arena_vec![self; lowered_ens],
)
}

fn lower_contract_check_just_precond(
&mut self,
precond: rustc_hir::Stmt<'hir>,
) -> rustc_hir::Stmt<'hir> {
let stmts = self.arena.alloc_from_iter([precond].into_iter());

let then_block_stmts = self.block_all(precond.span, stmts, None);
let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));

let precond_check = rustc_hir::ExprKind::If(
self.arena.alloc(self.expr_bool_literal(precond.span, self.tcx.sess.contract_checks())),
then_block,
None,
);

let precond_check = self.expr(precond.span, precond_check);
self.stmt_expr(precond.span, precond_check)
}

fn lower_contract_check_with_postcond(
&mut self,
precond: Option<rustc_hir::Stmt<'hir>>,
postcond_checker: &'hir rustc_hir::Expr<'hir>,
) -> &'hir rustc_hir::Expr<'hir> {
let stmts = self.arena.alloc_from_iter(precond.into_iter());
let span = match precond {
Some(precond) => precond.span,
None => postcond_checker.span,
};

let postcond_checker = self.arena.alloc(self.expr_enum_variant_lang_item(
postcond_checker.span,
rustc_hir::lang_items::LangItem::OptionSome,
&*arena_vec![self; *postcond_checker],
));
let then_block_stmts = self.block_all(span, stmts, Some(postcond_checker));
let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));

let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item(
postcond_checker.span,
rustc_hir::lang_items::LangItem::OptionNone,
Default::default(),
));
let else_block = self.block_expr(none_expr);
let else_block = self.arena.alloc(self.expr_block(else_block));

let contract_check = rustc_hir::ExprKind::If(
self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())),
then_block,
Some(else_block),
);
self.arena.alloc(self.expr(span, contract_check))
}

fn wrap_body_with_contract_check(
&mut self,
body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
contract_check: &'hir rustc_hir::Expr<'hir>,
postcond_span: rustc_span::Span,
) -> &'hir rustc_hir::Block<'hir> {
let check_ident: rustc_span::Ident =
rustc_span::Ident::from_str_and_span("__ensures_checker", postcond_span);
let (check_hir_id, postcond_decl) = {
// Set up the postcondition `let` statement.
let (checker_pat, check_hir_id) = self.pat_ident_binding_mode_mut(
postcond_span,
check_ident,
rustc_hir::BindingMode::NONE,
);
(
check_hir_id,
self.stmt_let_pat(
None,
postcond_span,
Some(contract_check),
self.arena.alloc(checker_pat),
rustc_hir::LocalSource::Contract,
),
)
};

// Install contract_ensures so we will intercept `return` statements,
// then lower the body.
self.contract_ensures = Some((postcond_span, check_ident, check_hir_id));
let body = self.arena.alloc(body(self));

// Finally, inject an ensures check on the implicit return of the body.
let body = self.inject_ensures_check(body, postcond_span, check_ident, check_hir_id);

// Flatten the body into precond, then postcond, then wrapped body.
let wrapped_body = self.block_all(
body.span,
self.arena.alloc_from_iter([postcond_decl].into_iter()),
Some(body),
);
wrapped_body
}

/// Create an `ExprKind::Ret` that is optionally wrapped by a call to check
/// a contract ensures clause, if it exists.
pub(super) fn checked_return(
&mut self,
opt_expr: Option<&'hir rustc_hir::Expr<'hir>>,
) -> rustc_hir::ExprKind<'hir> {
let checked_ret =
if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures {
let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span));
Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id))
} else {
opt_expr
};
rustc_hir::ExprKind::Ret(checked_ret)
}

/// Wraps an expression with a call to the ensures check before it gets returned.
pub(super) fn inject_ensures_check(
&mut self,
expr: &'hir rustc_hir::Expr<'hir>,
span: rustc_span::Span,
cond_ident: rustc_span::Ident,
cond_hir_id: rustc_hir::HirId,
) -> &'hir rustc_hir::Expr<'hir> {
// {
// let ret = { body };
//
// if contract_checks {
// contract_check_ensures(__postcond, ret)
// } else {
// ret
// }
// }
let ret_ident: rustc_span::Ident = rustc_span::Ident::from_str_and_span("__ret", span);

// Set up the return `let` statement.
let (ret_pat, ret_hir_id) =
self.pat_ident_binding_mode_mut(span, ret_ident, rustc_hir::BindingMode::NONE);

let ret_stmt = self.stmt_let_pat(
None,
span,
Some(expr),
self.arena.alloc(ret_pat),
rustc_hir::LocalSource::Contract,
);

let ret = self.expr_ident(span, ret_ident, ret_hir_id);

let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
let contract_check = self.expr_call_lang_item_fn_mut(
span,
rustc_hir::LangItem::ContractCheckEnsures,
arena_vec![self; *cond_fn, *ret],
);
let contract_check = self.arena.alloc(contract_check);
let call_expr = self.block_expr_block(contract_check);

// same ident can't be used in 2 places, so we create a new one for the
// else branch
let ret = self.expr_ident(span, ret_ident, ret_hir_id);
let ret_block = self.block_expr_block(ret);

let contracts_enabled: rustc_hir::Expr<'_> =
self.expr_bool_literal(span, self.tcx.sess.contract_checks());
let contract_check = self.arena.alloc(self.expr(
span,
rustc_hir::ExprKind::If(
self.arena.alloc(contracts_enabled),
call_expr,
Some(ret_block),
),
));

let attrs: rustc_ast::AttrVec = thin_vec![self.unreachable_code_attr(span)];
self.lower_attrs(contract_check.hir_id, &attrs, span, rustc_hir::Target::Expression);

let ret_block = self.block_all(span, arena_vec![self; ret_stmt], Some(contract_check));
self.arena.alloc(self.expr_block(self.arena.alloc(ret_block)))
}
}
Loading
Loading