-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Represent FnDefs as zero-sized structs instead of Code #1338
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? |
@@ -618,7 +618,7 @@ impl<'tcx> GotocCtx<'tcx> { | |||
self.find_function(&fname).unwrap().call(vec![init]) | |||
} | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And document what this returns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in the next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be cleaner to just return the symbol, which would have the name and type included
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
@@ -618,7 +618,7 @@ impl<'tcx> GotocCtx<'tcx> { | |||
self.find_function(&fname).unwrap().call(vec![init]) | |||
} | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be cleaner to just return the symbol, which would have the name and type included
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
.
@@ -440,7 +440,10 @@ impl<'tcx> GotocCtx<'tcx> { | |||
| InstanceDef::ReifyShim(..) | |||
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is duplicated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 (
FnDef
s) toCode
in 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 representFnDef
s as zero-sized objects.For this reason, we create for each function instance
f
in Rust an empty dummy typef::FnDefStruct
and 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_expr
instead.This change also allows us to enable type checking of fields (cf. #1243).
Resolved issues:
Resolves #1243
Call-outs:
Also, we no longer ignore
FnDef
parameters 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.