Skip to content

Commit be5ba3f

Browse files
committed
Early support for basic (non-capturing) closures: add closure detection, extraction, and lowering logic; update related modules and tests.
1 parent 5906cb2 commit be5ba3f

File tree

9 files changed

+450
-44
lines changed

9 files changed

+450
-44
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
**/.gradle/*
66
**/build/*
77
**/.kotlin/*
8-
library/gradle/**
98

109
# Files generated by Tester.py
1110
**/*.generated

src/lib.rs

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,75 @@ mod optimise1;
4949
/// An instance of our Java bytecode codegen backend.
5050
struct MyBackend;
5151

52+
/// Helper function to lower a closure definition to OOMIR
53+
///
54+
/// This function is called when we encounter a closure call and need to ensure
55+
/// the closure's implementation is available in the OOMIR module.
56+
fn lower_closure_to_oomir<'tcx>(
57+
tcx: TyCtxt<'tcx>,
58+
closure_def_id: rustc_hir::def_id::DefId,
59+
oomir_module: &mut oomir::Module,
60+
) {
61+
// Generate the closure function name
62+
let closure_name = lower1::generate_closure_function_name(tcx, closure_def_id);
63+
64+
// Check if we've already lowered this closure
65+
if oomir_module.functions.contains_key(&closure_name) {
66+
breadcrumbs::log!(
67+
breadcrumbs::LogLevel::Info,
68+
"closure-lowering",
69+
format!("Closure {} already lowered, skipping", closure_name)
70+
);
71+
return;
72+
}
73+
74+
breadcrumbs::log!(
75+
breadcrumbs::LogLevel::Info,
76+
"closure-lowering",
77+
format!(
78+
"Lowering closure: {} (DefId: {:?})",
79+
closure_name, closure_def_id
80+
)
81+
);
82+
83+
// Get the closure's MIR using expect_resolve with fully monomorphized typing environment
84+
// We use fully_monomorphized() since we've already filtered out closures with captures.
85+
use rustc_middle::ty::TypingEnv;
86+
let typing_env = TypingEnv::fully_monomorphized();
87+
let generic_args = rustc_middle::ty::GenericArgs::empty();
88+
89+
let instance = rustc_middle::ty::Instance::expect_resolve(
90+
tcx,
91+
typing_env,
92+
closure_def_id,
93+
generic_args,
94+
rustc_span::DUMMY_SP,
95+
);
96+
97+
let mut mir = tcx.optimized_mir(instance.def_id()).clone();
98+
99+
breadcrumbs::log!(
100+
breadcrumbs::LogLevel::Info,
101+
"closure-lowering",
102+
format!("Closure MIR for {}: {:?}", closure_name, mir)
103+
);
104+
105+
// Lower the closure MIR to OOMIR, providing the closure name as an override
106+
// since closures don't have proper item names in rustc
107+
let (oomir_function, data_types) =
108+
lower1::mir_to_oomir(tcx, instance, &mut mir, Some(closure_name.clone()));
109+
110+
breadcrumbs::log!(
111+
breadcrumbs::LogLevel::Info,
112+
"closure-lowering",
113+
format!("Successfully lowered closure: {}", closure_name)
114+
);
115+
116+
// Add the closure function to the module
117+
oomir_module.functions.insert(closure_name, oomir_function);
118+
oomir_module.merge_data_types(&data_types);
119+
}
120+
52121
impl CodegenBackend for MyBackend {
53122
fn locale_resource(&self) -> &'static str {
54123
""
@@ -69,6 +138,10 @@ impl CodegenBackend for MyBackend {
69138
data_types: std::collections::HashMap::new(),
70139
};
71140

141+
// Track closures we need to lower
142+
let mut closures_to_lower: std::collections::HashSet<rustc_hir::def_id::DefId> =
143+
std::collections::HashSet::new();
144+
72145
// Iterate through all items in the crate and find functions
73146
let module_items = tcx.hir_crate_items(());
74147

@@ -92,12 +165,45 @@ impl CodegenBackend for MyBackend {
92165
format!("MIR for function {i}: {:?}", mir)
93166
);
94167

168+
// Collect closures from mentioned_items in the MIR
169+
if let Some(mentioned_items) = &mir.mentioned_items {
170+
for mentioned in mentioned_items.iter() {
171+
// Check if this mentioned item is a closure
172+
if let rustc_middle::mir::MentionedItem::Fn(fn_ty) = &mentioned.node {
173+
if let rustc_middle::ty::TyKind::FnDef(_fn_def_id, fn_args) =
174+
fn_ty.kind()
175+
{
176+
// Check the first argument to see if it's a closure type
177+
if let Some(first_arg) = fn_args.get(0) {
178+
if let Some(ty) = first_arg.as_type() {
179+
if let rustc_middle::ty::TyKind::Closure(
180+
closure_def_id,
181+
_,
182+
) = ty.kind()
183+
{
184+
breadcrumbs::log!(
185+
breadcrumbs::LogLevel::Info,
186+
"closure-discovery",
187+
format!(
188+
"Found closure {:?} in function {}",
189+
closure_def_id, i
190+
)
191+
);
192+
closures_to_lower.insert(*closure_def_id);
193+
}
194+
}
195+
}
196+
}
197+
}
198+
}
199+
}
200+
95201
breadcrumbs::log!(
96202
breadcrumbs::LogLevel::Info,
97203
"mir-lowering",
98204
format!("--- Starting MIR to OOMIR Lowering for function: {i} ---")
99205
);
100-
let oomir_result = lower1::mir_to_oomir(tcx, instance, &mut mir);
206+
let oomir_result = lower1::mir_to_oomir(tcx, instance, &mut mir, None);
101207
breadcrumbs::log!(
102208
breadcrumbs::LogLevel::Info,
103209
"mir-lowering",
@@ -167,12 +273,43 @@ impl CodegenBackend for MyBackend {
167273
format!("MIR for function {i2}: {:?}", mir)
168274
);
169275

276+
// Collect closures from mentioned_items in the MIR
277+
if let Some(mentioned_items) = &mir.mentioned_items {
278+
for mentioned in mentioned_items.iter() {
279+
if let rustc_middle::mir::MentionedItem::Fn(fn_ty) = &mentioned.node {
280+
if let rustc_middle::ty::TyKind::FnDef(_fn_def_id, fn_args) =
281+
fn_ty.kind()
282+
{
283+
if let Some(first_arg) = fn_args.get(0) {
284+
if let Some(ty) = first_arg.as_type() {
285+
if let rustc_middle::ty::TyKind::Closure(
286+
closure_def_id,
287+
_,
288+
) = ty.kind()
289+
{
290+
breadcrumbs::log!(
291+
breadcrumbs::LogLevel::Info,
292+
"closure-discovery",
293+
format!(
294+
"Found closure {:?} in impl method {}",
295+
closure_def_id, i2
296+
)
297+
);
298+
closures_to_lower.insert(*closure_def_id);
299+
}
300+
}
301+
}
302+
}
303+
}
304+
}
305+
}
306+
170307
breadcrumbs::log!(
171308
breadcrumbs::LogLevel::Info,
172309
"mir-lowering",
173310
format!("--- Starting MIR to OOMIR Lowering for function: {i2} ---")
174311
);
175-
let oomir_result = lower1::mir_to_oomir(tcx, instance, &mut mir);
312+
let oomir_result = lower1::mir_to_oomir(tcx, instance, &mut mir, None);
176313
breadcrumbs::log!(
177314
breadcrumbs::LogLevel::Info,
178315
"mir-lowering",
@@ -566,6 +703,37 @@ impl CodegenBackend for MyBackend {
566703
}
567704
}
568705

706+
// Now lower all discovered closures
707+
breadcrumbs::log!(
708+
breadcrumbs::LogLevel::Info,
709+
"closure-lowering",
710+
format!(
711+
"Attempting to lower {} discovered closures",
712+
closures_to_lower.len()
713+
)
714+
);
715+
716+
for closure_def_id in closures_to_lower {
717+
// Check if this closure captures any variables (has upvars)
718+
// Closures that don't capture anything can be lowered with Instance::mono
719+
let upvars = tcx.upvars_mentioned(closure_def_id);
720+
721+
if upvars.is_some() && !upvars.unwrap().is_empty() {
722+
breadcrumbs::log!(
723+
breadcrumbs::LogLevel::Warn,
724+
"closure-lowering",
725+
format!(
726+
"Closure {} captures variables ({} upvars), skipping for now",
727+
lower1::generate_closure_function_name(tcx, closure_def_id),
728+
upvars.unwrap().len()
729+
)
730+
);
731+
continue;
732+
}
733+
734+
lower_closure_to_oomir(tcx, closure_def_id, &mut oomir_module);
735+
}
736+
569737
breadcrumbs::log!(
570738
breadcrumbs::LogLevel::Info,
571739
"backend",
@@ -714,7 +882,7 @@ impl CodegenBackend for MyBackend {
714882

715883
struct RustcCodegenJvmLogListener;
716884

717-
const LISTENING_CHANNELS: &[&str] = &["backend"];
885+
const LISTENING_CHANNELS: &[&str] = &[];
718886

719887
impl breadcrumbs::LogListener for RustcCodegenJvmLogListener {
720888
fn on_log(&mut self, log: breadcrumbs::Log) {

src/lower1.rs

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,47 @@ use rustc_middle::{
1616
use std::collections::HashMap;
1717
use types::ty_to_oomir_type;
1818

19+
mod closures;
1920
mod control_flow;
2021
pub mod operand;
2122
pub mod place;
2223
pub mod types;
2324

25+
pub use closures::{ClosureCallInfo, extract_closure_info, generate_closure_function_name};
26+
2427
/// Converts a MIR Body into an OOMIR Function.
25-
/// This function extracts a functions signature (currently minimal) and builds
28+
/// This function extracts a function's signature (currently minimal) and builds
2629
/// a control flow graph of basic blocks.
30+
///
31+
/// If `fn_name_override` is provided, it will be used as the function name instead of
32+
/// querying rustc for the item name. This is necessary for closures, which don't have
33+
/// proper item names in rustc's internal representation.
2734
pub fn mir_to_oomir<'tcx>(
2835
tcx: TyCtxt<'tcx>,
2936
instance: Instance<'tcx>,
3037
mir: &mut Body<'tcx>,
38+
fn_name_override: Option<String>,
3139
) -> (oomir::Function, HashMap<String, oomir::DataType>) {
32-
// Get a function name from the instance.
33-
let fn_name = tcx.item_name(instance.def_id()).to_string();
40+
use rustc_middle::ty::TyKind;
41+
42+
// Get a function name from the instance or use the provided override.
43+
// Closures don't have proper item names, so we must use an override for them.
44+
let fn_name = fn_name_override.unwrap_or_else(|| tcx.item_name(instance.def_id()).to_string());
3445

3546
// Extract function signature
36-
let mir_sig = tcx.type_of(instance.def_id()).skip_binder().fn_sig(tcx);
37-
let params_ty = mir_sig.inputs();
38-
let return_ty = mir_sig.output();
47+
// Closures require special handling - we must use as_closure().sig() instead of fn_sig()
48+
let instance_ty = tcx.type_of(instance.def_id()).skip_binder();
49+
let (params_ty, return_ty) = match instance_ty.kind() {
50+
TyKind::Closure(_def_id, args) => {
51+
let sig = args.as_closure().sig();
52+
(sig.inputs(), sig.output())
53+
}
54+
_ => {
55+
// Regular function - use fn_sig()
56+
let mir_sig = instance_ty.fn_sig(tcx);
57+
(mir_sig.inputs(), mir_sig.output())
58+
}
59+
};
3960

4061
let data_types = &mut HashMap::new();
4162

@@ -89,42 +110,48 @@ pub fn mir_to_oomir<'tcx>(
89110
basic_blocks.insert(bb_ir.label.clone(), bb_ir);
90111
}
91112

92-
/*let mut instrs = vec![];
93-
94-
// Initialize local variables (excluding return place and arguments)
95-
// MIR local indices:
96-
// 0: return place
97-
// 1..=arg_count: arguments
98-
// arg_count+1..: user variables and temporaries
99-
for (local_index, local_decl) in mir_cloned.local_decls.iter_enumerated() {
100-
// local_index has type Local
101-
// local_decl has type &LocalDecl<'tcx>
102-
103-
// Skip return place (_0) and arguments (_1 to _arg_count)
104-
// They are initialized by return value / function call respectively.
105-
if local_index.index() == 0 || local_index.index() <= mir_cloned.arg_count {
106-
continue; // Skip this local
107-
}
108-
109-
let ty = local_decl.ty;
110-
let oomir_ty = ty_to_oomir_type(ty, tcx, data_types);
111-
112-
// Only add initialization if the type has a default value we can represent
113-
let default = oomir_ty.get_default_value(data_types);
114-
if default.is_none() {
115-
continue;
113+
// For closures, we need to unpack the tuple argument into local variables
114+
// Closures take a single tuple parameter, but MIR expects individual arguments in separate locals
115+
let mut instrs = vec![];
116+
117+
if matches!(instance_ty.kind(), TyKind::Closure(..)) && mir_cloned.arg_count > 0 {
118+
// For closures: local 0 = return place, local 1 = tuple argument
119+
// MIR expects: local 0 = return, local 1 = first arg, local 2 = second arg, etc.
120+
// But we receive: local 1 = tuple containing all args
121+
122+
// Get the tuple parameter type (should be the first parameter in the signature)
123+
if let Some(tuple_param_ty) = signature.params.first() {
124+
// Check if it's a tuple/struct type that we need to unpack
125+
if let oomir::Type::Class(class_name) = tuple_param_ty {
126+
// Get the data type definition to see its fields
127+
if let Some(oomir::DataType::Class { fields, .. }) = data_types.get(class_name) {
128+
// Unpack each field from the tuple into the expected local variables
129+
// Local 1 contains the tuple, we need to extract fields to locals 2, 3, 4...
130+
for (field_idx, (field_name, field_ty)) in fields.iter().enumerate() {
131+
let local_var_index = field_idx + 2; // Start from local 2 (local 1 is the tuple)
132+
133+
// Get the field from the tuple object (local 1)
134+
instrs.push(oomir::Instruction::GetField {
135+
dest: format!("_{}", local_var_index),
136+
object: oomir::Operand::Variable {
137+
name: "_1".to_string(),
138+
ty: tuple_param_ty.clone(),
139+
},
140+
field_name: field_name.clone(),
141+
field_ty: field_ty.clone(),
142+
owner_class: class_name.clone(),
143+
});
144+
}
145+
}
146+
}
116147
}
117-
// Get the underlying usize index for formatting
118-
let idx_usize = local_index.index();
119-
instrs.push(Instruction::Move {
120-
dest: format!("_{}", idx_usize),
121-
src: Operand::Constant(default.unwrap()),
122-
});
123148
}
124149

125150
// add instrs to the start of the entry block
126-
let entry_block = basic_blocks.get_mut(&entry_label).unwrap();
127-
entry_block.instructions.splice(0..0, instrs);*/
151+
if !instrs.is_empty() {
152+
let entry_block = basic_blocks.get_mut(&entry_label).unwrap();
153+
entry_block.instructions.splice(0..0, instrs);
154+
}
128155

129156
let codeblock = oomir::CodeBlock {
130157
basic_blocks,

0 commit comments

Comments
 (0)