diff --git a/c2rust-transpile/src/translator/functions.rs b/c2rust-transpile/src/translator/functions.rs index 01593fb623..e423e49610 100644 --- a/c2rust-transpile/src/translator/functions.rs +++ b/c2rust-transpile/src/translator/functions.rs @@ -114,6 +114,10 @@ impl<'c> Translation<'c> { ) -> TranslationResult { self.function_context.borrow_mut().enter_new(name); + if let Some(body) = body { + self.collect_function_statics_to_hoist(body); + } + self.with_scope(|| { let mut args: Vec = vec![]; diff --git a/c2rust-transpile/src/translator/mod.rs b/c2rust-transpile/src/translator/mod.rs index 16fe7a87cd..177087ce79 100644 --- a/c2rust-transpile/src/translator/mod.rs +++ b/c2rust-transpile/src/translator/mod.rs @@ -292,6 +292,9 @@ pub struct Translation<'c> { // Accumulated outputs pub features: RefCell>, sectioned_static_initializers: RefCell>, + /// Function-local statics that need hoisting to module scope because a + /// sectioned static's initializer references them. + function_statics_to_hoist: RefCell>, extern_crates: RefCell, // Translation state and utilities @@ -1679,6 +1682,7 @@ impl<'c> Translation<'c> { comment_store: RefCell::new(CommentStore::new()), spans: HashMap::new(), sectioned_static_initializers: RefCell::new(Vec::new()), + function_statics_to_hoist: RefCell::new(IndexSet::new()), items: RefCell::new(items), mod_names: RefCell::new(IndexMap::new()), main_file, @@ -2300,16 +2304,13 @@ impl<'c> Translation<'c> { static_def.span(span).static_item(new_name, ty, init), )) } else { - let ConvertedVariable { ty, mutbl: _, init } = - self.convert_variable(ctx.const_(), initializer, typ)?; - let mut init = init?; - let mut items = init.stmts_to_items().ok_or_else(|| { - format_err!("Expected only item statements in static initializer") - })?; - let init = init.wrap_unsafe().to_pure_expr().unwrap(); - let item = static_def.span(span).static_item(new_name, ty, init); - items.push(item); - + let items = self.convert_compilable_static( + ctx, + static_def.span(span), + new_name, + initializer, + typ, + )?; Ok(ConvertedDecl::Items(items)) } } @@ -2557,6 +2558,89 @@ impl<'c> Translation<'c> { false } + /// Find function-local statics in `body` that must be hoisted to module + /// scope: those referenced, transitively, by the initializer of a sectioned + /// static. `c2rust_run_static_initializers` cannot see function-local names, + /// so everything a sectioned initializer references must be hoisted with it. + fn collect_function_statics_to_hoist(&self, body: CStmtId) { + // Entries are only consulted while converting the current function's + // body, so drop any left over from the previous function. + self.function_statics_to_hoist.borrow_mut().clear(); + + // Map each local static (in any nested block) to its initializer; + // seed the worklist with the ones that will get sectioned. + let mut local_statics: IndexMap> = IndexMap::new(); + let mut worklist: Vec = Vec::new(); + for id in DFExpr::new(&self.ast_context, body.into()) { + if let SomeId::Decl(decl_id) = id { + if let CDeclKind::Variable { + has_static_duration: true, + is_externally_visible: false, + is_defn: true, + initializer, + typ, + .. + } = self.ast_context[decl_id].kind + { + local_statics.insert(decl_id, initializer); + if self.static_initializer_is_uncompilable(initializer, typ) { + worklist.push(decl_id); + } + } + } + } + if worklist.is_empty() { + return; + } + // Transitively collect local statics referenced by the seeds' + // initializers. Hoisting too much is harmless, so unlike + // `has_decl_reference` we don't bother pruning sizeof/typeof subtrees. + let mut visited: IndexSet = worklist.iter().copied().collect(); + while let Some(decl_id) = worklist.pop() { + let init = match local_statics.get(&decl_id) { + Some(&Some(init)) => init, + _ => continue, + }; + for i in DFExpr::new(&self.ast_context, init.into()) { + if let SomeId::Expr(e) = i { + if let CExprKind::DeclRef(_, target, _) = + self.ast_context.index_unwrap_parens(e).kind + { + if local_statics.contains_key(&target) && visited.insert(target) { + self.function_statics_to_hoist.borrow_mut().insert(target); + worklist.push(target); + } + } + } + } + } + } + + /// Convert a static with a compilable initializer into its Rust items: + /// any auxiliary items produced while converting the initializer, followed + /// by the static item itself, built from `static_def`. + fn convert_compilable_static( + &self, + ctx: ExprContext, + static_def: Builder, + name: &str, + initializer: Option, + typ: CQualTypeId, + ) -> TranslationResult>> { + let ConvertedVariable { ty, mutbl: _, init } = + self.convert_variable(ctx.const_(), initializer, typ)?; + let mut init = init?; + let mut items = init + .stmts_to_items() + .ok_or_else(|| format_err!("Expected only item statements in static initializer"))?; + let init = init + .wrap_unsafe() + .to_pure_expr() + .expect("no statements remain after stmts_to_items"); + items.push(static_def.static_item(name, ty, init)); + Ok(items) + } + pub fn convert_decl_stmt_info( &self, ctx: ExprContext, @@ -2603,6 +2687,36 @@ impl<'c> Translation<'c> { 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()); + } else if self.function_statics_to_hoist.borrow().contains(&decl_id) { + // A sectioned static's initializer references this static, so + // hoist it to module scope too. Its own initializer is + // compilable and can be emitted as-is. + let ident2 = self + .renamer + .borrow_mut() + .insert_root(decl_id, ident, Namespaces::values()) + .ok_or_else(|| { + TranslationError::generic("Unable to rename hoisted function scoped static") + })?; + + let span = self + .get_span(SomeId::Decl(decl_id)) + .unwrap_or_else(Span::call_site); + let items = self.convert_compilable_static( + ctx.static_(), + mk().span(span).mutbl(), + &ident2, + initializer, + typ, + )?; + + let mut item_stores = self.items.borrow_mut(); + let store = &mut item_stores[&self.main_file]; + for item in items { + store.add_item(item); + } + return Ok(cfg::DeclStmtInfo::empty()); } }; diff --git a/tests/unit/statics/src/sections.c b/tests/unit/statics/src/sections.c index 5f29c476ba..5071d0af13 100644 --- a/tests/unit/statics/src/sections.c +++ b/tests/unit/statics/src/sections.c @@ -52,11 +52,14 @@ static stat3 selfref = { NULL, &selfref.first }; void (*int_to_fn_ptr)(void) = -1; void (*int_to_fn_ptr2)(int, ...) = -1; void_fn_ptr int_to_fn_ptr3 = -1; +// Add a sectioned static here to test how the transpiler handles renames +uintptr_t sectioned_ptr = NULL; size_t fn_scoped_static_init(void) { extern size_t fn_scoped_extern; static size_t sectioned_scoped_init = §ion_me; static unsigned not_sectioned = 1; + static uintptr_t sectioned_ptr = §ion_me; section_me -= fn_scoped_extern; return sectioned_scoped_init; } @@ -74,3 +77,28 @@ static size_t sectioned_array[] = { void use_sectioned_array() { size_t f = sectioned_array[0]; } + +// Function-scoped statics referenced by a sectioned static's initializer +// must be hoisted along with it (https://github.com/immunant/c2rust/issues/1981): +typedef struct { + const int *p; + unsigned len; +} slice_ref; + +unsigned fn_scoped_static_hoist(void) { + static const int local_static_arr[] = { 1, 2, 3 }; + /* unsigned division => sectioned */ + static const slice_ref local_static_s[] = { + { local_static_arr, sizeof(local_static_arr) / sizeof(local_static_arr[0]) }, + }; + static uintptr_t sectioned_ptr = §ion_me3; + return local_static_s[0].p[0] + local_static_s[0].len; +} + +int fn_scoped_static_chain(void) { + static int chain_a[] = { 5, 6, 7 }; + static int *chain_b = chain_a; + /* pointer arithmetic => sectioned; hoists chain_b, then chain_a */ + static int **chain_c = &chain_b + 0; + return **chain_c + chain_a[1]; +} diff --git a/tests/unit/statics/src/test_sections.rs b/tests/unit/statics/src/test_sections.rs index f4516fe32f..5fde3fdeb6 100644 --- a/tests/unit/statics/src/test_sections.rs +++ b/tests/unit/statics/src/test_sections.rs @@ -30,6 +30,16 @@ pub fn test_sectioned_statics() { } } +#[test] +pub fn test_fn_scoped_static_hoisting() { + unsafe { + // Fails to compile unless the function-scoped statics referenced by + // these sectioned statics are hoisted to module scope + assert_eq!(rust_fn_scoped_static_hoist(), 4); + assert_eq!(rust_fn_scoped_static_chain(), 11); + } +} + #[test] pub fn test_sectioned_used_static() { if cfg!(not(target_os = "macos")) {