Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[profile.default.overrides]]
filter = "package(c2rust-transpile) and test(issue_1821_transpiles_quickly)"
slow-timeout = { period = "30s", terminate-after = 1 }
15 changes: 15 additions & 0 deletions c2rust-transpile/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,21 @@ impl<S1, S2> BasicBlock<StructureLabel<S1>, S2> {
})
.collect()
}

/// Check whether this block has a `GoTo` to `target`.
fn has_successor(&self, target: &Label) -> bool {
let is_target = |label: &StructureLabel<S1>| match label {
StructureLabel::GoTo(successor) => successor == target,
_ => false,
};

match &self.terminator {
End => false,
Jump(label) => is_target(label),
Branch(_, then_label, else_label) => is_target(then_label) || is_target(else_label),
Switch { cases, .. } => cases.iter().any(|(_, label)| is_target(label)),
}
}
}

/// Represents the control flow choices one can make when at the end of a `BasicBlock`.
Expand Down
26 changes: 18 additions & 8 deletions c2rust-transpile/src/cfg/relooper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,6 @@ impl RelooperState {
return;
}

let local_successors = blocks
.iter()
.map(|(lbl, bb)| (lbl.clone(), bb.successors()))
.collect();

// Map from each label to the set of labels that reach it. A label does not
// count as reaching itself unless there is an explicit back edge to that label.
//
Expand All @@ -241,13 +236,26 @@ impl RelooperState {
// blocks. Global reachability won't work here because when we're inside a loop
// body we don't want to consider back edges, which we strip before processing
// the loop body.
let strict_reachable_from = flip_edges(transitive_closure(&local_successors));
//
// TODO: If possible we should avoid recomputing this every time `relooper`
// recurses. I think the reachability information only meaningfully changes when
// we strip a back edge, so in theory we should only need to recompute this after
// processing a loop.
let strict_reachable_from = |blocks: &BasicBlocks| {
let local_successors = blocks
.iter()
.map(|(lbl, bb)| (lbl.clone(), bb.successors()))
.collect();
flip_edges(transitive_closure(&local_successors))
};

// Handle our simple case of only 1 entry. If there's no back edge to the entry
// we generate a `Simple` structure, otherwise we make a loop.
if entries.len() == 1 {
let entry = entries.first().unwrap();
if !strict_reachable_from.contains_key(entry) {
let has_back_edge = blocks.values().any(|block| block.has_successor(entry));

if !has_back_edge {
let bb = blocks
.swap_remove(entry)
.expect("Entry not present in current blocks");
Expand Down Expand Up @@ -295,11 +303,13 @@ impl RelooperState {

self.relooper(new_entries, blocks, result);
} else {
self.make_loop(&strict_reachable_from, blocks, entries, result);
self.make_loop(&strict_reachable_from(&blocks), blocks, entries, result);
}
return;
}

let strict_reachable_from = strict_reachable_from(&blocks);

// Sanity check that we have entries that are in our current set of blocks. We
// may have entries that aren't present in our current blocks when we're inside
// the branch of a `Multiple`, but there must always be at least one present
Expand Down
60 changes: 60 additions & 0 deletions c2rust-transpile/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::path::PathBuf;

use c2rust_rust_tools::RustEdition;
use c2rust_transpile::{ReplaceMode, TranspilerConfig};

pub fn config(edition: RustEdition) -> TranspilerConfig {
TranspilerConfig {
dump_untyped_context: false,
dump_typed_context: false,
pretty_typed_context: false,
dump_function_cfgs: false,
json_function_cfgs: false,
dump_cfg_liveness: false,
dump_structures: false,
verbose: false,
debug_ast_exporter: false,
emit_c_decl_map: true, // Let snapshot tests validate the C declaration map.
incremental_relooper: true,
fail_on_multiple: false,
filter: None,
debug_relooper_labels: false,
cross_checks: false,
cross_check_backend: Default::default(),
cross_check_configs: Default::default(),
prefix_function_names: None,
translate_asm: true,
use_c_loop_info: true,
use_c_multiple_info: true,
simplify_structures: true,
panic_on_translator_failure: false,
emit_modules: false,
fail_on_error: true,
replace_unsupported_decls: ReplaceMode::Extern,
translate_valist: true,
overwrite_existing: true,
reduce_type_annotations: false,
reorganize_definitions: false,
enabled_warnings: Default::default(),
emit_no_std: false,
output_dir: None,
translate_const_macros: Default::default(),
translate_fn_macros: Default::default(),
disable_rustfmt: false,
disable_refactoring: false,
preserve_unused_functions: false,
log_level: log::LevelFilter::Warn,
edition,
deny_unsafe_op_in_unsafe_fn: false,
postprocess: false,
emit_build_files: false,
binaries: Vec::new(),
thin_binaries: false,
c2rust_dir: Some(
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.to_path_buf(),
),
}
}
42 changes: 42 additions & 0 deletions c2rust-transpile/tests/relooper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
mod common;

use c2rust_rust_tools::RustEdition::Edition2021;

use common::config;

fn run_transpiler() {
let fixture =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/relooper/issue_1821.c");
let output_dir = c2rust_transpile::TempDir::new().unwrap();
let (_compile_commands_dir, compile_commands) =
c2rust_transpile::create_temp_compile_commands(&[fixture]);

let mut cfg = config(Edition2021);
cfg.disable_rustfmt = true;
cfg.emit_c_decl_map = false;
cfg.output_dir = Some(output_dir.path().to_owned());
c2rust_transpile::transpile(cfg, &compile_commands, &[]);
Comment thread
randomPoison marked this conversation as resolved.
Resolved

let output = std::fs::read(output_dir.path().join("src/issue_1821.rs"))
.expect("transpiler did not produce issue_1821.rs");
assert!(
!output.is_empty(),
"transpiler produced an empty output file"
);
}

#[test]
fn issue_1821_transpiles_quickly() {
// HACK: Use a thread with a larger stack size. Relooper is tail-recursive,
// and long functions can cause us to recurse far enough to overflow the
// stack. We should fix that issue, but for now we work around it in the
// test by using a thread with a larger stack size.
//
// See https://github.com/immunant/c2rust/issues/1908
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(run_transpiler)
.unwrap()
.join()
.unwrap();
}
Loading
Loading