Skip to content

Commit d1ecb5b

Browse files
committed
Support for mutable borrows (&mut).
Uses a cool trick with JVM arrays to get it work. Includes new integration test. Also in a previous commit accidentally replaced the ops test with a simpler version, restored it.
1 parent c91755d commit d1ecb5b

File tree

19 files changed

+827
-126
lines changed

19 files changed

+827
-126
lines changed

Readme.md

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ All examples live in `tests/binary` and are compiled to JVM bytecode & run/teste
4949
- Arrays & slices with nested indexing
5050
- Structs, tuples, enums (both C‑like and Rust‑style)
5151
- Executable `.jar` generation for binary crates
52+
- Mutable borrowing, references, and dereferencing
53+
- **Integration tests** for all features
5254

5355
🚧 **Next Milestone:** Full support for the Rust `core` crate.
5456

src/lower1/control_flow.rs

+142-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::oomir;
1010

1111
use rustc_middle::{
1212
mir::{
13-
BasicBlock, BasicBlockData, Body, Operand as MirOperand, Place, StatementKind,
13+
BasicBlock, BasicBlockData, Body, Local, Operand as MirOperand, Place, StatementKind,
1414
TerminatorKind,
1515
},
1616
ty::TyCtxt,
@@ -33,6 +33,8 @@ pub fn convert_basic_block<'tcx>(
3333
// Use the basic block index as its label.
3434
let label = format!("bb{}", bb.index());
3535
let mut instructions = Vec::new();
36+
let mut mutable_borrow_arrays: HashMap<Local, (Place<'tcx>, String, oomir::Type)> =
37+
HashMap::new();
3638

3739
// Convert each MIR statement in the block.
3840
for stmt in &bb_data.statements {
@@ -48,6 +50,53 @@ pub fn convert_basic_block<'tcx>(
4850
// Add instructions needed to calculate the Rvalue
4951
instructions.extend(rvalue_instructions);
5052

53+
if let rustc_middle::mir::Rvalue::Ref(
54+
_,
55+
rustc_middle::mir::BorrowKind::Mut { .. },
56+
borrowed_place,
57+
) = rvalue
58+
{
59+
// Check if the destination is a simple local (most common case for &mut assignment)
60+
if place.projection.is_empty() {
61+
if let oomir::Operand::Variable {
62+
name: array_var_name,
63+
ty: array_ty,
64+
} = &source_operand
65+
{
66+
// Extract element type from array type
67+
if let oomir::Type::MutableReference(element_ty) = array_ty {
68+
println!(
69+
"Info: Tracking mutable borrow array for place {:?} stored in local {:?}. Original: {:?}, ArrayVar: {}, ElementTy: {:?}",
70+
place, place.local, borrowed_place, array_var_name, element_ty
71+
);
72+
mutable_borrow_arrays.insert(
73+
place.local, // The local holding the array reference (e.g., _3)
74+
(
75+
borrowed_place.clone(), // The original place borrowed (e.g., _1)
76+
array_var_name.clone(), // The OOMIR name of the array var (e.g., "3_tmp0")
77+
*element_ty.clone(), // The type of the element in the array
78+
),
79+
);
80+
} else {
81+
println!(
82+
"Warning: Expected type for mutable borrow ref, found {:?}",
83+
array_ty
84+
);
85+
}
86+
} else {
87+
println!(
88+
"Warning: Expected variable operand for mutable borrow ref assignment result, found {:?}",
89+
source_operand
90+
);
91+
}
92+
} else {
93+
println!(
94+
"Warning: Mutable borrow assigned to complex place {:?}, write-back might not work correctly.",
95+
place
96+
);
97+
}
98+
}
99+
51100
// 2. Generate instructions to store the computed value into the destination place
52101
let assignment_instructions = emit_instructions_to_set_value(
53102
place, // The actual destination Place
@@ -158,25 +207,110 @@ pub fn convert_basic_block<'tcx>(
158207
} => {
159208
println!("the function name is {:?}", func);
160209
let function_name = make_jvm_safe(format!("{:?}", func).as_str()); // Get function name - needs refinement to extract actual name
161-
let oomir_args = args
162-
.iter()
163-
.map(|arg| convert_operand(&arg.node, tcx, mir, data_types, &mut instructions))
164-
.collect();
165-
let dest = Some(format!("{:?}", destination.local));
166210

211+
// --- Track Argument Origins ---
212+
// Store tuples: (Maybe Original MIR Place of Arg, OOMIR Operand for Arg)
213+
let mut processed_args: Vec<(Option<Place<'tcx>>, oomir::Operand)> = Vec::new();
214+
let mut pre_call_instructions = Vec::new(); // Instructions needed *before* the call for args
215+
216+
for arg in args {
217+
let mir_op = &arg.node;
218+
// Important: Pass pre_call_instructions here to collect setup code for this arg
219+
let oomir_op =
220+
convert_operand(mir_op, tcx, mir, data_types, &mut pre_call_instructions);
221+
222+
// Identify if the MIR operand is a direct use of a local Place
223+
let maybe_arg_place = match mir_op {
224+
MirOperand::Move(p) | MirOperand::Copy(p) if p.projection.is_empty() => {
225+
Some(p.clone())
226+
}
227+
_ => None,
228+
};
229+
processed_args.push((maybe_arg_place, oomir_op));
230+
}
231+
// Add instructions needed to prepare arguments *before* the call
232+
instructions.extend(pre_call_instructions);
233+
234+
// Collect just the OOMIR operands for the call itself
235+
let oomir_args: Vec<oomir::Operand> =
236+
processed_args.iter().map(|(_, op)| op.clone()).collect();
237+
238+
// Determine destination OOMIR variable name (if any)
239+
let dest_var_name = destination.projection.is_empty()
240+
.then(|| format!("_{}", destination.local.index()))
241+
.or_else(|| {
242+
println!("Warning: Call destination {:?} is complex, return value might be lost.", destination);
243+
None // Handle complex destinations if needed later
244+
});
245+
246+
// --- Emit the Call Instruction ---
167247
instructions.push(oomir::Instruction::Call {
168-
dest,
248+
dest: dest_var_name,
169249
function: function_name,
170250
args: oomir_args,
171251
});
172252

253+
let mut write_back_instrs = Vec::new();
254+
for (maybe_arg_place, oomir_arg_operand) in &processed_args {
255+
if let Some(arg_place) = maybe_arg_place {
256+
// Check if the local used for this argument is one we tracked as a mutable borrow array
257+
if let Some((original_place, array_var_name, element_ty)) =
258+
mutable_borrow_arrays.get(&arg_place.local)
259+
{
260+
// Double-check if the operand passed was indeed the variable we expected
261+
if let oomir::Operand::Variable {
262+
name: passed_var_name,
263+
..
264+
} = oomir_arg_operand
265+
{
266+
println!(
267+
"Info: Emitting write-back for mutable borrow. Arg Place: {:?}, Original Place: {:?}, Array Var: {}",
268+
arg_place, original_place, array_var_name
269+
);
270+
271+
// Create a temporary variable for the value read from the array
272+
let temp_writeback_var =
273+
format!("_writeback_{}", original_place.local.index());
274+
275+
// 1. Get value from array (using the tracked array_var_name)
276+
let array_operand = oomir::Operand::Variable {
277+
name: array_var_name.clone(),
278+
// Reconstruct array type for clarity (though not strictly needed by ArrayGet)
279+
ty: oomir::Type::Array(Box::new(element_ty.clone())),
280+
};
281+
write_back_instrs.push(oomir::Instruction::ArrayGet {
282+
dest: temp_writeback_var.clone(),
283+
array: array_operand,
284+
index: oomir::Operand::Constant(oomir::Constant::I32(0)), // Always index 0
285+
});
286+
287+
// 2. Set value back to original place
288+
let value_to_set = oomir::Operand::Variable {
289+
name: temp_writeback_var,
290+
ty: element_ty.clone(), // Use the tracked element type
291+
};
292+
let set_instrs = emit_instructions_to_set_value(
293+
original_place, // The original Place (_1)
294+
value_to_set, // The value read from the array
295+
tcx,
296+
mir,
297+
data_types,
298+
);
299+
write_back_instrs.extend(set_instrs);
300+
}
301+
}
302+
}
303+
}
304+
instructions.extend(write_back_instrs);
305+
306+
// --- Add Jump to Target Block (if call returns) ---
173307
if let Some(target_bb) = target {
174308
let target_label = format!("bb{}", target_bb.index());
175309
println!(
176310
"Info: Adding Jump to {} after Call in bb{}",
177311
target_label,
178312
bb.index()
179-
); // Add log
313+
);
180314
instructions.push(oomir::Instruction::Jump {
181315
target: target_label,
182316
});

src/lower1/control_flow/rvalue.rs

+88-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rustc_abi::FieldIdx;
22
use rustc_middle::{
3-
mir::{BinOp, Body, Operand as MirOperand, Place, Rvalue, UnOp},
3+
mir::{BinOp, Body, BorrowKind as MirBorrowKind, Operand as MirOperand, Place, Rvalue, UnOp},
44
ty::{ConstKind, TyCtxt, TyKind, inherent::ValueConst},
55
};
66
use std::collections::HashMap;
@@ -117,17 +117,87 @@ pub fn convert_rvalue_to_operand<'a>(
117117
result_operand = get_placeholder_operand(original_dest_place, mir, tcx, data_types);
118118
}
119119
}
120+
Rvalue::Ref(_region, borrow_kind, source_place) => {
121+
match borrow_kind {
122+
MirBorrowKind::Mut { .. } => {
123+
// --- MUTABLE BORROW (&mut T) -> Use Array Hack ---
124+
println!(
125+
"Info: Handling Rvalue::Ref(Mut) for place '{}' -> Temp Array Var",
126+
place_to_string(source_place, tcx)
127+
);
120128

121-
Rvalue::Ref(_region, _borrow_kind, source_place) => {
122-
// In many Java-like backends, a reference is often represented by the
123-
// value/object itself. Let's assume that for now.
124-
let (temp_var_name, get_instructions, temp_var_type) =
125-
emit_instructions_to_get_on_own(source_place, tcx, mir, data_types);
126-
instructions.extend(get_instructions);
127-
result_operand = oomir::Operand::Variable {
128-
name: temp_var_name,
129-
ty: temp_var_type, // Or potentially wrap in Ref type if needed
130-
};
129+
// 1. Get the value of the place being referenced (the 'pointee').
130+
let (pointee_value_var_name, pointee_get_instructions, pointee_oomir_type) =
131+
emit_instructions_to_get_on_own(source_place, tcx, mir, data_types);
132+
instructions.extend(pointee_get_instructions); // Add instructions to get the value
133+
134+
// 2. Determine the OOMIR type for the array reference itself.
135+
let array_ref_oomir_type =
136+
oomir::Type::MutableReference(Box::new(pointee_oomir_type.clone()));
137+
138+
// 3. Create a temporary variable name for the new array.
139+
let array_ref_var_name = generate_temp_var_name(&base_temp_name);
140+
141+
// 4. Emit instruction to allocate the single-element array (new T[1]).
142+
instructions.push(oomir::Instruction::NewArray {
143+
dest: array_ref_var_name.clone(),
144+
element_type: pointee_oomir_type.clone(),
145+
size: oomir::Operand::Constant(oomir::Constant::I32(1)),
146+
});
147+
148+
// 5. Emit instruction to store the pointee's value into the array's first element.
149+
let pointee_value_operand = oomir::Operand::Variable {
150+
name: pointee_value_var_name,
151+
ty: pointee_oomir_type,
152+
};
153+
instructions.push(oomir::Instruction::ArrayStore {
154+
array: array_ref_var_name.clone(),
155+
index: oomir::Operand::Constant(oomir::Constant::I32(0)),
156+
value: pointee_value_operand,
157+
});
158+
159+
// 6. The result is the reference to the newly created array.
160+
result_operand = oomir::Operand::Variable {
161+
name: array_ref_var_name,
162+
ty: array_ref_oomir_type,
163+
};
164+
println!(
165+
"Info: -> Temp Array Var '{}' ({:?})",
166+
result_operand.get_name().unwrap_or("<unknown>"),
167+
result_operand.get_type()
168+
);
169+
}
170+
171+
MirBorrowKind::Shared | MirBorrowKind::Fake { .. } => {
172+
// Treat Fake like Shared (used for closures etc.)
173+
// --- SHARED BORROW (&T) or others -> Pass Through Value ---
174+
println!(
175+
"Info: Handling Rvalue::Ref({:?}) for place '{}' -> Direct Value",
176+
borrow_kind,
177+
place_to_string(source_place, tcx)
178+
);
179+
180+
// 1. Get the value/reference of the place being borrowed directly.
181+
// `emit_instructions_to_get_on_own` handles loading/accessing the value.
182+
let (pointee_value_var_name, pointee_get_instructions, pointee_oomir_type) =
183+
emit_instructions_to_get_on_own(source_place, tcx, mir, data_types);
184+
185+
// 2. Add the instructions needed to get this value.
186+
instructions.extend(pointee_get_instructions);
187+
188+
// 3. The result *is* the operand representing the borrowed value itself.
189+
// No array wrapping is done.
190+
result_operand = oomir::Operand::Variable {
191+
name: pointee_value_var_name,
192+
ty: pointee_oomir_type,
193+
};
194+
println!(
195+
"Info: -> Direct Value Operand '{}' ({:?})",
196+
result_operand.get_name().unwrap_or("<unknown>"),
197+
result_operand.get_type()
198+
);
199+
}
200+
}
131201
}
132202

133203
Rvalue::Len(source_place) => {
@@ -795,6 +865,13 @@ pub fn convert_rvalue_to_operand<'a>(
795865
panic!("Discriminant on Ref to non-class type: {:?}", inner)
796866
}
797867
}
868+
oomir::Type::MutableReference(inner) => {
869+
if let oomir::Type::Class(name) = inner.as_ref() {
870+
name.clone()
871+
} else {
872+
panic!("Discriminant on MutableRef to non-class type: {:?}", inner)
873+
}
874+
}
798875
_ => panic!(
799876
"Discriminant on non-class type: {:?}",
800877
actual_value_oomir_type

src/lower1/operand.rs

+43-7
Original file line numberDiff line numberDiff line change
@@ -1070,14 +1070,50 @@ pub fn handle_const_value<'tcx>(
10701070
}
10711071
ConstValue::ZeroSized => {
10721072
println!("Info: Encountered ZeroSized constant for type {:?}", ty);
1073-
oomir::Operand::Constant(oomir::Constant::I32(0)) // Placeholder
1073+
oomir::Operand::Constant(oomir::Constant::I32(0))
10741074
}
1075-
ConstValue::Indirect {
1076-
alloc_id: _,
1077-
offset: _,
1078-
} => {
1079-
println!("Warning: Indirect constants not fully handled yet.");
1080-
oomir::Operand::Constant(oomir::Constant::I32(0)) // Placeholder
1075+
ConstValue::Indirect { alloc_id, offset } => {
1076+
println!(
1077+
"Info: Handling Indirect constant (AllocId: {:?}, Offset: {:?}, Type: {:?})",
1078+
alloc_id, offset, ty
1079+
);
1080+
// Get the allocation where the constant data resides
1081+
match tcx.global_alloc(alloc_id) {
1082+
rustc_middle::mir::interpret::GlobalAlloc::Memory(const_allocation) => {
1083+
let allocation = const_allocation.inner();
1084+
// Use the dedicated function to read the value from memory
1085+
match read_constant_value_from_memory(
1086+
tcx, allocation,
1087+
offset, // The offset provided by ConstValue::Indirect
1088+
*ty, // The type of the constant we are reading
1089+
data_types,
1090+
) {
1091+
Ok(oomir_const) => {
1092+
println!(
1093+
"Info: Successfully extracted indirect constant: {:?}",
1094+
oomir_const
1095+
);
1096+
oomir::Operand::Constant(oomir_const)
1097+
}
1098+
Err(e) => {
1099+
println!(
1100+
"Error: Failed to read indirect constant of type {:?} from allocation {:?}: {}",
1101+
ty, alloc_id, e
1102+
);
1103+
// Return an error placeholder, maybe distinct from other errors
1104+
oomir::Operand::Constant(oomir::Constant::I64(-60))
1105+
}
1106+
}
1107+
}
1108+
other_alloc => {
1109+
// This case should be rare for constants defined in code, but handle it defensively.
1110+
println!(
1111+
"Warning: Indirect constant points to unexpected GlobalAlloc kind: {:?}. AllocId: {:?}, Type: {:?}",
1112+
other_alloc, alloc_id, ty
1113+
);
1114+
oomir::Operand::Constant(oomir::Constant::I64(-61))
1115+
}
1116+
}
10811117
}
10821118
}
10831119
}

0 commit comments

Comments
 (0)