Skip to content

Commit 2e89c7d

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 f2b583e commit 2e89c7d

File tree

4 files changed

+252
-240
lines changed

4 files changed

+252
-240
lines changed

c2rust-transpile/src/c_ast/mod.rs

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

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

c2rust-transpile/src/translator/mod.rs

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

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

c2rust-transpile/tests/snapshots/macros.c

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

290290
typedef int (*fn_ptr_ty)(char);
291291

292-
const struct fn_ptrs fns = {NULL, NULL, NULL};
292+
// TODO Skip for now since it uses `libc`, which we don't test in snapshots.
293+
// const struct fn_ptrs fns = {NULL, NULL, NULL};
294+
const struct fn_ptrs fns = {};
293295

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

0 commit comments

Comments
 (0)