1use crate::lcnf::*;
6use std::collections::HashMap;
7
8use super::types::{
9 ConstantPool, JVMAnalysisCache, JVMConstantFoldingHelper, JVMDepGraph, JVMDominatorTree,
10 JVMExtCache, JVMExtConstFolder, JVMExtDepGraph, JVMExtDomTree, JVMExtLiveness,
11 JVMExtPassConfig, JVMExtPassPhase, JVMExtPassRegistry, JVMExtPassStats, JVMExtWorklist,
12 JVMLivenessInfo, JVMPassConfig, JVMPassPhase, JVMPassRegistry, JVMPassStats, JVMWorklist,
13 JvmBackend, JvmClass, JvmCodegenError, JvmField, JvmOpcode, JvmType, MethodDescriptor,
14};
15
16pub mod access_flags {
18 pub const PUBLIC: u16 = 0x0001;
19 pub const PRIVATE: u16 = 0x0002;
20 pub const PROTECTED: u16 = 0x0004;
21 pub const STATIC: u16 = 0x0008;
22 pub const FINAL: u16 = 0x0010;
23 pub const SUPER: u16 = 0x0020;
24 pub const SYNCHRONIZED: u16 = 0x0020;
25 pub const VOLATILE: u16 = 0x0040;
26 pub const TRANSIENT: u16 = 0x0080;
27 pub const NATIVE: u16 = 0x0100;
28 pub const INTERFACE: u16 = 0x0200;
29 pub const ABSTRACT: u16 = 0x0400;
30 pub const STRICT: u16 = 0x0800;
31 pub const SYNTHETIC: u16 = 0x1000;
32 pub const ANNOTATION: u16 = 0x2000;
33 pub const ENUM: u16 = 0x4000;
34}
35pub type JvmResult<T> = Result<T, JvmCodegenError>;
36#[cfg(test)]
37mod tests {
38 use super::*;
39 #[test]
40 pub(super) fn test_jvm_type_descriptor() {
41 assert_eq!(JvmType::Int.descriptor(), "I");
42 assert_eq!(JvmType::Long.descriptor(), "J");
43 assert_eq!(JvmType::Double.descriptor(), "D");
44 assert_eq!(JvmType::Boolean.descriptor(), "Z");
45 assert_eq!(JvmType::Void.descriptor(), "V");
46 assert_eq!(
47 JvmType::Object("java/lang/String".to_string()).descriptor(),
48 "Ljava/lang/String;"
49 );
50 assert_eq!(JvmType::Array(Box::new(JvmType::Int)).descriptor(), "[I");
51 assert_eq!(
52 JvmType::Generic("T".to_string()).descriptor(),
53 "Ljava/lang/Object;"
54 );
55 }
56 #[test]
57 pub(super) fn test_jvm_type_slot_size_and_predicates() {
58 assert_eq!(JvmType::Int.slot_size(), 1);
59 assert_eq!(JvmType::Long.slot_size(), 2);
60 assert_eq!(JvmType::Double.slot_size(), 2);
61 assert_eq!(JvmType::Object("Foo".to_string()).slot_size(), 1);
62 assert!(JvmType::Long.is_wide());
63 assert!(!JvmType::Int.is_wide());
64 assert!(JvmType::Object("Foo".to_string()).is_reference());
65 assert!(JvmType::Int.is_int_category());
66 assert!(!JvmType::Long.is_int_category());
67 }
68 #[test]
69 pub(super) fn test_method_descriptor_rendering() {
70 let md = MethodDescriptor::new(
71 vec![
72 JvmType::Int,
73 JvmType::Object("java/lang/String".to_string()),
74 ],
75 JvmType::Void,
76 );
77 assert_eq!(md.to_string(), "(ILjava/lang/String;)V");
78 let md2 = MethodDescriptor::new(vec![], JvmType::Long);
79 assert_eq!(md2.to_string(), "()J");
80 }
81 #[test]
82 pub(super) fn test_constant_pool_deduplication() {
83 let mut cp = ConstantPool::new();
84 let idx1 = cp.utf8("hello");
85 let idx2 = cp.utf8("hello");
86 let idx3 = cp.utf8("world");
87 assert_eq!(idx1, idx2);
88 assert_ne!(idx1, idx3);
89 assert_eq!(cp.entries().len(), 2);
90 }
91 #[test]
92 pub(super) fn test_jvm_class_construction_and_summary() {
93 let mut cls = JvmClass::new("com/example/Foo");
94 cls.set_superclass("com/example/Base");
95 cls.add_interface("com/example/IFoo");
96 cls.add_field(JvmField::new("count", &JvmType::Int, access_flags::PRIVATE));
97 let backend = JvmBackend::default_backend();
98 let init = backend.emit_default_init("com/example/Base");
99 cls.add_method(init);
100 assert_eq!(cls.name, "com/example/Foo");
101 assert_eq!(cls.superclass, "com/example/Base");
102 assert_eq!(cls.interfaces.len(), 1);
103 assert_eq!(cls.fields.len(), 1);
104 assert_eq!(cls.methods.len(), 1);
105 let summary = cls.summary();
106 assert!(summary.contains("com/example/Foo"));
107 assert!(summary.contains("com/example/Base"));
108 assert!(summary.contains("IFoo"));
109 assert!(summary.contains("count"));
110 }
111 #[test]
112 pub(super) fn test_emit_binop() {
113 let backend = JvmBackend::default_backend();
114 let add = backend.emit_binop("+").expect("add emit should succeed");
115 assert!(matches!(add.opcode, JvmOpcode::Iadd));
116 let mul = backend.emit_binop("mul").expect("mul emit should succeed");
117 assert!(matches!(mul.opcode, JvmOpcode::Imul));
118 let ladd = backend
119 .emit_binop("ladd")
120 .expect("ladd emit should succeed");
121 assert!(matches!(ladd.opcode, JvmOpcode::Ladd));
122 let err = backend.emit_binop("unknown");
123 assert!(err.is_err());
124 }
125 #[test]
126 pub(super) fn test_emit_load_store_return() {
127 let backend = JvmBackend::default_backend();
128 let load_int = backend.emit_load(2, &JvmType::Int);
129 assert!(matches!(load_int.opcode, JvmOpcode::Iload(2)));
130 let load_ref = backend.emit_load(0, &JvmType::Object("Foo".to_string()));
131 assert!(matches!(load_ref.opcode, JvmOpcode::Aload(0)));
132 let store_long = backend.emit_store(1, &JvmType::Long);
133 assert!(matches!(store_long.opcode, JvmOpcode::Lstore(1)));
134 let ret_void = backend.emit_return(&JvmType::Void);
135 assert!(matches!(ret_void.opcode, JvmOpcode::Return_));
136 let ret_double = backend.emit_return(&JvmType::Double);
137 assert!(matches!(ret_double.opcode, JvmOpcode::Dreturn));
138 let ret_ref = backend.emit_return(&JvmType::Object("X".to_string()));
139 assert!(matches!(ret_ref.opcode, JvmOpcode::Areturn));
140 }
141 #[test]
142 pub(super) fn test_emit_new_default_sequence() {
143 let backend = JvmBackend::default_backend();
144 let instrs = backend.emit_new_default("com/example/Foo");
145 assert_eq!(instrs.len(), 3);
146 assert!(matches!(instrs[0].opcode, JvmOpcode::New(_)));
147 assert!(matches!(instrs[1].opcode, JvmOpcode::Dup));
148 assert!(matches!(instrs[2].opcode, JvmOpcode::Invokespecial { .. }));
149 }
150 #[test]
151 pub(super) fn test_emit_clinit() {
152 let backend = JvmBackend::default_backend();
153 let clinit = backend.emit_clinit("com/example/Singleton");
154 assert_eq!(clinit.name, "<clinit>");
155 assert_eq!(clinit.descriptor, "()V");
156 assert!(clinit.access_flags & access_flags::STATIC != 0);
157 assert!(!clinit.code.is_empty());
158 assert!(matches!(
159 clinit
160 .code
161 .last()
162 .expect("opcode should be accessible")
163 .opcode,
164 JvmOpcode::Return_
165 ));
166 }
167 #[test]
168 pub(super) fn test_emit_fun_decl() {
169 let mut backend = JvmBackend::default_backend();
170 let decl = LcnfFunDecl {
171 name: "Main_hello".to_string(),
172 original_name: None,
173 params: vec![],
174 ret_type: LcnfType::Nat,
175 body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(42))),
176 is_recursive: false,
177 is_lifted: false,
178 inline_cost: 1,
179 };
180 let cls = backend
181 .emit_fun_decl(&decl)
182 .expect("cls emit should succeed");
183 assert!(cls.name.contains("hello"));
184 assert!(!cls.methods.is_empty());
185 let apply = cls.methods.iter().find(|m| m.name == "apply");
186 assert!(apply.is_some());
187 let m = apply.expect("m should be Some/Ok");
188 assert!(m.access_flags & access_flags::STATIC != 0);
189 }
190}
191#[cfg(test)]
192mod JVM_infra_tests {
193 use super::*;
194 #[test]
195 pub(super) fn test_pass_config() {
196 let config = JVMPassConfig::new("test_pass", JVMPassPhase::Transformation);
197 assert!(config.enabled);
198 assert!(config.phase.is_modifying());
199 assert_eq!(config.phase.name(), "transformation");
200 }
201 #[test]
202 pub(super) fn test_pass_stats() {
203 let mut stats = JVMPassStats::new();
204 stats.record_run(10, 100, 3);
205 stats.record_run(20, 200, 5);
206 assert_eq!(stats.total_runs, 2);
207 assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
208 assert!((stats.success_rate() - 1.0).abs() < 0.01);
209 let s = stats.format_summary();
210 assert!(s.contains("Runs: 2/2"));
211 }
212 #[test]
213 pub(super) fn test_pass_registry() {
214 let mut reg = JVMPassRegistry::new();
215 reg.register(JVMPassConfig::new("pass_a", JVMPassPhase::Analysis));
216 reg.register(JVMPassConfig::new("pass_b", JVMPassPhase::Transformation).disabled());
217 assert_eq!(reg.total_passes(), 2);
218 assert_eq!(reg.enabled_count(), 1);
219 reg.update_stats("pass_a", 5, 50, 2);
220 let stats = reg.get_stats("pass_a").expect("stats should exist");
221 assert_eq!(stats.total_changes, 5);
222 }
223 #[test]
224 pub(super) fn test_analysis_cache() {
225 let mut cache = JVMAnalysisCache::new(10);
226 cache.insert("key1".to_string(), vec![1, 2, 3]);
227 assert!(cache.get("key1").is_some());
228 assert!(cache.get("key2").is_none());
229 assert!((cache.hit_rate() - 0.5).abs() < 0.01);
230 cache.invalidate("key1");
231 assert!(!cache.entries["key1"].valid);
232 assert_eq!(cache.size(), 1);
233 }
234 #[test]
235 pub(super) fn test_worklist() {
236 let mut wl = JVMWorklist::new();
237 assert!(wl.push(1));
238 assert!(wl.push(2));
239 assert!(!wl.push(1));
240 assert_eq!(wl.len(), 2);
241 assert_eq!(wl.pop(), Some(1));
242 assert!(!wl.contains(1));
243 assert!(wl.contains(2));
244 }
245 #[test]
246 pub(super) fn test_dominator_tree() {
247 let mut dt = JVMDominatorTree::new(5);
248 dt.set_idom(1, 0);
249 dt.set_idom(2, 0);
250 dt.set_idom(3, 1);
251 assert!(dt.dominates(0, 3));
252 assert!(dt.dominates(1, 3));
253 assert!(!dt.dominates(2, 3));
254 assert!(dt.dominates(3, 3));
255 }
256 #[test]
257 pub(super) fn test_liveness() {
258 let mut liveness = JVMLivenessInfo::new(3);
259 liveness.add_def(0, 1);
260 liveness.add_use(1, 1);
261 assert!(liveness.defs[0].contains(&1));
262 assert!(liveness.uses[1].contains(&1));
263 }
264 #[test]
265 pub(super) fn test_constant_folding() {
266 assert_eq!(JVMConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
267 assert_eq!(JVMConstantFoldingHelper::fold_div_i64(10, 0), None);
268 assert_eq!(JVMConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
269 assert_eq!(
270 JVMConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
271 0b1000
272 );
273 assert_eq!(JVMConstantFoldingHelper::fold_bitnot_i64(0), -1);
274 }
275 #[test]
276 pub(super) fn test_dep_graph() {
277 let mut g = JVMDepGraph::new();
278 g.add_dep(1, 2);
279 g.add_dep(2, 3);
280 g.add_dep(1, 3);
281 assert_eq!(g.dependencies_of(2), vec![1]);
282 let topo = g.topological_sort();
283 assert_eq!(topo.len(), 3);
284 assert!(!g.has_cycle());
285 let pos: std::collections::HashMap<u32, usize> =
286 topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
287 assert!(pos[&1] < pos[&2]);
288 assert!(pos[&1] < pos[&3]);
289 assert!(pos[&2] < pos[&3]);
290 }
291}
292#[cfg(test)]
293mod jvmext_pass_tests {
294 use super::*;
295 #[test]
296 pub(super) fn test_jvmext_phase_order() {
297 assert_eq!(JVMExtPassPhase::Early.order(), 0);
298 assert_eq!(JVMExtPassPhase::Middle.order(), 1);
299 assert_eq!(JVMExtPassPhase::Late.order(), 2);
300 assert_eq!(JVMExtPassPhase::Finalize.order(), 3);
301 assert!(JVMExtPassPhase::Early.is_early());
302 assert!(!JVMExtPassPhase::Early.is_late());
303 }
304 #[test]
305 pub(super) fn test_jvmext_config_builder() {
306 let c = JVMExtPassConfig::new("p")
307 .with_phase(JVMExtPassPhase::Late)
308 .with_max_iter(50)
309 .with_debug(1);
310 assert_eq!(c.name, "p");
311 assert_eq!(c.max_iterations, 50);
312 assert!(c.is_debug_enabled());
313 assert!(c.enabled);
314 let c2 = c.disabled();
315 assert!(!c2.enabled);
316 }
317 #[test]
318 pub(super) fn test_jvmext_stats() {
319 let mut s = JVMExtPassStats::new();
320 s.visit();
321 s.visit();
322 s.modify();
323 s.iterate();
324 assert_eq!(s.nodes_visited, 2);
325 assert_eq!(s.nodes_modified, 1);
326 assert!(s.changed);
327 assert_eq!(s.iterations, 1);
328 let e = s.efficiency();
329 assert!((e - 0.5).abs() < 1e-9);
330 }
331 #[test]
332 pub(super) fn test_jvmext_registry() {
333 let mut r = JVMExtPassRegistry::new();
334 r.register(JVMExtPassConfig::new("a").with_phase(JVMExtPassPhase::Early));
335 r.register(JVMExtPassConfig::new("b").disabled());
336 assert_eq!(r.len(), 2);
337 assert_eq!(r.enabled_passes().len(), 1);
338 assert_eq!(r.passes_in_phase(&JVMExtPassPhase::Early).len(), 1);
339 }
340 #[test]
341 pub(super) fn test_jvmext_cache() {
342 let mut c = JVMExtCache::new(4);
343 assert!(c.get(99).is_none());
344 c.put(99, vec![1, 2, 3]);
345 let v = c.get(99).expect("v should be present in map");
346 assert_eq!(v, &[1u8, 2, 3]);
347 assert!(c.hit_rate() > 0.0);
348 assert_eq!(c.live_count(), 1);
349 }
350 #[test]
351 pub(super) fn test_jvmext_worklist() {
352 let mut w = JVMExtWorklist::new(10);
353 w.push(5);
354 w.push(3);
355 w.push(5);
356 assert_eq!(w.len(), 2);
357 assert!(w.contains(5));
358 let first = w.pop().expect("first should be available to pop");
359 assert!(!w.contains(first));
360 }
361 #[test]
362 pub(super) fn test_jvmext_dom_tree() {
363 let mut dt = JVMExtDomTree::new(5);
364 dt.set_idom(1, 0);
365 dt.set_idom(2, 0);
366 dt.set_idom(3, 1);
367 dt.set_idom(4, 1);
368 assert!(dt.dominates(0, 3));
369 assert!(dt.dominates(1, 4));
370 assert!(!dt.dominates(2, 3));
371 assert_eq!(dt.depth_of(3), 2);
372 }
373 #[test]
374 pub(super) fn test_jvmext_liveness() {
375 let mut lv = JVMExtLiveness::new(3);
376 lv.add_def(0, 1);
377 lv.add_use(1, 1);
378 assert!(lv.var_is_def_in_block(0, 1));
379 assert!(lv.var_is_used_in_block(1, 1));
380 assert!(!lv.var_is_def_in_block(1, 1));
381 }
382 #[test]
383 pub(super) fn test_jvmext_const_folder() {
384 let mut cf = JVMExtConstFolder::new();
385 assert_eq!(cf.add_i64(3, 4), Some(7));
386 assert_eq!(cf.div_i64(10, 0), None);
387 assert_eq!(cf.mul_i64(6, 7), Some(42));
388 assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
389 assert_eq!(cf.fold_count(), 3);
390 assert_eq!(cf.failure_count(), 1);
391 }
392 #[test]
393 pub(super) fn test_jvmext_dep_graph() {
394 let mut g = JVMExtDepGraph::new(4);
395 g.add_edge(0, 1);
396 g.add_edge(1, 2);
397 g.add_edge(2, 3);
398 assert!(!g.has_cycle());
399 assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
400 assert_eq!(g.reachable(0).len(), 4);
401 let sccs = g.scc();
402 assert_eq!(sccs.len(), 4);
403 }
404}