diff --git a/c2rust-transpile/src/cfg/mod.rs b/c2rust-transpile/src/cfg/mod.rs index 12e167a899..c4b9aa0092 100644 --- a/c2rust-transpile/src/cfg/mod.rs +++ b/c2rust-transpile/src/cfg/mod.rs @@ -18,6 +18,7 @@ use crate::c_ast::iterators::{DFExpr, SomeId}; use crate::c_ast::CLabelId; +use crate::context::{ExprContext, ExprTreeContext}; use crate::diagnostics::TranslationResult; use crate::rust_ast::{self, SpanExt}; use c2rust_ast_printer::pprust; @@ -556,7 +557,7 @@ impl Cfg { /// Completely process a statement into a control flow graph. pub fn from_stmts( translator: &Translation, - ctx: ExprContext, + ctx: ExprTreeContext, stmt_ids: &[CStmtId], ret: ImplicitReturnType, ret_ty: Option, @@ -1373,7 +1374,7 @@ impl CfgBuilder { fn convert_stmts_help( &mut self, translator: &Translation, - ctx: ExprContext, + ctx: ExprTreeContext, stmt_ids: &[CStmtId], // C statements to translate in_tail: Option, // Are we in tail position (is there anything to fallthrough to)? entry: Label, // Current WIP block @@ -1406,7 +1407,7 @@ impl CfgBuilder { fn convert_stmt_help( &mut self, translator: &Translation, - ctx: ExprContext, + ctx: ExprTreeContext, // C statement to translate stmt_id: CStmtId, @@ -1462,7 +1463,8 @@ impl CfgBuilder { } CStmtKind::Return(expr) => { - let val = match expr.map(|i| translator.convert_expr(ctx.used(), i, ret_ty)) { + let val = match expr.map(|i| translator.convert_expr(ctx.used().into(), i, ret_ty)) + { Some(r) => Some(r?), None => None, }; @@ -1491,7 +1493,7 @@ impl CfgBuilder { // Condition let (stmts, val) = translator - .convert_scalar_to_bool_cast(ctx.used(), scrutinee, true)? + .convert_scalar_to_bool_cast(ctx.used().into(), scrutinee, true)? .discard_unsafe(); wip.extend(stmts); @@ -1564,7 +1566,7 @@ impl CfgBuilder { // Condition let (stmts, val) = translator - .convert_scalar_to_bool_cast(ctx.used(), condition, true)? + .convert_scalar_to_bool_cast(ctx.used().into(), condition, true)? .discard_unsafe(); let cond_val = translator .ast_context @@ -1643,7 +1645,7 @@ impl CfgBuilder { // Condition let (stmts, val) = translator - .convert_scalar_to_bool_cast(ctx.used(), condition, true)? + .convert_scalar_to_bool_cast(ctx.used().into(), condition, true)? .discard_unsafe(); let cond_val = translator .ast_context @@ -1698,7 +1700,7 @@ impl CfgBuilder { // Condition if let Some(cond) = condition { let (stmts, val) = translator - .convert_scalar_to_bool_cast(ctx.used(), cond, true)? + .convert_scalar_to_bool_cast(ctx.used().into(), cond, true)? .discard_unsafe(); let cond_val = translator .ast_context @@ -1743,7 +1745,7 @@ impl CfgBuilder { None => slf.add_block(incr_entry, BasicBlock::new_jump(cond_entry)), Some(incr) => { let incr_stmts = translator - .convert_expr(ctx.unused(), incr, None)? + .convert_expr(ctx.unused().into(), incr, None)? .into_stmts(); let mut incr_wip = slf.new_wip_block(incr_entry); incr_wip.extend(incr_stmts); @@ -1859,7 +1861,7 @@ impl CfgBuilder { Err(mut wip) => { wip.extend( translator - .convert_expr(ctx.unused(), expr, None)? + .convert_expr(ctx.unused().into(), expr, None)? .into_stmts(), ); @@ -1926,7 +1928,7 @@ impl CfgBuilder { let pat = translator .convert_expr_with_optional_cast( - ctx.const_().pattern().used(), + ctx.const_().pattern().used().into(), switch_case.expected_type_id, case_expr, ) @@ -2033,7 +2035,11 @@ impl CfgBuilder { } let (stmts, val) = translator - .convert_expr_with_optional_cast(ctx.used(), expected_type_id, scrutinee)? + .convert_expr_with_optional_cast( + ctx.used().into(), + expected_type_id, + scrutinee, + )? .discard_unsafe(); wip.extend(stmts); diff --git a/c2rust-transpile/src/context.rs b/c2rust-transpile/src/context.rs new file mode 100644 index 0000000000..bbf06383b4 --- /dev/null +++ b/c2rust-transpile/src/context.rs @@ -0,0 +1,310 @@ +use indexmap::IndexSet; + +use crate::c_ast::CDeclId; + +/// The translation context that applies to any item, and is inherited by all child items. +#[derive(Debug, Clone, Copy, Default)] +pub struct ItemContext { + /// Currently converting the macro definition with the given `CDeclId`. + converting_macro: Option, +} + +impl ItemContext { + pub(crate) fn converting_macro(&self) -> Option { + self.converting_macro + } + + pub(crate) fn set_converting_macro(self, mac: CDeclId) -> Self { + Self { + converting_macro: Some(mac), + } + } +} + +/// The translation context that applies to an expression and all of its children. +#[derive(Debug, Clone, Copy, Default)] +pub struct ExprTreeContext { + parent: ItemContext, + + /// In a Rust const context, for example in a static initializer or constant-like macro + /// translation. + is_const: bool, + + /// In a context where a pattern is expected, such as for `match` arms. + /// This restricts what kinds of expressions can be emitted. + is_pattern: bool, + + /// Evaluating a C global/static variable. + /// This is usually in a const context, but doesn't have to be, for example with initializers + /// that are executed by the `c2rust_run_static_initializers` function. + is_static: bool, + + /// Whether the result value of the expression is used in a larger expression. + /// + /// When the result value is not used in a particular context, only the side effects of the + /// expression matter. The `stmts` field of `WithStmts` should hold any statements with side + /// effects, and the `val` field is expected to be discarded. It should not appear in the final + /// transpiler output, and may be an expression that panics when evaluated. + /// + /// - `.unused()` should be called for the top-level expression of an `ExprStmt`, the increment + /// expression of a `for` loop, the `lhs` of a comma operator expression, and other such cases. + /// + /// - `.used()` should be called if an expression is needed to evaluate the side effects of a + /// parent expression, such as the arguments of a function call (unless the function is known to + /// be pure), the operands of an assignment expression, the expression of a `return` statement, + /// etc. An expression that sets `.used()` for one of its subexpressions should handle the case + /// that its own context has `!is_used`, by moving its side effects into the `stmts` field; + /// the `convert_side_effects_expr` helper can be used for this purpose. + /// + /// - If an expression is pure (has no side effects), then it should inherit its `is_used` value + /// from its parent expression: if the parent expression is going to be discarded, then so are + /// all of its pure child expressions. + is_used: bool, +} + +impl ExprTreeContext { + pub(crate) fn as_item_context(&self) -> ItemContext { + self.parent + } + + pub(crate) fn is_const(&self) -> bool { + self.is_const + } + + pub(crate) fn const_(self) -> Self { + Self { + is_const: true, + ..self + } + } + + pub(crate) fn not_const(self) -> Self { + Self { + is_const: false, + ..self + } + } + + pub(crate) fn converting_macro(&self) -> Option { + self.parent.converting_macro() + } + + pub(crate) fn is_pattern(&self) -> bool { + self.is_pattern + } + + pub(crate) fn pattern(self) -> Self { + Self { + is_pattern: true, + ..self + } + } + + pub(crate) fn is_static(&self) -> bool { + self.is_static + } + + pub(crate) fn static_(self) -> Self { + Self { + is_static: true, + ..self + } + } + + pub(crate) fn is_used(&self) -> bool { + self.is_used + } + + pub(crate) fn used(self) -> Self { + Self { + is_used: true, + ..self + } + } + + pub(crate) fn unused(self) -> Self { + Self { + is_used: false, + ..self + } + } +} + +impl From for ExprTreeContext { + fn from(decl: ItemContext) -> Self { + Self { + parent: decl, + ..Default::default() + } + } +} + +/// The translation context that applies to single expression. +#[derive(Debug, Clone, Copy, Default)] +pub struct ExprContext { + parent: ExprTreeContext, + + /// We will be referring to the expression by address. In this context we + /// can't index arrays because they may legally go out of bounds. We also + /// need to explicitly cast function references to fn() so we get their + /// address in function pointer literals. + is_address_needed: bool, + + is_bitfield_write: bool, + + pub(crate) decay_ref: DecayRef, +} + +impl ExprContext { + pub(crate) fn as_item_context(&self) -> ItemContext { + self.parent.parent + } + + pub(crate) fn as_tree_context(&self) -> ExprTreeContext { + self.parent + } + + pub(crate) fn is_address_needed(&self) -> bool { + self.is_address_needed + } + + pub(crate) fn address_needed(self) -> Self { + Self { + is_address_needed: true, + ..self + } + } + + pub(crate) fn not_address_needed(self) -> Self { + Self { + is_address_needed: false, + ..self + } + } + + pub(crate) fn is_bitfield_write(&self) -> bool { + self.is_bitfield_write + } + + pub(crate) fn bitfield_write(self) -> Self { + Self { + is_bitfield_write: true, + ..self + } + } + + pub(crate) fn is_const(&self) -> bool { + self.parent.is_const() + } + + pub(crate) fn converting_macro(&self) -> Option { + self.parent.converting_macro() + } + + pub(crate) fn decay_ref(self) -> Self { + Self { + decay_ref: DecayRef::Yes, + ..self + } + } + + pub(crate) fn is_pattern(&self) -> bool { + self.parent.is_pattern() + } + + pub(crate) fn is_static(&self) -> bool { + self.parent.is_static() + } + + pub(crate) fn is_used(&self) -> bool { + self.parent.is_used() + } + + pub(crate) fn used(self) -> Self { + Self { + parent: self.parent.used(), + ..self + } + } + + pub(crate) fn unused(self) -> Self { + Self { + parent: self.parent.unused(), + ..self + } + } +} + +impl From for ExprContext { + fn from(parent: ExprTreeContext) -> Self { + Self { + parent, + ..Default::default() + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum DecayRef { + Yes, + #[default] + Default, + No, +} + +impl DecayRef { + // Here we give intrinsic meaning to default to equate to yes/true + // when actually evaluated + pub(crate) fn is_yes(&self) -> bool { + match self { + DecayRef::Yes => true, + DecayRef::Default => true, + DecayRef::No => false, + } + } + + pub(crate) fn set_default_to_no(&mut self) { + if *self == DecayRef::Default { + *self = DecayRef::No; + } + } +} + +impl From for DecayRef { + fn from(b: bool) -> Self { + match b { + true => DecayRef::Yes, + false => DecayRef::No, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct FuncContext { + /// The name of the function we're currently translating + pub(crate) name: Option, + /// The name we give to the Rust function argument corresponding + /// to the ellipsis in variadic C functions. + pub(crate) va_list_arg_name: Option, + /// The va_list decls that are either `va_start`ed or `va_copy`ed. + pub(crate) va_list_decl_ids: Option>, + /// The name we give to the Rust variable holding all allocations made with `alloca`. + pub(crate) alloca_allocations_name: Option, +} + +impl FuncContext { + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn enter_new(&mut self, fn_name: &str) { + *self = Self { + name: Some(fn_name.to_string()), + ..Default::default() + }; + } + + pub(crate) fn get_va_list_arg_name(&self) -> &str { + self.va_list_arg_name.as_ref().unwrap() + } +} diff --git a/c2rust-transpile/src/lib.rs b/c2rust-transpile/src/lib.rs index 28d34bd305..cc37ca08b0 100644 --- a/c2rust-transpile/src/lib.rs +++ b/c2rust-transpile/src/lib.rs @@ -6,6 +6,7 @@ pub mod build_files; pub mod c_ast; pub mod cfg; mod compile_cmds; +mod context; pub mod convert_type; pub mod renamer; pub mod rust_ast; diff --git a/c2rust-transpile/src/translator/assembly.rs b/c2rust-transpile/src/translator/assembly.rs index dddf70709c..e1ade9ef72 100644 --- a/c2rust-transpile/src/translator/assembly.rs +++ b/c2rust-transpile/src/translator/assembly.rs @@ -685,7 +685,7 @@ impl<'c> Translation<'c> { /// the arguments to the assembly statement, however. pub fn convert_asm( &self, - ctx: ExprContext, + ctx: ExprTreeContext, span: Span, is_volatile: bool, asm: &str, @@ -874,7 +874,7 @@ impl<'c> Translation<'c> { // First, convert output expr if present let out_expr = if let Some((output_idx, out_expr)) = operand.out_expr { - let mut out_expr = self.convert_expr(ctx.used(), out_expr, None)?; + let mut out_expr = self.convert_expr(ctx.used().into(), out_expr, None)?; stmts.append(out_expr.stmts_mut()); let mut out_expr = out_expr.into_value(); @@ -927,7 +927,7 @@ impl<'c> Translation<'c> { // Then, handle input expr if present let in_expr = if let Some((input_idx, in_expr)) = operand.in_expr { - let mut in_expr = self.convert_expr(ctx.used(), in_expr, None)?; + let mut in_expr = self.convert_expr(ctx.used().into(), in_expr, None)?; stmts.append(in_expr.stmts_mut()); let mut in_expr = in_expr.into_value(); diff --git a/c2rust-transpile/src/translator/enums.rs b/c2rust-transpile/src/translator/enums.rs index 06529c41d2..a57daa0bcb 100644 --- a/c2rust-transpile/src/translator/enums.rs +++ b/c2rust-transpile/src/translator/enums.rs @@ -125,7 +125,7 @@ impl<'c> Translation<'c> { target_cty: CQualTypeId, mut val: Box, ) -> TranslationResult>> { - if ctx.is_pattern { + if ctx.is_pattern() { return Err(TranslationError::generic( "cast from enum is not supported in patterns", )); @@ -161,7 +161,7 @@ impl<'c> Translation<'c> { return Ok(WithStmts::new_val(val)); } - if ctx.is_pattern { + if ctx.is_pattern() { return Err(TranslationError::generic( "cast from enum is not supported in patterns", )); diff --git a/c2rust-transpile/src/translator/functions.rs b/c2rust-transpile/src/translator/functions.rs index 2526216c6a..55f6e25e59 100644 --- a/c2rust-transpile/src/translator/functions.rs +++ b/c2rust-transpile/src/translator/functions.rs @@ -8,7 +8,7 @@ use proc_macro2::{TokenStream, TokenTree}; impl<'c> Translation<'c> { pub fn convert_function( &self, - ctx: ExprContext, + ctx: ItemContext, decl_id: CDeclId, span: Span, is_global: bool, @@ -98,7 +98,7 @@ impl<'c> Translation<'c> { fn convert_function_inner( &self, - ctx: ExprContext, + ctx: ItemContext, span: Span, is_global: bool, is_inline: bool, @@ -207,7 +207,8 @@ impl<'c> Translation<'c> { let mut body_stmts = vec![]; for &(_, _, typ) in arguments { - body_stmts.append(&mut self.compute_variable_array_sizes(ctx, typ.ctype)?); + body_stmts + .append(&mut self.compute_variable_array_sizes(ctx.into(), typ.ctype)?); } let body_ids = match self.ast_context.index(body).kind { @@ -215,7 +216,7 @@ impl<'c> Translation<'c> { _ => panic!("function body expects to be a compound statement"), }; let mut converted_body = - self.convert_block_with_scope(ctx, name, body_ids, return_type, ret)?; + self.convert_block_with_scope(ctx.into(), name, body_ids, return_type, ret)?; strip_tail_return(&mut converted_body); // If `alloca` was used in the function body, include a variable to hold the @@ -341,7 +342,7 @@ impl<'c> Translation<'c> { fn convert_function_param( &self, - ctx: ExprContext, + ctx: ItemContext, typ: CQualTypeId, ) -> TranslationResult { if self.ast_context.is_va_list(typ.ctype) { @@ -350,7 +351,7 @@ impl<'c> Translation<'c> { return Ok(ConvertedFunctionParam { mutbl, ty }); } - self.convert_variable(ctx, None, typ) + self.convert_variable(ctx.into(), None, typ) .map(|ConvertedVariable { ty, mutbl, .. }| ConvertedFunctionParam { ty, mutbl }) } diff --git a/c2rust-transpile/src/translator/literals.rs b/c2rust-transpile/src/translator/literals.rs index 7483a053dd..134b4ef97a 100644 --- a/c2rust-transpile/src/translator/literals.rs +++ b/c2rust-transpile/src/translator/literals.rs @@ -37,7 +37,7 @@ impl<'c> Translation<'c> { expr = neg_expr(expr); } - Ok(if is_suffix || ctx.is_pattern { + Ok(if is_suffix || ctx.is_pattern() { expr } else { mk().cast_expr(expr, target_ty) @@ -76,7 +76,7 @@ impl<'c> Translation<'c> { // Always convert character literals as integers in patterns. // Character literals have problems with typing that need to be resolved. See // https://github.com/immunant/c2rust/issues/648 - !ctx.is_pattern + !ctx.is_pattern() }) { Some(c) => mk().lit_expr(c), None => { @@ -91,7 +91,7 @@ impl<'c> Translation<'c> { } }; - if !ctx.is_pattern { + if !ctx.is_pattern() { let type_rs = self.convert_type(ty.ctype)?; expr = mk().cast_expr(expr, type_rs); } @@ -108,7 +108,7 @@ impl<'c> Translation<'c> { }; let val = match self.ast_context.resolve_type(ty.ctype).kind { CTypeKind::LongDouble | CTypeKind::Float128 => { - if ctx.is_const { + if ctx.is_const() { return Err(format_translation_err!( None, "f128 cannot be used in constants because `f128::f128::new` is not `const`", @@ -130,7 +130,7 @@ impl<'c> Translation<'c> { } CLiteral::String(ref bytes, element_size) => { - if ctx.is_pattern { + if ctx.is_pattern() { return Err(TranslationError::generic( "CLiteral::String is not supported in patterns", )); @@ -140,7 +140,7 @@ impl<'c> Translation<'c> { let len = bytes_padded.len(); let val = mk().lit_expr(bytes_padded); - if ctx.needs_address && element_size == 1 { + if ctx.is_address_needed() && element_size == 1 { // Unlike in C, Rust string literals are already references by default. // So if the address needs to be taken, just make a bare literal and let // `convert_address_of_common` cast it to the appropriate type. @@ -159,7 +159,7 @@ impl<'c> Translation<'c> { // A transmute creates a temporary, which cannot have its address taken without // creating dangling pointers. Wrap it inside an inline `const` block, so that // it will be const-promoted to 'static. - if ctx.needs_address { + if ctx.is_address_needed() { self.use_feature("inline_const"); // An inline `const` block is its own safety context and does not inherit // the surrounding `unsafe`, so the transmute needs an explicit `unsafe` @@ -196,8 +196,8 @@ impl<'c> Translation<'c> { ) -> TranslationResult>> { // C compound literals are lvalues, but equivalent Rust expressions generally are not. // So if an address is needed, store it in an intermediate variable first. - if !ctx.needs_address || !ctx.is_used() || ctx.expanding_macro.is_some() { - return self.convert_expr(ctx.not_needs_address(), val, override_ty); + if !ctx.is_address_needed() || !ctx.is_used() || ctx.converting_macro().is_some() { + return self.convert_expr(ctx.not_address_needed(), val, override_ty); } let fresh_name = self @@ -208,11 +208,11 @@ impl<'c> Translation<'c> { // Translate the expression to be assigned to the fresh variable. // It will be assigned by value, so we don't need its address anymore. - let val = self.convert_expr(ctx.used().not_needs_address(), val, override_ty)?; + let val = self.convert_expr(ctx.used().not_address_needed(), val, override_ty)?; // If we are translating a static variable, // then the fresh variable should also be static. - if ctx.is_static { + if ctx.is_static() { Ok(val.wrap_unsafe().and_then(|val| { let item = mk().mutbl().static_item(&fresh_name, fresh_ty, val); let fresh_stmt = mk().item_stmt(item); diff --git a/c2rust-transpile/src/translator/macros.rs b/c2rust-transpile/src/translator/macros.rs index fe5c972660..7bbf4abeb3 100644 --- a/c2rust-transpile/src/translator/macros.rs +++ b/c2rust-transpile/src/translator/macros.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use syn::{Expr, MacroDelimiter}; use crate::c_ast::{CDeclId, CExprId, CQualTypeId, CTypeId, CTypeKind, MacroInvocationInfo}; +use crate::context::{ExprTreeContext, ItemContext}; use crate::diagnostics::{TranslationError, TranslationResult}; use crate::translator::{ConvertedDecl, ConvertedMacro, ExprContext, Translation}; use crate::with_stmts::WithStmts; @@ -14,7 +15,7 @@ use crate::TranslateMacros; impl<'c> Translation<'c> { pub fn convert_macro( &self, - ctx: ExprContext, + ctx: ItemContext, decl_id: CDeclId, span: Span, name: &str, @@ -60,7 +61,7 @@ impl<'c> Translation<'c> { /// this can fail. Or there could just be a feature we don't yet support. fn recreate_const_macro_from_expansions( &self, - ctx: ExprContext, + ctx: ItemContext, macro_id: CDeclId, ) -> TranslationResult<(Box, ConvertedMacro)> { struct ConvertedMacroExpr { @@ -68,7 +69,7 @@ impl<'c> Translation<'c> { ty: CTypeId, } - let ctx = ctx.const_().set_expanding_macro(macro_id); + let ctx = ExprTreeContext::from(ctx.set_converting_macro(macro_id)); let canonical = self.ast_context.macro_expansions[¯o_id] .iter() .try_fold::, _, _>(None, |canonical, info| { @@ -81,7 +82,7 @@ impl<'c> Translation<'c> { .kind .get_type() .ok_or_else(|| format_err!("Invalid expression type"))?; - let val = self.convert_expr(ctx.used(), expr_id, None)?; + let val = self.convert_expr(ctx.const_().used().into(), expr_id, None)?; let new = ConvertedMacroExpr { val, ty }; // Join ty and cur_ty to the smaller of the two types. If the @@ -171,7 +172,7 @@ impl<'c> Translation<'c> { // Find the first macro after the macro we're currently expanding, if any. let first_macro = macros - .splitn(2, |info| ctx.expanding_macro(&info.macro_id)) + .splitn(2, |info| ctx.converting_macro() == Some(info.macro_id)) .last() .unwrap() .first(); @@ -192,7 +193,7 @@ impl<'c> Translation<'c> { // We haven't tried to convert it yet. None => { - self.convert_decl(ctx.not_pattern(), info.macro_id)?; + self.convert_decl(ctx.as_item_context(), info.macro_id)?; let maybe_converted = self.converted_macros.borrow().get(&info.macro_id).cloned(); if let Some(Some(converted)) = maybe_converted { converted diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index d1fe3f362f..f605da209b 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -26,6 +26,7 @@ use syn::{ Stmt, Type, TypeTuple, UnOp, UseTree, Visibility, }; +use crate::context::{DecayRef, ExprContext, ExprTreeContext, FuncContext, ItemContext}; use crate::diagnostics::TranslationResult; use crate::rust_ast::comment_store::CommentStore; use crate::rust_ast::item_store::ItemStore; @@ -74,232 +75,12 @@ struct Import { ident_name: String, } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum DecayRef { - Yes, - Default, - No, -} - -impl DecayRef { - // Here we give intrinsic meaning to default to equate to yes/true - // when actually evaluated - pub fn is_yes(&self) -> bool { - match self { - DecayRef::Yes => true, - DecayRef::Default => true, - DecayRef::No => false, - } - } - - #[inline] - pub fn is_no(&self) -> bool { - !self.is_yes() - } - - pub fn set_default_to_no(&mut self) { - if *self == DecayRef::Default { - *self = DecayRef::No; - } - } -} - -impl From for DecayRef { - fn from(b: bool) -> Self { - match b { - true => DecayRef::Yes, - false => DecayRef::No, - } - } -} - #[derive(Debug, Copy, Clone)] pub enum ReplaceMode { None, Extern, } -/// Options that impact an expression and all of its subexpressions. -#[derive(Copy, Clone, Debug)] -pub struct ExprContext { - /// Whether the result value of the expression is used in a larger expression. - /// - /// When the result value is not used in a particular context, only the side effects of the - /// expression matter. The `stmts` field of `WithStmts` should hold any statements with side - /// effects, and the `val` field is expected to be discarded. It should not appear in the final - /// transpiler output, and may be an expression that panics when evaluated. - /// - /// - `.unused()` should be called for the top-level expression of an `ExprStmt`, the increment - /// expression of a `for` loop, the `lhs` of a comma operator expression, and other such cases. - /// - /// - `.used()` should be called if an expression is needed to evaluate the side effects of a - /// parent expression, such as the arguments of a function call (unless the function is known to - /// be pure), the operands of an assignment expression, the expression of a `return` statement, - /// etc. An expression that sets `.used()` for one of its subexpressions should handle the case - /// that its own context has `!is_used`, by moving its side effects into the `stmts` field; - /// the `convert_side_effects_expr` helper can be used for this purpose. - /// - /// - If an expression is pure (has no side effects), then it should inherit its `is_used` value - /// from its parent expression: if the parent expression is going to be discarded, then so are - /// all of its pure child expressions. - is_used: bool, - - /// In a Rust const context, for example in a static initializer or constant-like macro - /// translation. - is_const: bool, - - /// In a context where a pattern is expected, such as for `match` arms. - /// This restricts what kinds of expressions can be emitted. - is_pattern: bool, - - /// Evaluating a C global/static variable. - /// This is usually in a const context, but doesn't have to be, for example with initializers - /// that are executed by the `c2rust_run_static_initializers` function. - #[allow(dead_code)] - is_static: bool, - - decay_ref: DecayRef, - is_bitfield_write: bool, - - /// We will be referring to the expression by address. In this context we - /// can't index arrays because they may legally go out of bounds. We also - /// need to explicitly cast function references to fn() so we get their - /// address in function pointer literals. - needs_address: bool, - - expanding_macro: Option, -} - -impl ExprContext { - pub fn used(self) -> Self { - ExprContext { - is_used: true, - ..self - } - } - pub fn unused(self) -> Self { - ExprContext { - is_used: false, - ..self - } - } - pub fn is_used(&self) -> bool { - self.is_used - } - - pub fn decay_ref(self) -> Self { - ExprContext { - decay_ref: DecayRef::Yes, - ..self - } - } - pub fn const_(self) -> Self { - ExprContext { - is_const: true, - ..self - } - } - pub fn not_const(self) -> Self { - ExprContext { - is_const: false, - ..self - } - } - pub fn pattern(self) -> Self { - ExprContext { - is_pattern: true, - ..self - } - } - pub fn not_pattern(self) -> Self { - ExprContext { - is_pattern: false, - ..self - } - } - pub fn not_static(self) -> Self { - ExprContext { - is_static: false, - ..self - } - } - pub fn static_(self) -> Self { - ExprContext { - is_static: true, - ..self - } - } - - pub fn bitfield_write(self) -> Self { - ExprContext { - is_bitfield_write: true, - ..self - } - } - - pub fn needs_address(self) -> Self { - ExprContext { - needs_address: true, - ..self - } - } - - pub fn not_needs_address(self) -> Self { - ExprContext { - needs_address: false, - ..self - } - } - - /// Are we expanding the given macro in the current context? - pub fn expanding_macro(&self, mac: &CDeclId) -> bool { - match self.expanding_macro { - Some(expanding) => expanding == *mac, - None => false, - } - } - pub fn set_expanding_macro(self, mac: CDeclId) -> Self { - ExprContext { - expanding_macro: Some(mac), - ..self - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct FuncContext { - /// The name of the function we're currently translating - name: Option, - /// The name we give to the Rust function argument corresponding - /// to the ellipsis in variadic C functions. - va_list_arg_name: Option, - /// The va_list decls that are either `va_start`ed or `va_copy`ed. - va_list_decl_ids: Option>, - /// The name we give to the Rust variable holding all allocations made with `alloca`. - alloca_allocations_name: Option, -} - -impl FuncContext { - pub fn new() -> Self { - Self::default() - } - - pub fn enter_new(&mut self, fn_name: &str) { - *self = Self { - name: Some(fn_name.to_string()), - ..Default::default() - }; - } - - pub fn get_name(&self) -> &str { - self.name.as_ref().unwrap() - } - - pub fn get_va_list_arg_name(&self) -> &str { - self.va_list_arg_name.as_ref().unwrap() - } -} - struct ConvertedMacro { ty: CTypeId, } @@ -895,16 +676,7 @@ pub fn translate( preprocessed_definitions: &IndexMap, ) -> (String, Option, PragmaVec, CrateSet) { let mut t = Translation::new(ast_context, tcfg, main_file); - let ctx = ExprContext { - is_used: false, - is_const: false, - is_pattern: false, - is_static: false, - decay_ref: DecayRef::Default, - is_bitfield_write: false, - needs_address: false, - expanding_macro: None, - }; + let ctx = ItemContext::default(); { t.locate_comments(); @@ -1998,24 +1770,13 @@ impl<'c> Translation<'c> { false } - fn add_static_initializer_to_section( - &self, - ctx: ExprContext, - name: &str, - typ: CQualTypeId, - init: &mut Box, - ) -> TranslationResult<()> { - let mut default_init = self.implicit_default_expr(ctx.used(), typ.ctype)?.to_expr(); - - std::mem::swap(init, &mut default_init); - + fn add_static_initializer_to_section(&self, name: &str, init: WithStmts>) { + let init = init.to_expr(); let root_lhs_expr = mk().path_expr(vec![name]); - let assign_expr = mk().assign_expr(root_lhs_expr, default_init); + let assign_expr = mk().assign_expr(root_lhs_expr, init); let stmt = mk().expr_stmt(assign_expr); self.sectioned_static_initializers.borrow_mut().push(stmt); - - Ok(()) } fn generate_global_static_init(&mut self) -> (Box, Box) { @@ -2073,7 +1834,7 @@ impl<'c> Translation<'c> { (fn_item, static_item) } - fn convert_decl(&self, ctx: ExprContext, decl_id: CDeclId) -> TranslationResult { + fn convert_decl(&self, ctx: ItemContext, decl_id: CDeclId) -> TranslationResult { let decl = self .ast_context .get_decl(&decl_id) @@ -2213,8 +1974,11 @@ impl<'c> Translation<'c> { .borrow() .get(&decl_id) .expect("Variables should already be renamed"); - let ConvertedVariable { ty, mutbl, init: _ } = - self.convert_variable(ctx.static_().const_(), None, typ)?; + let ConvertedVariable { ty, mutbl, init: _ } = self.convert_variable( + ExprTreeContext::from(ctx).static_().const_(), + None, + typ, + )?; let mut extern_item = mk_linkage(true, &new_name, ident, self.tcfg.edition) .span(span) .set_mutbl(mutbl); @@ -2289,20 +2053,20 @@ impl<'c> Translation<'c> { }; } - let ctx = ctx.static_(); - // Collect problematic static initializers and offload them to sections for the linker // to initialize for us if self.static_initializer_is_uncompilable(initializer, typ) { // Note: We don't pass `is_const` through here. Extracted initializers are run // outside of the static initializer, in a non-const context. - let ctx = ctx.not_const(); + let ctx = ExprTreeContext::from(ctx).static_(); let ConvertedVariable { ty, mutbl: _, init } = self.convert_variable(ctx, initializer, typ)?; + self.add_static_initializer_to_section(new_name, init?); - let mut init = init?.to_expr(); - + let default_init = self + .implicit_default_expr(ctx.used().into(), typ.ctype)? + .to_expr(); let comment = String::from("// Initialized in c2rust_run_static_initializers"); let comment_pos = if span.is_dummy() { None @@ -2319,12 +2083,11 @@ impl<'c> Translation<'c> { ) .map(pos_to_span) .unwrap_or(span); + let static_item = static_def + .span(span) + .static_item(new_name, ty, default_init); - self.add_static_initializer_to_section(ctx, new_name, typ, &mut init)?; - - Ok(ConvertedDecl::Item( - static_def.span(span).static_item(new_name, ty, init), - )) + Ok(ConvertedDecl::Item(static_item)) } else { let items = self.convert_compilable_static( ctx, @@ -2471,7 +2234,7 @@ impl<'c> Translation<'c> { fn convert_block_with_scope( &self, - ctx: ExprContext, + ctx: ExprTreeContext, name: &str, body_ids: &[CStmtId], ret_ty: Option, @@ -2704,14 +2467,17 @@ impl<'c> Translation<'c> { #[allow(clippy::vec_box)] fn convert_compilable_static( &self, - ctx: ExprContext, + ctx: ItemContext, static_def: Builder, name: &str, initializer: Option, typ: CQualTypeId, ) -> TranslationResult>> { - let ConvertedVariable { ty, mutbl: _, init } = - self.convert_variable(ctx.const_(), initializer, typ)?; + let ConvertedVariable { ty, mutbl: _, init } = self.convert_variable( + ExprTreeContext::from(ctx).static_().const_(), + initializer, + typ, + )?; let mut init = init?; let mut items = init .stmts_to_items() @@ -2726,7 +2492,7 @@ impl<'c> Translation<'c> { pub fn convert_decl_stmt_info( &self, - ctx: ExprContext, + ctx: ExprTreeContext, decl_id: CDeclId, ) -> TranslationResult { if let CDeclKind::Variable { @@ -2753,7 +2519,11 @@ impl<'c> Translation<'c> { })?; let ConvertedVariable { ty, mutbl: _, init } = self.convert_variable(ctx, initializer, typ)?; - let default_init = self.implicit_default_expr(ctx.used(), typ.ctype)?.to_expr(); + self.add_static_initializer_to_section(&ident2, init?.set_unsafe()); + + let default_init = self + .implicit_default_expr(ctx.used().into(), typ.ctype)? + .to_expr(); let comment = String::from("// Initialized in c2rust_run_static_initializers"); let span = self .comment_store @@ -2765,9 +2535,6 @@ impl<'c> Translation<'c> { .span(span) .mutbl() .static_item(&ident2, ty, default_init); - let mut init = init?.set_unsafe().to_expr(); - - self.add_static_initializer_to_section(ctx, &ident2, typ, &mut init)?; self.items.borrow_mut()[&self.main_file].add_item(static_item); return Ok(cfg::DeclStmtInfo::empty()); @@ -2787,7 +2554,7 @@ impl<'c> Translation<'c> { .get_span(SomeId::Decl(decl_id)) .unwrap_or_else(Span::call_site); let items = self.convert_compilable_static( - ctx.static_(), + ctx.as_item_context(), mk().span(span).mutbl(), &ident2, initializer, @@ -2862,7 +2629,7 @@ impl<'c> Translation<'c> { // A `const` context is not "already unsafe" the way code within a `fn` is // (since we translate all `fn`s as `unsafe`). Therefore, in `const` contexts, // expose any underlying unsafety in the initializer with an `unsafe` block. - let init = if ctx.is_const { + let init = if ctx.is_const() { init.wrap_unsafe() .to_pure_expr() .expect("init should not have any statements") @@ -2870,8 +2637,8 @@ impl<'c> Translation<'c> { init.into_value() }; - let zeroed = self.implicit_default_expr(ctx.used(), typ.ctype)?; - let zeroed = if ctx.is_const { + let zeroed = self.implicit_default_expr(ctx.used().into(), typ.ctype)?; + let zeroed = if ctx.is_const() { zeroed.wrap_unsafe().to_pure_expr() } else { zeroed.to_pure_expr() @@ -2976,7 +2743,7 @@ impl<'c> Translation<'c> { } use ConvertedDecl::*; - let items = match self.convert_decl(ctx, decl_id)? { + let items = match self.convert_decl(ctx.as_item_context(), decl_id)? { Item(item) => vec![item], ForeignItem(item) => { vec![mk() @@ -3093,13 +2860,13 @@ impl<'c> Translation<'c> { fn convert_variable( &self, - ctx: ExprContext, + ctx: ExprTreeContext, initializer: Option, typ: CQualTypeId, ) -> TranslationResult { let init = match initializer { - Some(x) => self.convert_expr(ctx.used(), x, Some(typ)), - None => self.implicit_default_expr(ctx.used(), typ.ctype), + Some(x) => self.convert_expr(ctx.used().into(), x, Some(typ)), + None => self.implicit_default_expr(ctx.used().into(), typ.ctype), }; // Variable declarations for variable-length arrays use the type of a pointer to the @@ -3240,7 +3007,7 @@ impl<'c> Translation<'c> { /// the given type. pub fn compute_variable_array_sizes( &self, - ctx: ExprContext, + ctx: ExprTreeContext, mut type_id: CTypeId, ) -> TranslationResult> { let mut stmts = vec![]; @@ -3254,7 +3021,7 @@ impl<'c> Translation<'c> { // Convert this expression let expr = self - .convert_expr(ctx.used(), expr_id, None)? + .convert_expr(ctx.used().into(), expr_id, None)? .and_then(|expr| { let name = self .renamer @@ -3295,7 +3062,7 @@ impl<'c> Translation<'c> { let elts = self.compute_size_of_type(ctx, expected_type_id, result_type_id, elts)?; return elts.and_then_try(|lhs| { - let len = self.convert_expr(ctx.not_static(), len, expected_type_id)?; + let len = self.convert_expr(ctx, len, expected_type_id)?; Ok(len.map(|len| { let rhs = cast_int(len, "usize", true); mk().binary_expr(BinOp::Mul(Default::default()), lhs, rhs) @@ -3446,7 +3213,7 @@ impl<'c> Translation<'c> { } } - if ctx.is_pattern + if ctx.is_pattern() && !matches!( expr_kind, CExprKind::Paren(..) @@ -3552,7 +3319,7 @@ impl<'c> Translation<'c> { ConstantExpr(ty, child, value) => { if let Some(constant) = value { - if ctx.is_pattern { + if ctx.is_pattern() { self.convert_expr(ctx, child, override_ty) } else { self.convert_constant(constant).map(WithStmts::new_val) @@ -3866,7 +3633,7 @@ impl<'c> Translation<'c> { .get_decl(&decl_id) .ok_or_else(|| format_err!("Missing declref {:?}", decl_id))? .kind; - if ctx.expanding_macro.is_some() { + if ctx.converting_macro().is_some() { // TODO Determining which declarations have been declared within the scope of the const macro expr // vs. which are out-of-scope of the const macro is non-trivial, // so for now, we don't allow const macros referencing any declarations. @@ -3896,7 +3663,7 @@ impl<'c> Translation<'c> { ); } - if ctx.is_pattern { + if ctx.is_pattern() { return Err(TranslationError::generic( "non-EnumConstant DeclRefs are not supported in patterns", )); @@ -3924,7 +3691,7 @@ impl<'c> Translation<'c> { CDeclKind::Function { parameters, .. } => { // If we are referring to a function and need its address, we // need to cast it to fn() to ensure that it has a real address. - if ctx.needs_address { + if ctx.is_address_needed() { let ty = self.convert_type(result_type_id.ctype)?; let actual_ty = self .type_converter @@ -3972,7 +3739,7 @@ impl<'c> Translation<'c> { // but this requirement was removed in later versions of the // `raw_ref_op` feature. if (*has_static_duration || *has_thread_duration) - && (self.tcfg.edition < Edition2024 || !ctx.needs_address) + && (self.tcfg.edition < Edition2024 || !ctx.is_address_needed()) { set_unsafe = true; } @@ -4071,7 +3838,7 @@ impl<'c> Translation<'c> { CStmtKind::Expr(expr_id) => { let ret = cfg::ImplicitReturnType::StmtExpr(ctx, expr_id, lbl.clone()); self.convert_block_with_scope( - ctx, + ctx.as_tree_context(), &name, &substmt_ids[0..(n - 1)], None, @@ -4080,7 +3847,7 @@ impl<'c> Translation<'c> { } _ => self.convert_block_with_scope( - ctx, + ctx.as_tree_context(), &name, substmt_ids, None, @@ -4215,7 +3982,7 @@ impl<'c> Translation<'c> { CastKind::ArrayToPointerDecay | CastKind::FunctionToPointerDecay | CastKind::BuiltinFnToFnPtr => { - ctx.needs_address = true; + ctx = ctx.address_needed(); } _ => {} } @@ -4223,7 +3990,7 @@ impl<'c> Translation<'c> { let mut val = self.convert_expr(ctx, expr, None)?; if is_explicit { - let stmts = self.compute_variable_array_sizes(ctx, ty.ctype)?; + let stmts = self.compute_variable_array_sizes(ctx.as_tree_context(), ty.ctype)?; val = val.prepend_stmts(stmts); } @@ -4260,7 +4027,7 @@ impl<'c> Translation<'c> { let mut expr_kind = &self.ast_context.index_unwrap_parens(expr_id).kind; // In patterns, skip over `ConstantExpr`s. - if ctx.is_pattern { + if ctx.is_pattern() { if let &CExprKind::ConstantExpr(_, expr_id, _) = expr_kind { expr_kind = &self.ast_context.index_unwrap_parens(expr_id).kind; } @@ -4388,7 +4155,7 @@ impl<'c> Translation<'c> { return Ok(val); } - if ctx.is_pattern && !matches!(kind, CastKind::ToVoid | CastKind::IntegralCast) { + if ctx.is_pattern() && !matches!(kind, CastKind::ToVoid | CastKind::IntegralCast) { return Err(TranslationError::generic( "cast kind is not supported in patterns", )); @@ -4422,7 +4189,7 @@ impl<'c> Translation<'c> { | CastKind::BooleanToSignedIntegral => { let target_ty = self.convert_type(target_cty.ctype)?; - if ctx.is_pattern && !target_ty_kind.is_enum() { + if ctx.is_pattern() && !target_ty_kind.is_enum() { return Err(TranslationError::generic( "integral casts to non-enums are not supported in patterns", )); @@ -4436,7 +4203,7 @@ impl<'c> Translation<'c> { // just be a no-op. Ok(val) } else { - if ctx.is_const { + if ctx.is_const() { return Err(format_translation_err!( None, "f128 cannot be used in constants because \ diff --git a/c2rust-transpile/src/translator/pointers.rs b/c2rust-transpile/src/translator/pointers.rs index 08c5e17ae0..028d2fffb7 100644 --- a/c2rust-transpile/src/translator/pointers.rs +++ b/c2rust-transpile/src/translator/pointers.rs @@ -33,7 +33,7 @@ impl<'c> Translation<'c> { // Array subscript functions as a deref too. &CExprKind::ArraySubscript(_, lhs, rhs, _) => { return self.convert_array_subscript( - ctx.needs_address(), + ctx.address_needed(), Some(cqual_type), lhs, rhs, @@ -48,7 +48,7 @@ impl<'c> Translation<'c> { _ => (), } - let val = self.convert_expr(ctx.needs_address(), arg, None)?; + let val = self.convert_expr(ctx.address_needed(), arg, None)?; // & becomes a no-op when applied to a function. if self.ast_context.is_function_pointer(cqual_type.ctype) { @@ -110,7 +110,7 @@ impl<'c> Translation<'c> { let mut needs_cast = false; let mut ref_cast_pointee_ty = None; - let mutbl = if ctx.is_const && !pointee_cty.qualifiers.is_const { + let mutbl = if ctx.is_const() && !pointee_cty.qualifiers.is_const { // const contexts aren't able to use &mut, so we work around that // by using & and an extra cast through & to *const to *mut // TODO: Rust 1.83: Allowed, so this can be removed. @@ -202,7 +202,7 @@ impl<'c> Translation<'c> { return self.convert_expr(ctx, arg, None); } - self.convert_expr(ctx.not_needs_address(), arg, None)? + self.convert_expr(ctx.not_address_needed(), arg, None)? .try_map(|val: Box| { if let CTypeKind::Function(..) = self.ast_context.resolve_type(cqual_type.ctype).kind @@ -244,7 +244,7 @@ impl<'c> Translation<'c> { )); } - let simple_index_array = if ctx.needs_address { + let simple_index_array = if ctx.is_address_needed() { // We can't necessarily index into an array if we're using // that element to compute an address. None @@ -292,13 +292,13 @@ impl<'c> Translation<'c> { ref other => panic!("Unexpected array type {:?}", other), }; - let array_rs = self.convert_expr(ctx.not_needs_address(), array_id, None)?; + let array_rs = self.convert_expr(ctx.not_address_needed(), array_id, None)?; // Don't dereference the offset if we're still within the variable portion let val = if let Some(elt_type_id) = var_elt_type_id { let target_type_id = self.ast_context.type_for_kind(&CTypeKind::SSize); let offset_rs = self.convert_expr_with_cast( - ctx.not_needs_address(), + ctx.not_address_needed(), CQualTypeId::new(target_type_id), offset_id, )?; @@ -308,7 +308,7 @@ impl<'c> Translation<'c> { } else { let target_type_id = self.ast_context.type_for_kind(&CTypeKind::Size); let offset_rs = self.convert_expr_with_cast( - ctx.not_needs_address(), + ctx.not_address_needed(), CQualTypeId::new(target_type_id), offset_id, )?; @@ -333,10 +333,10 @@ impl<'c> Translation<'c> { // LHS must be ref decayed for the offset method call's self param let pointer_rs = - self.convert_expr(ctx.not_needs_address().decay_ref(), pointer_id, None)?; + self.convert_expr(ctx.not_address_needed().decay_ref(), pointer_id, None)?; let target_type_id = self.ast_context.type_for_kind(&CTypeKind::SSize); let offset_rs = self.convert_expr_with_cast( - ctx.not_needs_address(), + ctx.not_address_needed(), CQualTypeId::new(target_type_id), offset_id, )?; @@ -526,7 +526,7 @@ impl<'c> Translation<'c> { let target_ty = self.convert_type(target_cty.ctype)?; if self.ast_context.is_function_pointer(target_cty.ctype) { - if ctx.is_const { + if ctx.is_const() { return Err(format_translation_err!( None, "cannot transmute integers to Option in `const` context", @@ -544,7 +544,7 @@ impl<'c> Translation<'c> { } // Rust 1.90: `const_strict_provenance` feature added // Rust 1.91: stabilized - else if ctx.is_const && self.tcfg.edition < RustEdition::Edition2024 { + else if ctx.is_const() && self.tcfg.edition < RustEdition::Edition2024 { if source_ty_kind.is_bool() { self.use_crate(ExternCrate::Libc); Ok(val.map(|mut val| { @@ -632,7 +632,7 @@ impl<'c> Translation<'c> { target_cty: CQualTypeId, val: WithStmts>, ) -> TranslationResult>> { - if ctx.is_const { + if ctx.is_const() { return Err(format_translation_err!( None, "cannot observe pointer values in `const` context", @@ -699,7 +699,7 @@ impl<'c> Translation<'c> { Ok(expr_rs.map(|expr_rs| mk().method_call_expr(expr_rs, method, vec![]))) } else { // TODO: `pointer::is_null` becomes stably const in Rust 1.84. - if ctx.is_const { + if ctx.is_const() { return Err(format_translation_err!( None, "cannot check nullity of pointer in `const` context", diff --git a/c2rust-transpile/src/translator/simd.rs b/c2rust-transpile/src/translator/simd.rs index d6349750be..6c9af9f53b 100644 --- a/c2rust-transpile/src/translator/simd.rs +++ b/c2rust-transpile/src/translator/simd.rs @@ -262,7 +262,7 @@ impl<'c> Translation<'c> { } }; - if ctx.is_const { + if ctx.is_const() { let zero_expr = mk().lit_expr(mk().int_lit(0, "u8")); let n_bytes_expr = mk().lit_expr(mk().int_lit(bytes, "")); let expr = mk().repeat_expr(zero_expr, n_bytes_expr); @@ -295,7 +295,7 @@ impl<'c> Translation<'c> { // When used in a const context, we cannot call the standard functions since they // are not const and so we are forced to transmute - let call = if ctx.is_const { + let call = if ctx.is_const() { is_unsafe = true; let tuple = mk().tuple_expr(params); transmute_expr(mk().infer_ty(), mk().infer_ty(), tuple) diff --git a/c2rust-transpile/src/translator/structs_unions.rs b/c2rust-transpile/src/translator/structs_unions.rs index a92444787a..8fe4585570 100644 --- a/c2rust-transpile/src/translator/structs_unions.rs +++ b/c2rust-transpile/src/translator/structs_unions.rs @@ -1101,7 +1101,7 @@ impl<'a> Translation<'a> { // are generated when we are expecting to require a write, which will need // to make a method call with some input which we do not yet have access // to and will have to be handled elsewhere, IE `bf.set_a(1)` - if !ctx.is_bitfield_write { + if !ctx.is_bitfield_write() { // Cases A and B above val = val.map(|v| mk().method_call_expr(v, field_name, vec![])); }