Represent FnDefs as zero-sized structs instead of Code#1338
Represent FnDefs as zero-sized structs instead of Code#1338fzaiser merged 14 commits intomodel-checking:mainfrom
Conversation
|
Just curious: do you actually need to put those elements in the struct at all? I can see how that might make sense at the Rust level for perhaps it introduces scopes or convenient names, but maybe this just doesn't need to be carried over? |
| } | ||
|
|
||
| pub fn codegen_func_expr(&mut self, instance: Instance<'tcx>, span: Option<&Span>) -> Expr { | ||
| pub fn ensure_func(&mut self, instance: Instance<'tcx>) -> (String, Type) { |
There was a problem hiding this comment.
And document what this returns
There was a problem hiding this comment.
Fixed in the next commit.
There was a problem hiding this comment.
Might be cleaner to just return the symbol, which would have the name and type included
There was a problem hiding this comment.
Actually, that does not work. Because at least for the function memcmp, the signature generated by codegen_function_sig is different from the type that's already in the symbol table. If I add an assertion to check that the two results are the same
let funct = self.codegen_function_sig(self.fn_sig_of_instance(instance).unwrap());
let sym = self.ensure(&func, |ctx, _| {
Symbol::function(
&func,
funct.clone(),
None,
Some(ctx.readable_instance_name(instance)),
Location::none(),
)
.with_is_extern(true)
});
dbg!(instance);
assert_eq!(funct, sym.typ.clone());Then I get the following error:
$ kani tests/kani/Whitespace/main.rs
[...]
[kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs:637] instance = Instance {
def: Item(
WithOptConstParam {
did: DefId(3:10534 ~ core[916d]::slice::cmp::{extern#0}::memcmp),
const_param_did: None,
},
),
substs: [],
}
thread 'rustc' panicked at 'assertion failed: `(left == right)`
left: `Code { parameters: [Parameter { typ: Pointer { typ: Unsignedbv { width: 8 } }, identifier: None, base_name: None }, Parameter { typ: Pointer { typ: Unsignedbv { width: 8 } }, identifier: None, base_name: None }, Parameter { typ: CInteger(SizeT), identifier: None, base_name: None }], return_type: Signedbv { width: 32 } }`,
right: `Code { parameters: [Parameter { typ: Pointer { typ: Empty }, identifier: None, base_name: None }, Parameter { typ: Pointer { typ: Empty }, identifier: None, base_name: None }, Parameter { typ: CInteger(SizeT), identifier: None, base_name: None }], return_type: CInteger(Int) }`'
I guess the type that's stored for memcmp is different because it's built-in? How should I proceed?
There was a problem hiding this comment.
I did not want to spend too much time on this, so I decided to just return the symbol and the type for now. I filed #1350 to track this problem and added a fixme in the code.
|
@tautschnig I think we could filter out struct fields, parameters and local variables of |
No, I'm not really sure there is much of a performance cost. I was just wondering whether getting rid of those struct members would be easier to implement. But you seem to have a working solution already, so all is well. |
|
I just added some explanations and a link to this PR in the comments to provide context. i also updated the PR description. |
| } | ||
|
|
||
| pub fn codegen_func_expr(&mut self, instance: Instance<'tcx>, span: Option<&Span>) -> Expr { | ||
| pub fn ensure_func(&mut self, instance: Instance<'tcx>) -> (String, Type) { |
There was a problem hiding this comment.
Might be cleaner to just return the symbol, which would have the name and type included
celinval
left a comment
There was a problem hiding this comment.
That's a clever approach. The function names are a bit confusing to me though. I would suggest moving the "ensure*" functions to typ.rs and rename them to codegen_*_type. I also suggest renaming codegen_func_expr_zst to something like codegen_fn_item.
| | InstanceDef::ClosureOnceShim { .. } | ||
| | InstanceDef::CloneShim(..) => { | ||
| let func_exp = self.codegen_operand(func); | ||
| // We need to handle FnDef items in a special way because `codegen_operand` compiles them to dummy structs. |
There was a problem hiding this comment.
This comment is duplicated
There was a problem hiding this comment.
I've shortened it. But I don't want to remove it completely because it adds important context in my opinion. Same for the comment in rvalue.rs. I agree that the comment in typ.rs is redundant though (it just copied the function doc), so I've removed that one.
|
You could also add the definition from the rust reference: https://doc.rust-lang.org/reference/types/function-item.html |
|
@celinval I've renamed the |
Description of changes:
Before this change, Kani compiled functions (
FnDefs) toCodein GotoC, i.e. C function types. This causes problems because functions in C are not first-class, e.g. they cannot be stored in a struct. In Rust, this is possible: functions are just zero-sized objects of a unique anonymous type. To allow the same behavior in GotoC, we need to actually representFnDefs as zero-sized objects.For this reason, we create for each function instance
fin Rust an empty dummy typef::FnDefStructand a global variablef::FnDefSingleton. We need to do this for each instance to ensure that function pointer equality behaves as expected. This is done by thecodegen_fun_expr_zst.In some contexts, e.g. when calling a function or casting a function to a function pointer, we need to convert the dummy struct back to an actual function. This is done by calling
codegen_func_exprinstead.This change also allows us to enable type checking of fields (cf. #1243).
Resolved issues:
Resolves #1243
Call-outs:
Also, we no longer ignore
FnDefparameters of functions. I believe this is desirable because it makes the code more consistent, but I can undo the change if you want.Testing:
How is this change tested? Additional tests in
tests/kani/FunctionSymbols, plus the existing regression tests exercising this code as well.Is this a refactor change? No.
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.