Skip to content

Commit 383e6a1

Browse files
committed
Implement full support for enums (C-like), including a new test.
Support for them having anonymous structs or tuples is coming in the next commit. :)
1 parent c4babb4 commit 383e6a1

15 files changed

+298
-12
lines changed

Readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ This backend currently supports a subset of Rust features:
4343
* ✅ Variable assignment including subfield and array index assignment, including nesting.
4444
* ✅ Arrays and slices, including inserting, accessing and mutating at a given index (supporting nesting).
4545
* ✅ Floats (`f32`, `f64`).
46-
* ✅ Structs and Tuples (enums and unions coming soon!) including nesting access/setting/mutation of fields.
46+
* ✅ Structs, Tuples and Enums (currently only C-like enums, no anonymous structs or tuples yet) including nesting access/setting/mutation of fields.
4747
* ✅ Generating executable `.jar` files for binary crates.
4848

4949
### Next Milestone:

src/lower1/control_flow.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,13 @@ pub fn convert_basic_block<'tcx>(
353353
target: target_label,
354354
});
355355
}
356-
// Other terminator kinds (like Resume, etc.) can be added as needed.
356+
TerminatorKind::Unreachable => {
357+
instructions.push(oomir::Instruction::ThrowNewWithMessage {
358+
exception_class: "java/lang/RuntimeException".to_string(),
359+
message: "Unreachable code reached".to_string(),
360+
});
361+
}
362+
// Other terminator kinds (like Resume, etc.) will be added as needed.
357363
_ => {
358364
println!("Warning: Unhandled terminator {:?}", terminator.kind);
359365
}

src/lower1/control_flow/rvalue.rs

+64
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use rustc_abi::FieldIdx;
12
use rustc_middle::{
23
mir::{BinOp, Body, Operand as MirOperand, Place, Rvalue, UnOp},
34
ty::{ConstKind, TyCtxt, TyKind, inherent::ValueConst},
@@ -655,6 +656,30 @@ pub fn convert_rvalue_to_operand<'a>(
655656
}
656657

657658
// Construct the enum variant object
659+
instructions.push(oomir::Instruction::ConstructObject {
660+
dest: temp_aggregate_var.clone(),
661+
class_name: variant_class_name.clone(),
662+
});
663+
664+
// Set fields
665+
for (i, field) in variant_def.fields.iter().enumerate() {
666+
let field_name = format!("field{}", i);
667+
let field_mir_ty = field.ty(tcx, substs);
668+
let field_oomir_type = ty_to_oomir_type(field_mir_ty, tcx, data_types);
669+
let value_operand = convert_operand(
670+
&operands[FieldIdx::from_usize(i)],
671+
tcx,
672+
mir,
673+
data_types,
674+
);
675+
instructions.push(oomir::Instruction::SetField {
676+
object_var: temp_aggregate_var.clone(),
677+
field_name,
678+
value: value_operand,
679+
field_ty: field_oomir_type,
680+
owner_class: variant_class_name.clone(),
681+
});
682+
}
658683
} else {
659684
// Union
660685
println!("Warning: Unhandled ADT Aggregate Kind -> Temp Placeholder");
@@ -726,7 +751,46 @@ pub fn convert_rvalue_to_operand<'a>(
726751
}
727752
}
728753
}
754+
Rvalue::Discriminant(place) => {
755+
// get the discriminate of the place (usually an enum)
756+
// so basically just call `getVariantIdx` on the class in the place
757+
758+
let place_name = place_to_string(place, tcx);
759+
let place_type = get_place_type(place, mir, tcx, data_types);
760+
761+
let temp_discriminant_var = generate_temp_var_name(&base_temp_name);
762+
let place_class_name = match place_type.clone() {
763+
oomir::Type::Class(name) => name.clone(),
764+
_ => panic!("Discriminant on non-class type {:?}", place),
765+
};
766+
let place_class = data_types.get(&place_class_name).unwrap();
767+
let method_name = "getVariantIdx".to_string();
768+
let method_return_tuple = place_class.methods.get(&method_name).unwrap();
769+
let method_return_type = method_return_tuple.0.clone();
770+
771+
let method_ty = oomir::Signature {
772+
params: vec![],
773+
ret: Box::new(method_return_type.clone()),
774+
};
729775

776+
// we can InvokeStatic as lower2 makes the field index method static (it doesn't depend on fields at all)
777+
instructions.push(oomir::Instruction::InvokeVirtual {
778+
class_name: place_class_name.clone(),
779+
method_name,
780+
args: vec![],
781+
dest: Some(temp_discriminant_var.clone()),
782+
method_ty,
783+
operand: oomir::Operand::Variable {
784+
name: place_name,
785+
ty: place_type,
786+
},
787+
});
788+
789+
result_operand = oomir::Operand::Variable {
790+
name: temp_discriminant_var,
791+
ty: method_return_type,
792+
};
793+
}
730794
// Handle other Rvalue variants by generating a placeholder
731795
_ => {
732796
println!(

src/lower1/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ pub fn mir_int_to_oomir_const<'tcx>(
215215
IntTy::I16 => oomir::Constant::I16(value as i16), // Be careful
216216
IntTy::I32 => oomir::Constant::I32(value as i32), // Be careful
217217
IntTy::I64 => oomir::Constant::I64(value as i64),
218-
IntTy::Isize => oomir::Constant::I64(value as i64), // Assuming 64-bit target
218+
IntTy::Isize => oomir::Constant::I32(value as i32), // JVM uses i32 for most "usize" tasks
219219
IntTy::I128 => oomir::Constant::I64(value as i64), // Truncate for JVM compatibility? Or error?
220220
},
221221
TyKind::Uint(uint_ty) => match uint_ty {

src/lower2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ pub fn oomir_to_jvm_bytecode(
131131
let dt_bytecode = create_data_type_classfile(
132132
&dt_name_oomir,
133133
data_type,
134-
"java/lang/Object", // Superclass
134+
data_type.super_class.as_deref().unwrap_or("java/lang/Object"),
135135
)?;
136136
generated_classes.insert(dt_name_oomir.clone(), dt_bytecode);
137137
}

src/lower2/jvm_gen.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,7 @@ pub(super) fn create_data_type_classfile(
8989

9090
let this_class_index = cp.add_class(class_name_jvm)?;
9191

92-
let super_class_from_data_type = &data_type.super_class;
93-
let super_class_index;
94-
if let Some(super_class) = super_class_from_data_type {
95-
super_class_index = cp.add_class(super_class)?;
96-
} else {
97-
super_class_index = cp.add_class(super_class_name_jvm)?;
98-
}
92+
let super_class_index = cp.add_class(super_class_name_jvm)?;
9993

10094
// --- Create Fields ---
10195
let mut fields: Vec<jvm::Field> = Vec::new();
@@ -165,7 +159,6 @@ pub(super) fn create_data_type_classfile(
165159

166160
let jvm_method = jvm::Method {
167161
access_flags: MethodAccessFlags::PUBLIC
168-
| MethodAccessFlags::STATIC
169162
| if is_abstract {
170163
MethodAccessFlags::ABSTRACT
171164
} else {

src/lower2/translator.rs

+44
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,50 @@ impl<'a, 'cp> FunctionTranslator<'a, 'cp> {
16131613
// The type for storage is the new type (ty)
16141614
self.store_result(dest, ty)?; // Stack: []
16151615
}
1616+
1617+
OI::InvokeVirtual {
1618+
dest,
1619+
class_name,
1620+
method_name,
1621+
method_ty,
1622+
args,
1623+
operand,
1624+
} => {
1625+
// 1. Add Method reference to constant pool
1626+
let class_index = self.constant_pool.add_class(class_name)?;
1627+
let method_ref_index = self.constant_pool.add_method_ref(
1628+
class_index,
1629+
method_name,
1630+
&method_ty.to_string(),
1631+
)?;
1632+
1633+
// 2. Load arguments onto the stack
1634+
for arg in args {
1635+
self.load_call_argument(arg)?; // Use helper to handle references properly
1636+
}
1637+
1638+
// 3. Load the object reference (self) onto the stack
1639+
// The object reference is the first argument for instance methods
1640+
self.load_operand(operand)?; // Stack: [object_ref, args...]
1641+
1642+
// 4. Emit 'invokevirtual' instruction
1643+
self.jvm_instructions
1644+
.push(JI::Invokevirtual(method_ref_index)); // Stack: [result]
1645+
// Note: The result type is determined by the method signature
1646+
1647+
// 5. Handle the return value
1648+
if let Some(dest_var) = dest {
1649+
// Store the result in the destination variable
1650+
self.store_result(dest_var, &method_ty.ret)?;
1651+
} else if *method_ty.ret.as_ref() != oomir::Type::Void {
1652+
// Pop the result if it's not void and no destination is provided
1653+
match get_type_size(&method_ty.ret) {
1654+
1 => self.jvm_instructions.push(JI::Pop),
1655+
2 => self.jvm_instructions.push(JI::Pop2),
1656+
_ => {}
1657+
}
1658+
}
1659+
}
16161660
}
16171661
Ok(())
16181662
}

src/oomir.rs

+8
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,14 @@ pub enum Instruction {
240240
ty: Type,
241241
dest: String, // Destination variable for the casted value
242242
},
243+
InvokeVirtual {
244+
dest: Option<String>, // Optional destination variable for the return value
245+
class_name: String, // JVM class name (e.g., MyStruct)
246+
method_name: String, // Name of the method to call
247+
method_ty: Signature, // Signature of the method (input/output types)
248+
args: Vec<Operand>, // Arguments to the function
249+
operand: Operand, // The object reference (this) for the method call
250+
},
243251
}
244252

245253
#[derive(Debug, Clone)]

tests/binary/enums/.cargo/config.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../config.toml

tests/binary/enums/Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/binary/enums/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "enums"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]

tests/binary/enums/java-output.expected

Whitespace-only changes.

tests/binary/enums/no_jvm_target.flag

Whitespace-only changes.

tests/binary/enums/src/main.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
enum Test {
2+
A,
3+
B,
4+
C,
5+
}
6+
7+
fn main() {
8+
let mut test_enum = Test::A;
9+
10+
match test_enum {
11+
Test::A => { /* correct */},
12+
Test::B => panic!("Enum should be A, not B"),
13+
Test::C => panic!("Enum shuld be A, not C"),
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#[derive(PartialEq, Clone, Debug)]
2+
struct ConfigData {
3+
id: u32,
4+
enabled: bool,
5+
params: (f32, f32), // Nested tuple
6+
}
7+
8+
#[derive(PartialEq, Clone, Debug)]
9+
enum ComplexEnum {
10+
SimpleVariant, // No data
11+
Count(i64), // Single primitive data
12+
Coords((i32, i32, i32)), // Tuple data
13+
UserData { name: String, age: u8 }, // Struct-like variant
14+
RawData([u8; 8]), // Array data
15+
Settings(ConfigData), // Struct data
16+
}
17+
18+
fn main() {
19+
// Initialize with one variant
20+
let mut current_state = ComplexEnum::UserData {
21+
name: "Alice".to_string(),
22+
age: 30,
23+
};
24+
25+
// Access initial values using match
26+
match current_state {
27+
ComplexEnum::UserData { ref name, age } => {
28+
assert!(name == "Alice", "Initial name should be Alice");
29+
assert!(age == 30, "Initial age should be 30");
30+
}
31+
_ => panic!("Initial state should be UserData!"),
32+
}
33+
34+
// Access initial values using if let
35+
if let ComplexEnum::UserData { name, .. } = current_state {
36+
assert!(name.starts_with('A'), "Name should start with A");
37+
} else {
38+
panic!("Still expect UserData here!");
39+
}
40+
41+
// Mutate the enum variable to a different variant (Settings)
42+
current_state = ComplexEnum::Settings(ConfigData {
43+
id: 123,
44+
enabled: true,
45+
params: (1.0, -0.5),
46+
});
47+
48+
// Access nested values in the new variant
49+
match current_state {
50+
ComplexEnum::Settings(config) => {
51+
assert!(config.id == 123, "Settings ID should be 123");
52+
assert!(config.enabled, "Settings should be enabled");
53+
assert!(config.params.0 == 1.0, "Settings params.0 should be 1.0");
54+
assert!(config.params.1 == -0.5, "Settings params.1 should be -0.5");
55+
}
56+
_ => panic!("State should now be Settings!"),
57+
}
58+
59+
// Mutate the enum variable to a different variant (RawData)
60+
current_state = ComplexEnum::RawData([0, 1, 2, 3, 4, 5, 6, 7]);
61+
62+
// Mutate data *inside* the RawData variant
63+
let mut mutated_internally = false;
64+
match &mut current_state {
65+
// Use mutable borrow (&mut) to modify internal data
66+
ComplexEnum::RawData(data_array) => {
67+
assert!(data_array[0] == 0, "RawData[0] should be 0 initially");
68+
assert!(data_array[7] == 7, "RawData[7] should be 7 initially");
69+
70+
// Modify elements
71+
data_array[0] = 100;
72+
data_array[7] = 200;
73+
data_array[1] *= 5; // Modify based on existing value
74+
75+
mutated_internally = true;
76+
}
77+
_ => { /* No mutation needed for other branches in this step */ }
78+
}
79+
assert!(mutated_internally, "Internal mutation should have happened");
80+
81+
// Assert internal mutations in RawData
82+
if let ComplexEnum::RawData(data_array) = current_state {
83+
assert!(data_array[0] == 100, "RawData[0] should now be 100");
84+
assert!(data_array[1] == 5, "RawData[1] should now be 5 (1*5)");
85+
assert!(data_array[7] == 200, "RawData[7] should now be 200");
86+
} else {
87+
panic!("State should still be RawData after internal mutation!");
88+
}
89+
90+
// Mutate data *inside* the nested ConfigData struct within Settings variant
91+
current_state = ComplexEnum::Settings(ConfigData {
92+
// Reset state
93+
id: 999,
94+
enabled: false,
95+
params: (0.0, 0.0),
96+
});
97+
98+
match &mut current_state {
99+
ComplexEnum::Settings(config) => {
100+
config.enabled = true; // Mutate bool field
101+
config.params.1 = config.params.0 + 10.0; // Mutate tuple field
102+
config.id += 1; // Mutate id field
103+
}
104+
_ => panic!("State should be Settings for nested mutation!"),
105+
}
106+
107+
// Assert internal nested mutations
108+
match current_state {
109+
ComplexEnum::Settings(config) => {
110+
assert!(config.id == 1000, "ConfigData id should be 1000");
111+
assert!(config.enabled, "ConfigData enabled should be true");
112+
assert!(
113+
config.params.0 == 0.0,
114+
"ConfigData params.0 should be unchanged"
115+
);
116+
assert!(config.params.1 == 10.0, "ConfigData params.1 should be 10.0");
117+
}
118+
_ => panic!("State should still be Settings after nested mutation!"),
119+
}
120+
121+
// Test remaining variants
122+
current_state = ComplexEnum::Count(5000);
123+
assert!(current_state == ComplexEnum::Count(5000));
124+
if let ComplexEnum::Count(c) = current_state {
125+
assert!(c == 5000);
126+
} else {
127+
panic!("State should be Count");
128+
}
129+
130+
current_state = ComplexEnum::Coords((-10, 0, 20));
131+
assert!(current_state == ComplexEnum::Coords((-10, 0, 20)));
132+
if let ComplexEnum::Coords((x, y, z)) = current_state {
133+
assert!(x == -10);
134+
assert!(y == 0);
135+
assert!(z == 20);
136+
} else {
137+
panic!("State should be Coords");
138+
}
139+
140+
current_state = ComplexEnum::SimpleVariant;
141+
assert!(current_state == ComplexEnum::SimpleVariant);
142+
}

0 commit comments

Comments
 (0)