Skip to content

Commit 4ffc28d

Browse files
committed
transpile: add --emit-c-decl-map flag to emit map from translated item to the corresponding C definition
1 parent e8b323f commit 4ffc28d

File tree

5 files changed

+143
-3
lines changed

5 files changed

+143
-3
lines changed

c2rust-transpile/src/c_ast/mod.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,39 @@ impl TypedAstContext {
299299
}
300300
}
301301

302+
/// Construct a map from top-level decls in the main file to their source ranges.
303+
pub fn top_decl_locs(&self) -> IndexMap<CDeclId, (SrcLoc, SrcLoc)> {
304+
let mut name_loc_map = IndexMap::new();
305+
let mut prev_src_loc = SrcLoc {
306+
fileid: 0,
307+
line: 0,
308+
column: 0,
309+
};
310+
for (decl_id, decl) in &self.c_decls {
311+
let begin_loc: SrcLoc = decl.begin_loc().expect("no begin loc for top-level decl");
312+
let end_loc: SrcLoc = decl.end_loc().expect("no end loc for top-level decl");
313+
314+
// If encountering a new file, reset end of last top-level decl.
315+
if prev_src_loc.fileid != begin_loc.fileid {
316+
prev_src_loc = SrcLoc {
317+
fileid: begin_loc.fileid,
318+
line: 1,
319+
column: 1,
320+
}
321+
}
322+
323+
// Include only decls from the main file.
324+
if self.c_decls_top.contains(decl_id)
325+
&& self.get_source_path(decl) == Some(&self.main_file)
326+
{
327+
let entry = (prev_src_loc, end_loc);
328+
name_loc_map.insert(*decl_id, entry);
329+
}
330+
prev_src_loc = end_loc;
331+
}
332+
name_loc_map
333+
}
334+
302335
pub fn iter_decls(&self) -> indexmap::map::Iter<'_, CDeclId, CDecl> {
303336
self.c_decls.iter()
304337
}

c2rust-transpile/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub struct TranspilerConfig {
7272
pub dump_structures: bool,
7373
pub verbose: bool,
7474
pub debug_ast_exporter: bool,
75+
pub emit_c_decl_map: bool,
7576

7677
// Options that control translation
7778
pub incremental_relooper: bool,
@@ -599,9 +600,30 @@ fn transpile_single(
599600
}
600601

601602
// Perform the translation
602-
let (translated_string, pragmas, crates) =
603+
let (translated_string, maybe_decl_map, pragmas, crates) =
603604
translator::translate(typed_context, tcfg, input_path);
604605

606+
if let Some(decl_map) = maybe_decl_map {
607+
let decl_map_path = output_path.with_extension("c_decls.json");
608+
let file = match File::create(&decl_map_path) {
609+
Ok(file) => file,
610+
Err(e) => panic!(
611+
"Unable to open file {} for writing: {}",
612+
output_path.display(),
613+
e
614+
),
615+
};
616+
617+
match serde_json::ser::to_writer(file, &decl_map) {
618+
Ok(()) => (),
619+
Err(e) => panic!(
620+
"Unable to write C declaration map to file {}: {}",
621+
output_path.display(),
622+
e
623+
),
624+
};
625+
}
626+
605627
let mut file = match File::create(&output_path) {
606628
Ok(file) => file,
607629
Err(e) => panic!(

c2rust-transpile/src/translator/mod.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use dtoa;
99
use failure::{err_msg, format_err, Fail};
1010
use indexmap::indexmap;
1111
use indexmap::{IndexMap, IndexSet};
12+
use itertools::Itertools;
1213
use log::{error, info, trace, warn};
1314
use proc_macro2::{Punct, Spacing::*, Span, TokenStream, TokenTree};
1415
use syn::spanned::Spanned as _;
@@ -492,11 +493,13 @@ pub fn translate_failure(tcfg: &TranspilerConfig, msg: &str) {
492493
}
493494
}
494495

496+
type DeclMap = IndexMap<String, String>;
497+
495498
pub fn translate(
496499
ast_context: TypedAstContext,
497500
tcfg: &TranspilerConfig,
498501
main_file: &Path,
499-
) -> (String, PragmaVec, CrateSet) {
502+
) -> (String, Option<DeclMap>, PragmaVec, CrateSet) {
500503
let mut t = Translation::new(ast_context, tcfg, main_file);
501504
let ctx = ExprContext {
502505
used: true,
@@ -513,6 +516,8 @@ pub fn translate(
513516
{
514517
t.locate_comments();
515518

519+
let decl_source_ranges = t.ast_context.top_decl_locs();
520+
516521
// Headers often pull in declarations that are unused;
517522
// we simplify the translator output by omitting those.
518523
t.ast_context
@@ -762,6 +767,80 @@ pub fn translate(
762767
})
763768
.collect::<HashMap<_, _>>();
764769

770+
// Generate a map from Rust items to the source code of their C declarations.
771+
let decl_map = if tcfg.emit_c_decl_map {
772+
let mut path_to_c_source_range: HashMap<&Ident, (SrcLoc, SrcLoc)> = Default::default();
773+
for (decl, source_range) in decl_source_ranges {
774+
match converted_decls.get(&decl) {
775+
Some(ConvertedDecl::ForeignItem(item)) => {
776+
path_to_c_source_range
777+
.insert(foreign_item_ident_vis(&*item).unwrap().0, source_range);
778+
}
779+
Some(ConvertedDecl::Item(item)) => {
780+
path_to_c_source_range.insert(item_ident(&*item).unwrap(), source_range);
781+
}
782+
Some(ConvertedDecl::Items(items)) => {
783+
for item in items {
784+
path_to_c_source_range
785+
.insert(item_ident(&*item).unwrap(), source_range);
786+
}
787+
}
788+
Some(ConvertedDecl::NoItem) => {}
789+
None => eprintln!("failed to convert top-level decl {decl:?}!"),
790+
}
791+
}
792+
793+
let file_content =
794+
std::fs::read(&t.ast_context.get_file_path(t.main_file).unwrap()).unwrap();
795+
let line_end_offsets = //memchr::memchr_iter(file_content, '\n')
796+
file_content.iter().positions(|c| *c == b'\n')
797+
.collect::<Vec<_>>();
798+
799+
/// Convert a source location line/column into a byte offset, given the positions of each newline in the file.
800+
fn src_loc_to_byte_offset(line_end_offsets: &[usize], loc: SrcLoc) -> usize {
801+
let line_offset = line_end_offsets
802+
.get(loc.line as usize - 2) // end of the previous line; for line < 1, index out of bounds
803+
.map(|x| x + 1) // increment end of the prev line to find start of this one
804+
.unwrap_or(0); // if we indexed out of bounds, start at byte 0
805+
line_offset + (loc.column as usize).saturating_sub(1)
806+
}
807+
808+
// Slice into the source file, fixing up the ends to account for Clang AST quirks.
809+
let slice_decl_with_fixups = |begin, end| -> &[u8] {
810+
let mut begin_offset = src_loc_to_byte_offset(&line_end_offsets, begin);
811+
let mut end_offset = src_loc_to_byte_offset(&line_end_offsets, end);
812+
const VT: u8 = 11;
813+
/* Skip whitespace and any trailing semicolons after the previous decl. */
814+
while let Some(b'\t' | b'\n' | &VT | b'\r' | b' ' | b';') =
815+
file_content.get(begin_offset)
816+
{
817+
begin_offset += 1;
818+
}
819+
820+
// Extend to include a single trailing semicolon if this decl is not a block
821+
// (e.g., a variable declaration).
822+
if file_content.get(end_offset - 1) != Some(&b'}')
823+
&& file_content.get(end_offset) == Some(&b';')
824+
{
825+
end_offset += 1;
826+
}
827+
828+
&file_content[begin_offset..end_offset]
829+
};
830+
831+
let item_path_to_c_source: IndexMap<_, _> = path_to_c_source_range
832+
.into_iter()
833+
.map(|(ident, (begin, end))| {
834+
let path = ident.to_string();
835+
let c_src = std::str::from_utf8(slice_decl_with_fixups(begin, end)).unwrap();
836+
(path, c_src.to_owned())
837+
})
838+
.collect();
839+
Some(item_path_to_c_source)
840+
} else {
841+
None
842+
};
843+
765844
t.ast_context.sort_top_decls_for_emitting();
766845

767846
for top_id in &t.ast_context.c_decls_top {
@@ -926,7 +1005,7 @@ pub fn translate(
9261005
.copied()
9271006
.collect();
9281007

929-
(translation, pragmas, crates)
1008+
(translation, decl_map, pragmas, crates)
9301009
}
9311010
}
9321011

c2rust-transpile/tests/snapshots.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ fn config() -> TranspilerConfig {
1616
dump_structures: false,
1717
verbose: false,
1818
debug_ast_exporter: false,
19+
emit_c_decl_map: false,
1920
incremental_relooper: true,
2021
fail_on_multiple: false,
2122
filter: None,

c2rust/src/bin/c2rust-transpile.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ struct Args {
3636
#[clap(long)]
3737
debug_ast_exporter: bool,
3838

39+
/// Dump map of top-level C decls
40+
#[clap(long)]
41+
emit_c_decl_map: bool,
42+
3943
/// Verbose mode
4044
#[clap(short = 'v', long)]
4145
verbose: bool,
@@ -205,6 +209,7 @@ fn main() {
205209
dump_cfg_liveness: args.dump_cfgs_liveness,
206210
dump_structures: args.dump_structures,
207211
debug_ast_exporter: args.debug_ast_exporter,
212+
emit_c_decl_map: args.emit_c_decl_map,
208213
verbose: args.verbose,
209214

210215
incremental_relooper: !args.no_incremental_relooper,

0 commit comments

Comments
 (0)