Skip to content

Commit a4708f4

Browse files
committed
transpile: expand --translate-const-macros conservative to a lot more CExprKinds
We do this by recursively checking whether an expression is `const`. This is generally done in a conservative manner, modulo the `ExplicitCast` bug (#853), non-`const` `sizeof(VLA)`s, and `f128`'s non-`const` methods (#1262). Statements are not handled yet.
1 parent fbbc9cb commit a4708f4

File tree

4 files changed

+239
-214
lines changed

4 files changed

+239
-214
lines changed

c2rust-transpile/src/c_ast/mod.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ impl TypedAstContext {
601601
}
602602
}
603603

604-
// Pessimistically try to check if an expression doesn't return. If it does, or we can't tell
604+
/// Pessimistically try to check if an expression doesn't return. If it does, or we can't tell
605605
/// that it doesn't, return `false`.
606606
pub fn expr_diverges(&self, expr_id: CExprId) -> bool {
607607
let func_id = match self.index(expr_id).kind {
@@ -624,6 +624,70 @@ impl TypedAstContext {
624624
}
625625
}
626626

627+
/// Pessimistically try to check if an expression is `const`.
628+
/// If it's not, or we can't tell if it is, return `false`.
629+
pub fn is_const_expr(&self, expr: CExprId) -> bool {
630+
use CExprKind::*;
631+
let is_const = |expr| self.is_const_expr(expr);
632+
match self[expr].kind {
633+
// A literal is always `const`.
634+
Literal(_, _) => true,
635+
// Unary ops should be `const`.
636+
// TODO handle `f128` or use the primitive type.
637+
Unary(_, _, expr, _) => is_const(expr),
638+
// Not sure what a `None` `CExprId` means here
639+
// or how to detect a `sizeof` of a VLA, which is non-`const`.
640+
UnaryType(_, _, _, _) => true,
641+
// Not sure what a `OffsetOfKind::Variable` means.
642+
OffsetOf(_, _) => true,
643+
// `ptr::offset` (ptr `BinOp::Add`) was `const` stabilized in `1.61.0`.
644+
// `ptr::offset_from` (ptr `BinOp::Subtract`) was `const` stabilized in `1.65.0`.
645+
// TODO `f128` is not yet handled, as we should eventually
646+
// switch to the (currently unstable) `f128` primitive type (#1262).
647+
Binary(_, _, lhs, rhs, _, _) => is_const(lhs) && is_const(rhs),
648+
// `as` casts are always `const`.
649+
ImplicitCast(_, _, _, _, _) => true,
650+
// `as` casts are always `const`.
651+
// TODO This is `const`, although there's a bug #853.
652+
ExplicitCast(_, _, _, _, _) => true,
653+
// This is used in `const` locations like `match` patterns and array lengths, so it must be `const`.
654+
ConstantExpr(_, _, _) => true,
655+
// A reference in an already otherwise `const` context should be `const` itself.
656+
DeclRef(_, _, _) => true,
657+
Call(_, fn_expr, ref args) => {
658+
let is_const_fn = false; // TODO detect which `fn`s are `const`.
659+
is_const(fn_expr) && args.iter().copied().all(is_const) && is_const_fn
660+
}
661+
Member(_, expr, _, _, _) => is_const(expr),
662+
ArraySubscript(_, array, index, _) => is_const(array) && is_const(index),
663+
Conditional(_, cond, if_true, if_false) => {
664+
is_const(cond) && is_const(if_true) && is_const(if_false)
665+
}
666+
BinaryConditional(_, cond, if_false) => is_const(cond) && is_const(if_false),
667+
InitList(_, ref inits, _, _) => inits.iter().copied().all(is_const),
668+
ImplicitValueInit(_) => true,
669+
Paren(_, expr) => is_const(expr),
670+
CompoundLiteral(_, expr) => is_const(expr),
671+
Predefined(_, expr) => is_const(expr),
672+
Statements(_, stmt) => self.is_const_stmt(stmt),
673+
VAArg(_, expr) => is_const(expr),
674+
// SIMD is not yet `const` in Rust.
675+
ShuffleVector(_, _) | ConvertVector(_, _) => false,
676+
DesignatedInitExpr(_, _, expr) => is_const(expr),
677+
Choose(_, cond, if_true, if_false, _) => {
678+
is_const(cond) && is_const(if_true) && is_const(if_false)
679+
}
680+
// Atomics are not yet `const` in Rust.
681+
Atomic { .. } => false,
682+
BadExpr => false,
683+
}
684+
}
685+
686+
pub fn is_const_stmt(&self, _stmt: CStmtId) -> bool {
687+
// TODO
688+
false
689+
}
690+
627691
pub fn prune_unwanted_decls(&mut self, want_unused_functions: bool) {
628692
// Starting from a set of root declarations, walk each one to find declarations it
629693
// depends on. Then walk each of those, recursively.
@@ -1186,6 +1250,9 @@ pub enum OffsetOfKind {
11861250

11871251
/// Represents an expression in C (6.5 Expressions)
11881252
///
1253+
/// This is modeled on Clang's APIs, so where documentation
1254+
/// is lacking here, look at Clang.
1255+
///
11891256
/// We've kept a qualified type on every node since Clang has this information available, and since
11901257
/// the semantics of translations of certain constructs often depend on the type of the things they
11911258
/// are given.

c2rust-transpile/src/translator/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,15 +2163,16 @@ impl<'c> Translation<'c> {
21632163

21642164
/// Determine if we're able to convert this const macro expansion.
21652165
fn can_convert_const_macro_expansion(&self, expr_id: CExprId) -> TranslationResult<()> {
2166-
let kind = &self.ast_context[expr_id].kind;
21672166
match self.tcfg.translate_const_macros {
21682167
TranslateMacros::None => Err(format_err!("translate_const_macros is None"))?,
2169-
TranslateMacros::Conservative => match *kind {
2170-
CExprKind::Literal(..) => Ok(()), // Literals are leaf expressions, so they should always be const-compatible.
2171-
_ => Err(format_err!(
2172-
"conservative const macros don't yet allow {kind:?}"
2173-
))?,
2174-
},
2168+
TranslateMacros::Conservative => {
2169+
// TODO We still allow `CExprKind::ExplicitCast`s
2170+
// even though they're broken (see #853).
2171+
if !self.ast_context.is_const_expr(expr_id) {
2172+
Err(format_err!("non-const expr {expr_id:?}"))?;
2173+
}
2174+
Ok(())
2175+
}
21752176
TranslateMacros::Experimental => Ok(()),
21762177
}
21772178
}

c2rust-transpile/tests/snapshots/macros.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ struct fn_ptrs {
282282

283283
typedef int (*fn_ptr_ty)(char);
284284

285-
const struct fn_ptrs fns = {NULL, NULL, NULL};
285+
// TODO Skip for now since it uses `libc`, which we don't test in snapshots.
286+
// const struct fn_ptrs fns = {NULL, NULL, NULL};
287+
const struct fn_ptrs fns = {};
286288

287289
// Make sure we can't refer to globals in a const macro
288290
#define GLOBAL_REF &fns

0 commit comments

Comments
 (0)