Skip to content

Commit afe238d

Browse files
authored
Add support to casting closure to function pointer (rust-lang#2124)
Change function declaration and call to ignore arguments that are ZST, and implement the closure to function pointer by reifying it to its FnOnce implementation. The casting of closures to function pointers rely on the fact that the closures do not capture anything. Those closures are represented by empty structures, which should get pruned from its argument list of the FnOnce::call_once implementation.
1 parent 5de2eb4 commit afe238d

File tree

14 files changed

+242
-128
lines changed

14 files changed

+242
-128
lines changed

docs/src/rust-feature-support.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,7 @@ not formally defined which makes it harder to ensure that we can properly model
154154
all their use cases.
155155

156156
In particular, there are some outstanding issues to note here:
157-
* Unimplemented `PointerCast::ClosureFnPointer` in
158-
[#274](https://github.com/model-checking/kani/issues/274) and `Variant` case
159-
in projections type in
157+
* Sanity check `Variant` type in projections
160158
[#448](https://github.com/model-checking/kani/issues/448).
161159
* Unexpected fat pointer results in
162160
[#277](https://github.com/model-checking/kani/issues/277),

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use kani_queries::UserInput;
1212
use rustc_ast::Attribute;
1313
use rustc_hir::def::DefKind;
1414
use rustc_hir::def_id::DefId;
15-
use rustc_middle::mir::{HasLocalDecls, Local};
15+
use rustc_middle::mir::{Body, HasLocalDecls, Local};
1616
use rustc_middle::ty::layout::FnAbiOf;
1717
use rustc_middle::ty::{self, Instance};
1818
use std::collections::BTreeMap;
@@ -47,14 +47,19 @@ impl<'tcx> GotocCtx<'tcx> {
4747
let base_name = self.codegen_var_base_name(&lc);
4848
let name = self.codegen_var_name(&lc);
4949
let ldata = &ldecls[lc];
50-
let t = self.monomorphize(ldata.ty);
51-
let t = self.codegen_ty(t);
50+
let var_ty = self.monomorphize(ldata.ty);
51+
let var_type = self.codegen_ty(var_ty);
5252
let loc = self.codegen_span(&ldata.source_info.span);
5353
// Indices [1, N] represent the function parameters where N is the number of parameters.
54-
let sym =
55-
Symbol::variable(name, base_name, t, self.codegen_span(&ldata.source_info.span))
56-
.with_is_hidden(!ldata.is_user_variable())
57-
.with_is_parameter(idx > 0 && idx <= num_args);
54+
// Except that ZST fields are not included as parameters.
55+
let sym = Symbol::variable(
56+
name,
57+
base_name,
58+
var_type,
59+
self.codegen_span(&ldata.source_info.span),
60+
)
61+
.with_is_hidden(!ldata.is_user_variable())
62+
.with_is_parameter((idx > 0 && idx <= num_args) && !self.is_zst(var_ty));
5863
let sym_e = sym.to_expr();
5964
self.symbol_table.insert(sym);
6065

@@ -96,29 +101,41 @@ impl<'tcx> GotocCtx<'tcx> {
96101
self.reset_current_fn();
97102
}
98103

104+
/// Codegen changes required due to the function ABI.
105+
/// We currently untuple arguments for RustCall ABI where the `spread_arg` is set.
106+
fn codegen_function_prelude(&mut self) {
107+
let mir = self.current_fn().mir();
108+
if let Some(spread_arg) = mir.spread_arg {
109+
self.codegen_spread_arg(mir, spread_arg);
110+
}
111+
}
112+
99113
/// MIR functions have a `spread_arg` field that specifies whether the
100114
/// final argument to the function is "spread" at the LLVM/codegen level
101115
/// from a tuple into its individual components. (Used for the "rust-
102-
/// call" ABI, necessary because dynamic trait closure cannot have an
116+
/// call" ABI, necessary because the function traits and closures cannot have an
103117
/// argument list in MIR that is both generic and variadic, so Rust
104118
/// allows a generic tuple).
105119
///
106-
/// If `spread_arg` is Some, then the wrapped value is the local that is
107-
/// to be "spread"/untupled. However, the MIR function body itself expects
108-
/// the tuple instead of the individual components, so we need to generate
109-
/// a function prelude that _retuples_, that is, writes the components
110-
/// back to the tuple local for use in the body.
120+
/// These tuples are used in the MIR to invoke a shim, and it's used in the shim body.
121+
///
122+
/// The `spread_arg` represents the the local variable that is to be "spread"/untupled.
123+
/// However, the function body itself may refer to the members of
124+
/// the tuple instead of the individual spread parameters, so we need to add to the
125+
/// function prelude code that _retuples_, that is, writes the arguments
126+
/// back to a local tuple that can be used in the body.
111127
///
112128
/// See:
113129
/// <https://rust-lang.zulipchat.com/#narrow/stream/182449-t-compiler.2Fhelp/topic/Determine.20untupled.20closure.20args.20from.20Instance.3F>
114-
fn codegen_function_prelude(&mut self) {
115-
let mir = self.current_fn().mir();
116-
if mir.spread_arg.is_none() {
117-
// No special tuple argument, no work to be done.
130+
fn codegen_spread_arg(&mut self, mir: &Body<'tcx>, spread_arg: Local) {
131+
tracing::debug!(current=?self.current_fn, "codegen_spread_arg");
132+
let spread_data = &mir.local_decls()[spread_arg];
133+
let tup_ty = self.monomorphize(spread_data.ty);
134+
if self.is_zst(tup_ty) {
135+
// No need to spread a ZST since it will be ignored.
118136
return;
119137
}
120-
let spread_arg = mir.spread_arg.unwrap();
121-
let spread_data = &mir.local_decls()[spread_arg];
138+
122139
let loc = self.codegen_span(&spread_data.source_info.span);
123140

124141
// Get the function signature from MIR, _before_ we untuple
@@ -159,7 +176,7 @@ impl<'tcx> GotocCtx<'tcx> {
159176
// };
160177
// ```
161178
// Note how the compiler has reordered the fields to improve packing.
162-
let tup_typ = self.codegen_ty(self.monomorphize(spread_data.ty));
179+
let tup_type = self.codegen_ty(tup_ty);
163180

164181
// We need to marshall the arguments into the tuple
165182
// The arguments themselves have been tacked onto the explicit function paramaters by
@@ -192,7 +209,7 @@ impl<'tcx> GotocCtx<'tcx> {
192209
let (name, base_name) = self.codegen_spread_arg_name(&lc);
193210
let sym = Symbol::variable(name, base_name, self.codegen_ty(arg_t), loc)
194211
.with_is_hidden(false)
195-
.with_is_parameter(true);
212+
.with_is_parameter(!self.is_zst(arg_t));
196213
// The spread arguments are additional function paramaters that are patched in
197214
// They are to the function signature added in the `fn_typ` function.
198215
// But they were never added to the symbol table, which we currently do here.
@@ -204,12 +221,12 @@ impl<'tcx> GotocCtx<'tcx> {
204221
(arg_i.to_string().intern(), sym.to_expr())
205222
}));
206223
let marshalled_tuple_value =
207-
Expr::struct_expr(tup_typ.clone(), marshalled_tuple_fields, &self.symbol_table)
224+
Expr::struct_expr(tup_type.clone(), marshalled_tuple_fields, &self.symbol_table)
208225
.with_location(loc);
209226
self.declare_variable(
210227
self.codegen_var_name(&spread_arg),
211228
self.codegen_var_base_name(&spread_arg),
212-
tup_typ,
229+
tup_type,
213230
Some(marshalled_tuple_value),
214231
loc,
215232
);

kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl<'tcx> GotocCtx<'tcx> {
5353

5454
if let Some(target) = target {
5555
let loc = self.codegen_span(&span);
56-
let fargs = self.codegen_funcall_args(args);
56+
let fargs = self.codegen_funcall_args(args, false);
5757
Stmt::block(
5858
vec![
5959
self.codegen_intrinsic(instance, fargs, destination, Some(span)),

kani-compiler/src/codegen_cprover_gotoc/codegen/rvalue.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -640,13 +640,13 @@ impl<'tcx> GotocCtx<'tcx> {
640640
fn codegen_pointer_cast(
641641
&mut self,
642642
k: &PointerCast,
643-
o: &Operand<'tcx>,
643+
operand: &Operand<'tcx>,
644644
t: Ty<'tcx>,
645645
loc: Location,
646646
) -> Expr {
647-
debug!(cast=?k, op=?o, ?loc, "codegen_pointer_cast");
647+
debug!(cast=?k, op=?operand, ?loc, "codegen_pointer_cast");
648648
match k {
649-
PointerCast::ReifyFnPointer => match self.operand_ty(o).kind() {
649+
PointerCast::ReifyFnPointer => match self.operand_ty(operand).kind() {
650650
ty::FnDef(def_id, substs) => {
651651
let instance =
652652
Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *def_id, substs)
@@ -658,17 +658,23 @@ impl<'tcx> GotocCtx<'tcx> {
658658
}
659659
_ => unreachable!(),
660660
},
661-
PointerCast::UnsafeFnPointer => self.codegen_operand(o),
661+
PointerCast::UnsafeFnPointer => self.codegen_operand(operand),
662662
PointerCast::ClosureFnPointer(_) => {
663-
let dest_typ = self.codegen_ty(t);
664-
self.codegen_unimplemented_expr(
665-
"PointerCast::ClosureFnPointer",
666-
dest_typ,
667-
loc,
668-
"https://github.com/model-checking/kani/issues/274",
669-
)
663+
if let ty::Closure(def_id, substs) = self.operand_ty(operand).kind() {
664+
let instance = Instance::resolve_closure(
665+
self.tcx,
666+
*def_id,
667+
substs,
668+
ty::ClosureKind::FnOnce,
669+
)
670+
.expect("failed to normalize and resolve closure during codegen")
671+
.polymorphize(self.tcx);
672+
self.codegen_func_expr(instance, None).address_of()
673+
} else {
674+
unreachable!("{:?} cannot be cast to a fn ptr", operand)
675+
}
670676
}
671-
PointerCast::MutToConstPointer => self.codegen_operand(o),
677+
PointerCast::MutToConstPointer => self.codegen_operand(operand),
672678
PointerCast::ArrayToPointer => {
673679
// TODO: I am not sure whether it is correct or not.
674680
//
@@ -677,11 +683,11 @@ impl<'tcx> GotocCtx<'tcx> {
677683
// if we had to, then [o] necessarily has type [T; n] where *T is a fat pointer, meaning
678684
// T is either [T] or str. but neither type is sized, which shouldn't participate in
679685
// codegen.
680-
match self.operand_ty(o).kind() {
686+
match self.operand_ty(operand).kind() {
681687
ty::RawPtr(ty::TypeAndMut { ty, .. }) => {
682688
// ty must be an array
683689
if let ty::Array(_, _) = ty.kind() {
684-
let oe = self.codegen_operand(o);
690+
let oe = self.codegen_operand(operand);
685691
oe.dereference() // : struct [T; n]
686692
.member("0", &self.symbol_table) // : T[n]
687693
.array_to_ptr() // : T*
@@ -693,8 +699,8 @@ impl<'tcx> GotocCtx<'tcx> {
693699
}
694700
}
695701
PointerCast::Unsize => {
696-
let src_goto_expr = self.codegen_operand(o);
697-
let src_mir_type = self.operand_ty(o);
702+
let src_goto_expr = self.codegen_operand(operand);
703+
let src_mir_type = self.operand_ty(operand);
698704
let dst_mir_type = t;
699705
self.codegen_unsized_cast(src_goto_expr, src_mir_type, dst_mir_type)
700706
}

kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ impl<'tcx> GotocCtx<'tcx> {
3333
StatementKind::Assign(box (l, r)) => {
3434
let lty = self.place_ty(l);
3535
let rty = self.rvalue_ty(r);
36-
let llayout = self.layout_of(lty);
3736
// we ignore assignment for all zero size types
38-
if llayout.is_zst() {
37+
if self.is_zst(lty) {
3938
Stmt::skip(location)
4039
} else if lty.is_fn_ptr() && rty.is_fn() && !rty.is_fn_ptr() {
4140
// implicit address of a function pointer, e.g.
@@ -402,10 +401,19 @@ impl<'tcx> GotocCtx<'tcx> {
402401
}
403402
}
404403

405-
/// As part of **calling** a function (closure actually), we may need to un-tuple arguments.
404+
/// As part of **calling** a function (or closure), we may need to un-tuple arguments.
406405
///
407-
/// See [GotocCtx::ty_needs_closure_untupled]
408-
fn codegen_untuple_closure_args(
406+
/// This function will replace the last `fargs` argument by its un-tupled version.
407+
///
408+
/// Some context: A closure / shim takes two arguments:
409+
/// 0. a struct (or a pointer to) representing the environment
410+
/// 1. a tuple containing the parameters (if not empty)
411+
///
412+
/// However, Rust generates a function where the tuple of parameters are flattened
413+
/// as subsequent parameters.
414+
///
415+
/// See [GotocCtx::ty_needs_untupled_args] for more details.
416+
fn codegen_untupled_args(
409417
&mut self,
410418
instance: Instance<'tcx>,
411419
fargs: &mut Vec<Expr>,
@@ -416,32 +424,22 @@ impl<'tcx> GotocCtx<'tcx> {
416424
self.readable_instance_name(instance),
417425
fargs
418426
);
419-
// A closure takes two arguments:
420-
// 0. a struct representing the environment
421-
// 1. a tuple containing the parameters
422-
//
423-
// However, for some reason, Rust decides to generate a function which still
424-
// takes the first argument as the environment struct, but the tuple of parameters
425-
// are flattened as subsequent parameters.
426-
// Therefore, we have to project out the corresponding fields when we detect
427-
// an invocation of a closure.
428-
//
429-
// Note: In some cases, the environment struct has type FnDef, so we skip it in
430-
// ignore_var_ty. So the tuple is always the last arg, but it might be in the
431-
// first or the second position.
432-
// Note 2: For empty closures, the only argument needed is the environment struct.
433427
if !fargs.is_empty() {
428+
let tuple_ty = self.operand_ty(last_mir_arg.unwrap());
429+
if self.is_zst(tuple_ty) {
430+
// Don't pass anything if all tuple elements are ZST.
431+
// ZST arguments are ignored.
432+
return;
433+
}
434434
let tupe = fargs.remove(fargs.len() - 1);
435-
let tupled_args: Vec<Type> = match self.operand_ty(last_mir_arg.unwrap()).kind() {
436-
ty::Tuple(tupled_args) => tupled_args.iter().map(|s| self.codegen_ty(s)).collect(),
437-
_ => unreachable!("Argument to function with Abi::RustCall is not a tuple"),
438-
};
439-
440-
// Unwrap as needed
441-
for (i, _) in tupled_args.iter().enumerate() {
442-
// Access the tupled parameters through the `member` operation
443-
let index_param = tupe.clone().member(&i.to_string(), &self.symbol_table);
444-
fargs.push(index_param);
435+
if let ty::Tuple(tupled_args) = tuple_ty.kind() {
436+
for (idx, arg_ty) in tupled_args.iter().enumerate() {
437+
if !self.is_zst(arg_ty) {
438+
// Access the tupled parameters through the `member` operation
439+
let idx_expr = tupe.clone().member(&idx.to_string(), &self.symbol_table);
440+
fargs.push(idx_expr);
441+
}
442+
}
445443
}
446444
}
447445
}
@@ -459,16 +457,30 @@ impl<'tcx> GotocCtx<'tcx> {
459457
/// Generate Goto-C for each argument to a function call.
460458
///
461459
/// N.B. public only because instrinsics use this directly, too.
462-
pub(crate) fn codegen_funcall_args(&mut self, args: &[Operand<'tcx>]) -> Vec<Expr> {
463-
args.iter()
464-
.map(|o| {
465-
if self.operand_ty(o).is_bool() {
466-
self.codegen_operand(o).cast_to(Type::c_bool())
460+
/// When `skip_zst` is set to `true`, the return value will not include any argument that is ZST.
461+
/// This is used because we ignore ZST arguments, except for intrinsics.
462+
pub(crate) fn codegen_funcall_args(
463+
&mut self,
464+
args: &[Operand<'tcx>],
465+
skip_zst: bool,
466+
) -> Vec<Expr> {
467+
let fargs = args
468+
.iter()
469+
.filter_map(|o| {
470+
let op_ty = self.operand_ty(o);
471+
if op_ty.is_bool() {
472+
Some(self.codegen_operand(o).cast_to(Type::c_bool()))
473+
} else if !self.is_zst(op_ty) || !skip_zst {
474+
Some(self.codegen_operand(o))
467475
} else {
468-
self.codegen_operand(o)
476+
// We ignore ZST types.
477+
debug!(arg=?o, "codegen_funcall_args ignore");
478+
None
469479
}
470480
})
471-
.collect()
481+
.collect();
482+
debug!(?fargs, "codegen_funcall_args");
483+
fargs
472484
}
473485

474486
/// Generates Goto-C for a MIR [TerminatorKind::Call] statement.
@@ -498,16 +510,17 @@ impl<'tcx> GotocCtx<'tcx> {
498510

499511
let loc = self.codegen_span(&span);
500512
let funct = self.operand_ty(func);
501-
let mut fargs = self.codegen_funcall_args(args);
513+
let mut fargs = self.codegen_funcall_args(args, true);
502514
match &funct.kind() {
503515
ty::FnDef(defid, subst) => {
504516
let instance =
505517
Instance::resolve(self.tcx, ty::ParamEnv::reveal_all(), *defid, subst)
506518
.unwrap()
507519
.unwrap();
508520

509-
if self.ty_needs_closure_untupled(funct) {
510-
self.codegen_untuple_closure_args(instance, &mut fargs, args.last());
521+
// TODO(celina): Move this check to be inside codegen_funcall_args.
522+
if self.ty_needs_untupled_args(funct) {
523+
self.codegen_untupled_args(instance, &mut fargs, args.last());
511524
}
512525

513526
if let Some(hk) = self.hooks.hook_applies(self.tcx, instance) {
@@ -608,6 +621,7 @@ impl<'tcx> GotocCtx<'tcx> {
608621
) -> Vec<Stmt> {
609622
let vtable_field_name = self.vtable_field_name(def_id, idx);
610623
trace!(?self_ty, ?place, ?vtable_field_name, "codegen_virtual_funcall");
624+
debug!(?fargs, "codegen_virtual_funcall");
611625

612626
let trait_fat_ptr = self.extract_ptr(fargs[0].clone(), self_ty);
613627
assert!(

0 commit comments

Comments
 (0)