Skip to content

Commit 87f25ce

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 87f25ce

15 files changed

+310
-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

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[build]
2+
rustflags = [
3+
"-Z", "codegen-backend=/Users/mreeve27/rustc_codegen_jvm/target/debug/librustc_codegen_jvm.dylib",
4+
"-C", "linker=/Users/mreeve27/rustc_codegen_jvm/java-linker/target/debug/java-linker",
5+
"-C", "link-args=/Users/mreeve27/rustc_codegen_jvm/library/build/distributions/library-0.1.0/lib/library-0.1.0.jar /Users/mreeve27/rustc_codegen_jvm/library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar --asm-processor /Users/mreeve27/rustc_codegen_jvm/asm-processor/build/libs/asm-processor-1.0-SNAPSHOT-all.jar --known-good kotlin-stdlib"
6+
]
7+
8+
# Throwing a JVM exception will unwind and give a stack trace, no need for rust to handle unwinding.
9+
[profile.debug]
10+
panic = "abort"
11+
12+
[profile.release]
13+
panic = "abort"

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+
}

0 commit comments

Comments
 (0)