Skip to content

Commit 2d8367a

Browse files
authored
Make --gen-c-runnable produce runnable C code (#334)
1 parent 177386f commit 2d8367a

File tree

17 files changed

+1049
-109
lines changed

17 files changed

+1049
-109
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ For example, consider the `CopyIntrinsics` regression test:
144144
rmc --gen-c main.rs <other-args>
145145
```
146146
This generates a file `main.c` which contains a "C" like formatting of the CBMC IR.
147+
1. The `--gen-c` flag does not produce runnable C code due to differences in the Rust and C languages.
148+
To produce a runnable C program, try passing the `--gen-c-runnable` flag to RMC
149+
```
150+
rmc --gen-c-runnable main.rs <other-args>
151+
```
152+
This generates a file `main_runnable.c`.
153+
Note that this makes some compromises to produce runnable C code, so you should not expect exact semantic equivalence.
147154
1. You can also view the raw CBMC internal representation using the `--keep-temps` option.
148155

149156
### Experimental Cargo integration

compiler/rustc_codegen_llvm/src/gotoc/cbmc/goto_program/expr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ impl Expr {
878878
}
879879
}
880880
/// self op right;
881-
fn binop(self, op: BinaryOperand, rhs: Expr) -> Expr {
881+
pub fn binop(self, op: BinaryOperand, rhs: Expr) -> Expr {
882882
assert!(
883883
Expr::typecheck_binop_args(op, &self, &rhs),
884884
"BinaryOperation Expression does not typecheck {:?} {:?} {:?}",
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
use std::ops::{BitAnd, Shl, Shr};
5+
6+
use super::super::Transformer;
7+
use crate::gotoc::cbmc::goto_program::{
8+
BinaryOperand, CIntType, Expr, Location, Parameter, Stmt, Symbol, SymbolTable, SymbolValues,
9+
Type,
10+
};
11+
use num::bigint::BigInt;
12+
use rustc_data_structures::fx::FxHashMap;
13+
14+
/// Create an expr from an int constant using only values <= u64::MAX;
15+
/// this is needed because gcc allows 128 bit int variables, but not 128 bit constants
16+
fn bignum_to_expr(num: &BigInt, typ: &Type) -> Expr {
17+
// CInteger types should already be valid in C
18+
if typ.is_c_integer() {
19+
return Expr::int_constant(num.clone(), typ.clone());
20+
}
21+
22+
// Only need to handle type wider than 64 bits
23+
if let Some(width) = typ.width() {
24+
if width <= 64 {
25+
return Expr::int_constant(num.clone(), typ.clone());
26+
}
27+
}
28+
29+
// Only types that should be left are i128 and u128
30+
assert_eq!(typ.width(), Some(128), "Unexpected int width: {:?}", typ.width());
31+
32+
// Work with unsigned ints, and cast to original type at end
33+
let unsigned_typ = Type::unsigned_int(128);
34+
35+
// To transform a 128 bit num, we break it into two 64 bit nums
36+
37+
// left_mask = 11..1100..00 (64 1's followed by 64 0's)
38+
// left_mask = 00..0011..11 (64 0's followed by 64 1's)
39+
let left_mask = BigInt::from(u64::MAX).shl(64);
40+
let right_mask = BigInt::from(u64::MAX);
41+
42+
// Construct the two 64 bit ints such that
43+
// num = (left_half << 64) | right_half
44+
// = (left_half * 2^64) + right_half
45+
let left_half = {
46+
// Split into two parts to help type inference
47+
let temp: BigInt = num.bitand(left_mask);
48+
temp.shr(64)
49+
};
50+
let right_half = num.bitand(right_mask);
51+
52+
// Create CBMC constants for the left and right halfs
53+
let left_constant = Expr::int_constant(left_half, unsigned_typ.clone());
54+
let right_constant = Expr::int_constant(right_half, unsigned_typ);
55+
56+
// Construct CBMC expression: (typ) ((left << 64) | right)
57+
left_constant
58+
.shl(Expr::int_constant(64, Type::c_int()))
59+
.bitor(right_constant)
60+
.cast_to(typ.clone())
61+
}
62+
63+
/// Struct for handling the expression replacement transformations for --gen-c-runnable.
64+
pub struct ExprTransformer {
65+
new_symbol_table: SymbolTable,
66+
/// The `empty_statics` field is used to track extern static variables;
67+
/// when such a symbol is encountered, we add it to this map;
68+
/// in postprocessing, we initialize each of these variables
69+
/// with a default value to emphasize that these are externally defined.
70+
empty_statics: FxHashMap<String, Expr>,
71+
}
72+
73+
impl ExprTransformer {
74+
/// Replace expressions which lead to invalid C with alternatives.
75+
pub fn transform(original_symbol_table: &SymbolTable) -> SymbolTable {
76+
let new_symbol_table = SymbolTable::new(original_symbol_table.machine_model().clone());
77+
ExprTransformer { new_symbol_table, empty_statics: FxHashMap::default() }
78+
.transform_symbol_table(original_symbol_table)
79+
}
80+
81+
/// Extract `empty_statics` map for final processing.
82+
fn empty_statics_owned(&mut self) -> FxHashMap<String, Expr> {
83+
std::mem::replace(&mut self.empty_statics, FxHashMap::default())
84+
}
85+
86+
/// Add identifier to a transformed parameter if it's missing;
87+
/// necessary when function wasn't originally a definition, e.g. extern functions,
88+
/// so that we can give them a function body.
89+
fn add_parameter_identifier(&mut self, parameter: &Parameter) -> Parameter {
90+
if parameter.identifier().is_some() {
91+
parameter.clone()
92+
} else {
93+
let name = format!("__{}", parameter.typ().to_identifier());
94+
let parameter_sym = self.mut_symbol_table().ensure(&name, |_symtab, name| {
95+
Symbol::variable(
96+
name.to_string(),
97+
name.to_string(),
98+
parameter.typ().clone(),
99+
Location::none(),
100+
)
101+
});
102+
parameter_sym.to_function_parameter()
103+
}
104+
}
105+
}
106+
107+
impl Transformer for ExprTransformer {
108+
/// Get reference to symbol table.
109+
fn symbol_table(&self) -> &SymbolTable {
110+
&self.new_symbol_table
111+
}
112+
113+
/// Get mutable reference to symbol table.
114+
fn mut_symbol_table(&mut self) -> &mut SymbolTable {
115+
&mut self.new_symbol_table
116+
}
117+
118+
/// Get owned symbol table.
119+
fn extract_symbol_table(self) -> SymbolTable {
120+
self.new_symbol_table
121+
}
122+
123+
/// Translate Implies into Or/Not.
124+
fn transform_expr_bin_op(
125+
&mut self,
126+
_typ: &Type,
127+
op: &BinaryOperand,
128+
lhs: &Expr,
129+
rhs: &Expr,
130+
) -> Expr {
131+
let lhs = self.transform_expr(lhs);
132+
let rhs = self.transform_expr(rhs);
133+
134+
match op {
135+
BinaryOperand::Implies => lhs.not().bitor(rhs).cast_to(Type::bool()),
136+
_ => lhs.binop(*op, rhs),
137+
}
138+
}
139+
140+
/// Prevent error for too large constants with u128.
141+
fn transform_expr_int_constant(&mut self, typ: &Type, value: &BigInt) -> Expr {
142+
let transformed_typ = self.transform_type(typ);
143+
bignum_to_expr(value, &transformed_typ)
144+
}
145+
146+
/// When indexing into a SIMD vector, cast to a pointer first to make legal indexing in C.
147+
/// `typ __attribute__((vector_size (size * sizeof(typ)))) var;`
148+
/// `((typ*) &var)[index]`
149+
/// Tracking issue: https://github.com/model-checking/rmc/issues/444
150+
fn transform_expr_index(&mut self, _typ: &Type, array: &Expr, index: &Expr) -> Expr {
151+
let transformed_array = self.transform_expr(array);
152+
let transformed_index = self.transform_expr(index);
153+
if transformed_array.typ().is_vector() {
154+
let base_type = transformed_array.typ().base_type().unwrap().clone();
155+
transformed_array.address_of().cast_to(base_type.to_pointer()).index(transformed_index)
156+
} else {
157+
transformed_array.index(transformed_index)
158+
}
159+
}
160+
161+
/// Replace `extern` functions and values with `nondet` so linker doesn't break.
162+
fn transform_symbol(&mut self, symbol: &Symbol) -> Symbol {
163+
let mut new_symbol = symbol.clone();
164+
165+
if symbol.is_extern {
166+
if symbol.typ.is_code() || symbol.typ.is_variadic_code() {
167+
// Replace `extern` function with one which returns `nondet`
168+
assert!(symbol.value.is_none(), "Extern function should have no body.");
169+
170+
let transformed_typ = self.transform_type(&symbol.typ);
171+
172+
// Fill missing parameter names with dummy name
173+
let parameters = transformed_typ
174+
.parameters()
175+
.unwrap()
176+
.iter()
177+
.map(|parameter| self.add_parameter_identifier(parameter))
178+
.collect();
179+
let ret_typ = transformed_typ.return_type().unwrap().clone();
180+
let new_typ = if transformed_typ.is_code() {
181+
Type::code(parameters, ret_typ.clone())
182+
} else {
183+
Type::variadic_code(parameters, ret_typ.clone())
184+
};
185+
186+
// Create body, which returns nondet
187+
let ret_e = if ret_typ.is_empty() { None } else { Some(Expr::nondet(ret_typ)) };
188+
let body = Stmt::ret(ret_e, Location::none());
189+
let new_value = SymbolValues::Stmt(body);
190+
191+
new_symbol.is_extern = false;
192+
new_symbol.typ = new_typ;
193+
new_symbol.value = new_value;
194+
} else {
195+
// Replace `extern static`s and initialize in `main`
196+
assert!(
197+
symbol.is_static_lifetime,
198+
"Extern objects that aren't functions should be static variables."
199+
);
200+
let new_typ = self.transform_type(&symbol.typ);
201+
self.empty_statics.insert(symbol.name.clone(), Expr::nondet(new_typ.clone()));
202+
203+
// Symbol is no longer extern
204+
new_symbol.is_extern = false;
205+
206+
// Set location to none so that it is a global static
207+
new_symbol.location = Location::none();
208+
209+
new_symbol.typ = new_typ;
210+
new_symbol.value = SymbolValues::None;
211+
}
212+
} else if &symbol.name == "main" {
213+
// Replace `main` with `main_` since it has the wrong return type
214+
new_symbol.name = "main_".to_string();
215+
new_symbol.base_name = Some("main_".to_string());
216+
new_symbol.pretty_name = Some("main_".to_string());
217+
218+
let new_typ = self.transform_type(&symbol.typ);
219+
let new_value = self.transform_value(&symbol.value);
220+
221+
new_symbol.typ = new_typ;
222+
new_symbol.value = new_value;
223+
} else {
224+
// Handle all other symbols normally
225+
let new_typ = self.transform_type(&symbol.typ);
226+
let new_value = self.transform_value(&symbol.value);
227+
new_symbol.typ = new_typ;
228+
new_symbol.value = new_value;
229+
}
230+
231+
new_symbol
232+
}
233+
234+
/// Move `main` to `main_`, and create a wrapper `main` to initialize statics and return `int`.
235+
fn postprocess(&mut self) {
236+
// The body of the new `main` function
237+
let mut main_body = Vec::new();
238+
239+
// Initialize statics
240+
for (name, value) in self.empty_statics_owned() {
241+
let sym_expr = Expr::symbol_expression(name, value.typ().clone());
242+
main_body.push(Stmt::assign(sym_expr, value, Location::none()));
243+
}
244+
245+
// `main_();`, if it is present
246+
if let Some(main_) = self.symbol_table().lookup("main_") {
247+
main_body
248+
.push(Stmt::code_expression(main_.to_expr().call(Vec::new()), Location::none()));
249+
}
250+
251+
// `return 0;`
252+
main_body.push(Stmt::ret(
253+
Some(Expr::int_constant(0, Type::CInteger(CIntType::Int))),
254+
Location::none(),
255+
));
256+
257+
// Create `main` symbol
258+
let new_main = Symbol::function(
259+
"main",
260+
Type::code(Vec::new(), Type::CInteger(CIntType::Int)),
261+
Some(Stmt::block(main_body, Location::none())),
262+
Some("main".to_string()),
263+
Location::none(),
264+
);
265+
266+
self.mut_symbol_table().insert(new_main);
267+
}
268+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
mod expr_transformer;
5+
mod name_transformer;
6+
mod nondet_transformer;
7+
8+
pub use expr_transformer::ExprTransformer;
9+
pub use name_transformer::NameTransformer;
10+
pub use nondet_transformer::NondetTransformer;

0 commit comments

Comments
 (0)