irvm_lower/
lib.rs

1//! ## irmv-lower
2//!
3//! Lower irvm IR to other IRs.
4//!
5//! This crates currently only allows you to lower irmv IR to LLVM IR.
6//!
7//! See the [`llvm`] submodule for more information.
8//!
9//! ```bash
10//! cargo add irvm-lower
11//! ```
12//!
13
14pub mod llvm;
15
16#[cfg(test)]
17mod test {
18
19    use irvm::{
20        block::{GepIndex, IcmpCond},
21        common::Location,
22        function::Parameter,
23        module::Module,
24        types::{StructType, Type, TypeStorage},
25        value::Operand,
26    };
27
28    use crate::llvm::{JitValue, create_jit_engine, lower_module_to_llvmir};
29
30    /// Conditionally dump IR based on IRVM_DUMP_IR env var
31    fn maybe_dump_ir(ir: &crate::llvm::CompileResult) {
32        if std::env::var("IRVM_DUMP_IR").is_ok() {
33            ir.dump();
34        }
35    }
36
37    #[test]
38    fn test_function_llvm() -> Result<(), Box<dyn std::error::Error>> {
39        let mut module = Module::new("example", Location::unknown());
40        let mut storage = TypeStorage::new();
41        let _bool_ty = storage.add_type(Type::Int(1), Some("bool"));
42        let i32_ty = storage.add_type(Type::Int(32), Some("u32"));
43        let _i64_ty = storage.add_type(Type::Int(64), Some("u64"));
44        let _ptr_ty = storage.add_type(
45            Type::Ptr {
46                pointee: i32_ty,
47                address_space: None,
48            },
49            Some("*i32"),
50        );
51
52        let main_func = module
53            .add_function(
54                "main",
55                &[Parameter::new(i32_ty, Location::Unknown)],
56                Some(i32_ty),
57                Location::Unknown,
58            )
59            .get_id();
60        let test_func = module
61            .add_function(
62                "test",
63                &[Parameter::new(i32_ty, Location::Unknown)],
64                Some(i32_ty),
65                Location::Unknown,
66            )
67            .get_id();
68
69        let test_func_ret_ty = module.get_function(test_func).result_type;
70
71        // main function
72        {
73            let func = module.get_function_mut(main_func);
74            let param = func.param(0)?;
75            let param_dbg = func.create_debug_var_param("argv", i32_ty, 0, &Location::Unknown);
76            let entry_block = func.entry_block;
77
78            let value = func.blocks[entry_block].instr_add(
79                &param,
80                &Operand::const_int(4, i32_ty),
81                Location::Unknown,
82            )?;
83
84            func.blocks[entry_block].instr_dbg_value(
85                value.clone(),
86                param_dbg,
87                Location::Unknown,
88            )?;
89
90            let then_block = func.add_block(&[]);
91            let else_block = func.add_block(&[]);
92            let final_block = func.add_block(&[i32_ty]);
93
94            let cond = func.blocks[entry_block].instr_icmp(
95                IcmpCond::Eq,
96                value.clone(),
97                Operand::const_int(6, i32_ty),
98                Location::Unknown,
99                &storage,
100            )?;
101
102            func.blocks[entry_block].instr_cond_jmp(
103                then_block,
104                else_block,
105                &cond,
106                &[],
107                &[],
108                Location::Unknown,
109            );
110
111            // then block
112            {
113                let value = func.blocks[then_block].instr_add(
114                    &value,
115                    &Operand::const_int(2, i32_ty),
116                    Location::Unknown,
117                )?;
118                func.blocks[then_block].instr_jmp(final_block, &[value], Location::Unknown);
119            }
120
121            // else block
122            {
123                let value = func.blocks[else_block].instr_add(
124                    &value,
125                    &Operand::const_int(6, i32_ty),
126                    Location::Unknown,
127                )?;
128                func.blocks[else_block].instr_jmp(final_block, &[value], Location::Unknown);
129            }
130
131            // final block
132            {
133                let param = func.blocks[final_block].arg(0)?;
134                let value = func.blocks[final_block].instr_call(
135                    test_func,
136                    &[param],
137                    test_func_ret_ty.unwrap(),
138                    Location::Unknown,
139                )?;
140                func.blocks[final_block].instr_ret(Some(&value), Location::Unknown);
141            }
142        }
143
144        // test function
145        {
146            let func = module.get_function_mut(test_func);
147            let value = func.param(0)?;
148            func.entry_block()
149                .instr_ret(Some(&value), Location::Unknown);
150        }
151
152        let result = test_run_module(
153            &module,
154            &storage,
155            "main",
156            &[JitValue::U32(4)],
157            JitValue::U32(0),
158        )?;
159        assert_eq!(result, JitValue::U32(14));
160
161        Ok(())
162    }
163
164    #[test]
165    fn test_struct() -> Result<(), Box<dyn std::error::Error>> {
166        let mut module = Module::new("example", Location::unknown());
167        let mut storage = TypeStorage::new();
168        let i32_ty = storage.add_type(Type::Int(32), Some("u32"));
169        let ptr_ty = storage.add_type(
170            Type::Ptr {
171                pointee: i32_ty,
172                address_space: None,
173            },
174            Some("*u32"),
175        );
176
177        let strct_type = storage.add_type(
178            Type::Struct(
179                StructType {
180                    packed: false,
181                    ident: None,
182                    fields: vec![ptr_ty, i32_ty],
183                    debug_field_names: vec![
184                        ("ptr".to_string(), Location::unknown()),
185                        ("x".to_string(), Location::unknown()),
186                    ],
187                }
188                .into(),
189            ),
190            Some("hello"),
191        );
192
193        let func = module
194            .add_function(
195                "example",
196                &[
197                    Parameter::new(i32_ty, Location::Unknown),
198                    Parameter::new(strct_type, Location::Unknown),
199                ],
200                None,
201                Location::Unknown,
202            )
203            .get_id();
204
205        let func = module.get_function_mut(func);
206
207        let entry = func.entry_block;
208
209        func.blocks[entry].instr_ret(None, Location::Unknown);
210
211        let ir = lower_module_to_llvmir(&module, &storage)?;
212
213        maybe_dump_ir(&ir);
214
215        Ok(())
216    }
217
218    #[test]
219    fn test_gep() -> Result<(), Box<dyn std::error::Error>> {
220        let mut module = Module::new("gepexample", Location::unknown());
221        let mut storage = TypeStorage::new();
222        let i32_ty = storage.add_type(Type::Int(32), Some("u32"));
223        let ptr_ty = storage.add_type(
224            Type::Ptr {
225                pointee: i32_ty,
226                address_space: None,
227            },
228            Some("*u32"),
229        );
230
231        let func = module
232            .add_function(
233                "example",
234                &[Parameter::new(i32_ty, Location::Unknown)],
235                Some(i32_ty),
236                Location::Unknown,
237            )
238            .get_id();
239
240        let func = module.get_function_mut(func);
241
242        let entry = func.entry_block;
243
244        let param1 = func.param(0)?;
245
246        let ptr_val =
247            func.blocks[entry].instr_alloca(ptr_ty, 4, None, Location::Unknown, &storage)?;
248        let k1 = Operand::const_int(1, i32_ty);
249        func.blocks[entry].instr_store(
250            ptr_val.clone(),
251            k1.clone(),
252            None,
253            Location::Unknown,
254            &storage,
255        )?;
256
257        let ptr_idx1 = func.blocks[entry].instr_gep_ex(
258            ptr_val,
259            &[GepIndex::Const(1)],
260            ptr_ty,
261            Location::Unknown,
262            &storage,
263        )?;
264        func.blocks[entry].instr_store(
265            ptr_idx1.clone(),
266            param1,
267            None,
268            Location::Unknown,
269            &storage,
270        )?;
271
272        let result = func.blocks[entry].instr_load(ptr_idx1, None, Location::Unknown, &storage)?;
273
274        func.blocks[entry].instr_ret(Some(&result), Location::Unknown);
275
276        let result = test_run_module(
277            &module,
278            &storage,
279            "example",
280            &[JitValue::U32(2)],
281            JitValue::U32(0),
282        )?;
283        assert_eq!(result, JitValue::U32(2));
284
285        Ok(())
286    }
287
288    fn test_run_module(
289        module: &Module,
290        storage: &TypeStorage,
291        name: &str,
292        args: &[JitValue],
293        ret_ty: JitValue,
294    ) -> Result<JitValue, crate::llvm::Error> {
295        let result = lower_module_to_llvmir(module, storage)?;
296        maybe_dump_ir(&result);
297        let engine = create_jit_engine(result, 3)?;
298
299        let res = unsafe { engine.execute(name, args, ret_ty)? };
300
301        Ok(res)
302    }
303
304    #[test]
305    fn test_type_casts() -> Result<(), Box<dyn std::error::Error>> {
306        let mut module = Module::new("casts", Location::unknown());
307        let mut storage = TypeStorage::new();
308        let i16_ty = storage.add_type(Type::Int(16), None);
309        let i32_ty = storage.add_type(Type::Int(32), None);
310
311        // Test zext: trunc i32->i16, then zext i16->i32 (MCJIT only supports i32 params/returns reliably)
312        let func = module
313            .add_function(
314                "test_zext",
315                &[Parameter::new(i32_ty, Location::Unknown)],
316                Some(i32_ty),
317                Location::Unknown,
318            )
319            .get_id();
320        {
321            let func = module.get_function_mut(func);
322            let param = func.param(0)?;
323            // trunc to i16 first, then zext back to i32
324            let truncated = func
325                .entry_block()
326                .instr_trunc(param, i16_ty, Location::Unknown)?;
327            let extended = func
328                .entry_block()
329                .instr_zext(truncated, i32_ty, Location::Unknown)?;
330            func.entry_block()
331                .instr_ret(Some(&extended), Location::Unknown);
332        }
333
334        // Test sext: trunc i32->i16, then sext i16->i32
335        let func = module
336            .add_function(
337                "test_sext",
338                &[Parameter::new(i32_ty, Location::Unknown)],
339                Some(i32_ty),
340                Location::Unknown,
341            )
342            .get_id();
343        {
344            let func = module.get_function_mut(func);
345            let param = func.param(0)?;
346            let truncated = func
347                .entry_block()
348                .instr_trunc(param, i16_ty, Location::Unknown)?;
349            let extended = func
350                .entry_block()
351                .instr_sext(truncated, i32_ty, Location::Unknown)?;
352            func.entry_block()
353                .instr_ret(Some(&extended), Location::Unknown);
354        }
355
356        // zext: 50000 -> trunc to i16 -> zext to i32 = 50000 (no change, fits in u16)
357        let result = test_run_module(
358            &module,
359            &storage,
360            "test_zext",
361            &[JitValue::U32(50000)],
362            JitValue::U32(0),
363        )?;
364        assert_eq!(result, JitValue::U32(50000));
365
366        // zext: 0x12345 -> trunc to i16 (0x2345) -> zext to i32 = 0x2345
367        let result = test_run_module(
368            &module,
369            &storage,
370            "test_zext",
371            &[JitValue::U32(0x12345)],
372            JitValue::U32(0),
373        )?;
374        assert_eq!(result, JitValue::U32(0x2345));
375
376        // sext: 0xFFFF (= -1 as i16) -> sext to i32 = 0xFFFFFFFF (-1)
377        let result = test_run_module(
378            &module,
379            &storage,
380            "test_sext",
381            &[JitValue::U32(0xFFFF)],
382            JitValue::U32(0),
383        )?;
384        assert_eq!(result, JitValue::U32(0xFFFFFFFF));
385
386        Ok(())
387    }
388
389    #[test]
390    fn test_select() -> Result<(), Box<dyn std::error::Error>> {
391        let mut module = Module::new("select", Location::unknown());
392        let mut storage = TypeStorage::new();
393        let _i1_ty = storage.get_or_create_i1(); // Required for icmp result
394        let i32_ty = storage.add_type(Type::Int(32), None);
395
396        // Use i32 param and compare to create i1 condition (MCJIT doesn't like i1 params)
397        let func = module
398            .add_function(
399                "test_select",
400                &[Parameter::new(i32_ty, Location::Unknown)],
401                Some(i32_ty),
402                Location::Unknown,
403            )
404            .get_id();
405        {
406            let func = module.get_function_mut(func);
407            let param = func.param(0)?;
408            // cond = (param != 0)
409            let cond = func.entry_block().instr_icmp(
410                IcmpCond::Ne,
411                param,
412                Operand::const_int(0, i32_ty),
413                Location::Unknown,
414                &storage,
415            )?;
416            let true_val = Operand::const_int(42, i32_ty);
417            let false_val = Operand::const_int(100, i32_ty);
418            let result =
419                func.entry_block()
420                    .instr_select(cond, true_val, false_val, Location::Unknown)?;
421            func.entry_block()
422                .instr_ret(Some(&result), Location::Unknown);
423        }
424
425        // select(1 != 0, 42, 100) = 42
426        let result = test_run_module(
427            &module,
428            &storage,
429            "test_select",
430            &[JitValue::U32(1)],
431            JitValue::U32(0),
432        )?;
433        assert_eq!(result, JitValue::U32(42));
434
435        // select(0 != 0, 42, 100) = 100
436        let result = test_run_module(
437            &module,
438            &storage,
439            "test_select",
440            &[JitValue::U32(0)],
441            JitValue::U32(0),
442        )?;
443        assert_eq!(result, JitValue::U32(100));
444
445        Ok(())
446    }
447
448    #[test]
449    fn test_switch() -> Result<(), Box<dyn std::error::Error>> {
450        use irvm::block::SwitchCase;
451
452        let mut module = Module::new("switch", Location::unknown());
453        let mut storage = TypeStorage::new();
454        let i32_ty = storage.add_type(Type::Int(32), None);
455
456        let func = module
457            .add_function(
458                "test_switch",
459                &[Parameter::new(i32_ty, Location::Unknown)],
460                Some(i32_ty),
461                Location::Unknown,
462            )
463            .get_id();
464        {
465            let func = module.get_function_mut(func);
466            let param = func.param(0)?;
467
468            let case1_block = func.add_block(&[]);
469            let case2_block = func.add_block(&[]);
470            let default_block = func.add_block(&[]);
471
472            let entry = func.entry_block;
473            func.blocks[entry].instr_switch(
474                param,
475                default_block,
476                &[],
477                vec![
478                    SwitchCase {
479                        value: 1,
480                        block: case1_block,
481                        arguments: vec![],
482                    },
483                    SwitchCase {
484                        value: 2,
485                        block: case2_block,
486                        arguments: vec![],
487                    },
488                ],
489                Location::Unknown,
490            );
491
492            // case 1: return 10
493            func.blocks[case1_block]
494                .instr_ret(Some(&Operand::const_int(10, i32_ty)), Location::Unknown);
495            // case 2: return 20
496            func.blocks[case2_block]
497                .instr_ret(Some(&Operand::const_int(20, i32_ty)), Location::Unknown);
498            // default: return 0
499            func.blocks[default_block]
500                .instr_ret(Some(&Operand::const_int(0, i32_ty)), Location::Unknown);
501        }
502
503        assert_eq!(
504            test_run_module(
505                &module,
506                &storage,
507                "test_switch",
508                &[JitValue::U32(1)],
509                JitValue::U32(0)
510            )?,
511            JitValue::U32(10)
512        );
513        assert_eq!(
514            test_run_module(
515                &module,
516                &storage,
517                "test_switch",
518                &[JitValue::U32(2)],
519                JitValue::U32(0)
520            )?,
521            JitValue::U32(20)
522        );
523        assert_eq!(
524            test_run_module(
525                &module,
526                &storage,
527                "test_switch",
528                &[JitValue::U32(99)],
529                JitValue::U32(0)
530            )?,
531            JitValue::U32(0)
532        );
533
534        Ok(())
535    }
536
537    #[test]
538    fn test_global_variable() -> Result<(), Box<dyn std::error::Error>> {
539        use irvm::value::ConstValue;
540
541        let mut module = Module::new("globals", Location::unknown());
542        let mut storage = TypeStorage::new();
543        let i32_ty = storage.add_type(Type::Int(32), None);
544        let ptr_ty = storage.add_type(
545            Type::Ptr {
546                pointee: i32_ty,
547                address_space: None,
548            },
549            None,
550        );
551
552        // Create a global variable initialized to 42
553        let global_idx = module.add_global(
554            "my_global",
555            i32_ty,
556            Some(ConstValue::Int(42)),
557            false,
558            Location::Unknown,
559        );
560
561        let func = module
562            .add_function("read_global", &[], Some(i32_ty), Location::Unknown)
563            .get_id();
564        {
565            let func = module.get_function_mut(func);
566            let global_ptr = Operand::global(global_idx, ptr_ty);
567            let loaded =
568                func.entry_block()
569                    .instr_load(global_ptr, None, Location::Unknown, &storage)?;
570            func.entry_block()
571                .instr_ret(Some(&loaded), Location::Unknown);
572        }
573
574        let result = test_run_module(&module, &storage, "read_global", &[], JitValue::U32(0))?;
575        assert_eq!(result, JitValue::U32(42));
576
577        Ok(())
578    }
579
580    #[test]
581    fn test_atomicrmw() -> Result<(), Box<dyn std::error::Error>> {
582        use irvm::block::{AtomicOrdering, AtomicRMWOp};
583
584        let mut module = Module::new("atomic", Location::unknown());
585        let mut storage = TypeStorage::new();
586        let i32_ty = storage.add_type(Type::Int(32), None);
587        let ptr_ty = storage.add_type(
588            Type::Ptr {
589                pointee: i32_ty,
590                address_space: None,
591            },
592            None,
593        );
594
595        let func = module
596            .add_function(
597                "test_atomic_add",
598                &[Parameter::new(i32_ty, Location::Unknown)],
599                Some(i32_ty),
600                Location::Unknown,
601            )
602            .get_id();
603        {
604            let func = module.get_function_mut(func);
605            let param = func.param(0)?;
606            let entry = func.entry_block;
607
608            // Allocate, store initial value, atomicrmw add, return old value
609            let ptr =
610                func.blocks[entry].instr_alloca(ptr_ty, 4, None, Location::Unknown, &storage)?;
611            func.blocks[entry].instr_store(
612                ptr.clone(),
613                Operand::const_int(10, i32_ty),
614                None,
615                Location::Unknown,
616                &storage,
617            )?;
618            let old_val = func.blocks[entry].instr_atomicrmw(
619                AtomicRMWOp::Add,
620                ptr,
621                param,
622                AtomicOrdering::SeqCst,
623                Location::Unknown,
624            )?;
625            func.blocks[entry].instr_ret(Some(&old_val), Location::Unknown);
626        }
627
628        // atomicrmw add returns the old value (10), adds param (5)
629        let result = test_run_module(
630            &module,
631            &storage,
632            "test_atomic_add",
633            &[JitValue::U32(5)],
634            JitValue::U32(0),
635        )?;
636        assert_eq!(result, JitValue::U32(10));
637
638        Ok(())
639    }
640
641    #[test]
642    fn test_extractvalue_insertvalue() -> Result<(), Box<dyn std::error::Error>> {
643        use irvm::types::StructType;
644        use irvm::value::ConstValue;
645
646        let mut module = Module::new("aggregate", Location::unknown());
647        let mut storage = TypeStorage::new();
648        let i32_ty = storage.add_type(Type::Int(32), None);
649        let i64_ty = storage.add_type(Type::Int(64), None);
650
651        // Create struct { i32, i64 }
652        let struct_ty = storage.add_type(
653            Type::Struct(
654                StructType {
655                    packed: false,
656                    ident: None,
657                    fields: vec![i32_ty, i64_ty],
658                    debug_field_names: vec![],
659                }
660                .into(),
661            ),
662            None,
663        );
664
665        // Test insertvalue + extractvalue: create struct, insert value, extract it back
666        let func = module
667            .add_function(
668                "test_aggregate",
669                &[Parameter::new(i32_ty, Location::Unknown)],
670                Some(i32_ty),
671                Location::Unknown,
672            )
673            .get_id();
674        {
675            let func = module.get_function_mut(func);
676            let param = func.param(0)?;
677            // Start with undef struct, insert param into field 0, extract it back
678            let undef_struct = Operand::Constant(ConstValue::Undef, struct_ty);
679            let with_field = func.entry_block().instr_insertvalue(
680                undef_struct,
681                param.clone(),
682                &[0],
683                Location::Unknown,
684            )?;
685            let extracted = func.entry_block().instr_extractvalue(
686                with_field,
687                &[0],
688                i32_ty,
689                Location::Unknown,
690            )?;
691            func.entry_block()
692                .instr_ret(Some(&extracted), Location::Unknown);
693        }
694
695        // insertvalue then extractvalue should return the same value
696        let result = test_run_module(
697            &module,
698            &storage,
699            "test_aggregate",
700            &[JitValue::U32(123)],
701            JitValue::U32(0),
702        )?;
703        assert_eq!(result, JitValue::U32(123));
704
705        Ok(())
706    }
707
708    #[test]
709    fn test_param_attrs() -> Result<(), Box<dyn std::error::Error>> {
710        use irvm::function::ReturnAttrs;
711
712        let mut module = Module::new("param_attrs", Location::unknown());
713        let mut storage = TypeStorage::new();
714        let i32_ty = storage.add_type(Type::Int(32), None);
715        let ptr_ty = storage.add_type(
716            Type::Ptr {
717                pointee: i32_ty,
718                address_space: None,
719            },
720            None,
721        );
722
723        // Create a function with various parameter attributes
724        let mut param1 = Parameter::new(ptr_ty, Location::Unknown);
725        param1.nocapture = true;
726        param1.readonly = true;
727        param1.nonnull = true;
728        param1.dereferenceable = Some(4);
729
730        let mut param2 = Parameter::new(i32_ty, Location::Unknown);
731        param2.noundef = true;
732        param2.zeroext = true;
733
734        // Test function with pointer return type (for noalias return attr)
735        let func = module
736            .add_function(
737                "test_attrs",
738                &[param1, param2],
739                Some(ptr_ty),
740                Location::Unknown,
741            )
742            .get_id();
743
744        // Set return attributes (noalias is only valid for pointer return types)
745        {
746            let func = module.get_function_mut(func);
747            func.return_attrs = ReturnAttrs {
748                noalias: true,
749                noundef: true,
750                nonnull: true,
751                dereferenceable: Some(4),
752            };
753
754            // Simple function body - return the pointer parameter
755            let param = func.param(0)?;
756            func.entry_block()
757                .instr_ret(Some(&param), Location::Unknown);
758        }
759
760        // Also test a function with integer return type and noundef
761        // Note: noalias is only valid for pointer types
762        let mut param3 = Parameter::new(ptr_ty, Location::Unknown);
763        param3.noalias = true; // Valid because it's a pointer
764        param3.writeonly = true;
765
766        let func2 = module
767            .add_function("test_attrs2", &[param3], Some(i32_ty), Location::Unknown)
768            .get_id();
769
770        {
771            let func2 = module.get_function_mut(func2);
772            func2.return_attrs = ReturnAttrs {
773                noalias: false, // Can't use noalias on non-pointer return
774                noundef: true,
775                nonnull: false,
776                dereferenceable: None,
777            };
778            // Load a value from the pointer and return it
779            let param = func2.param(0)?;
780            let loaded =
781                func2
782                    .entry_block()
783                    .instr_load(param, None, Location::Unknown, &storage)?;
784            func2
785                .entry_block()
786                .instr_ret(Some(&loaded), Location::Unknown);
787        }
788
789        let ir = lower_module_to_llvmir(&module, &storage)?;
790        maybe_dump_ir(&ir);
791
792        // If we get here without errors, the attributes were successfully applied
793        Ok(())
794    }
795
796    #[test]
797    fn test_gc_name() -> Result<(), Box<dyn std::error::Error>> {
798        let mut module = Module::new("gc_test", Location::unknown());
799        let mut storage = TypeStorage::new();
800        let i32_ty = storage.add_type(Type::Int(32), None);
801
802        let func = module
803            .add_function(
804                "gc_func",
805                &[Parameter::new(i32_ty, Location::Unknown)],
806                Some(i32_ty),
807                Location::Unknown,
808            )
809            .get_id();
810
811        {
812            let func = module.get_function_mut(func);
813            func.gc_name = Some("shadow-stack".to_string());
814            let param = func.param(0)?;
815            func.entry_block()
816                .instr_ret(Some(&param), Location::Unknown);
817        }
818
819        let ir = lower_module_to_llvmir(&module, &storage)?;
820        maybe_dump_ir(&ir);
821
822        // If we get here without errors, the GC name was successfully set
823        Ok(())
824    }
825
826    #[test]
827    fn test_prefix_data() -> Result<(), Box<dyn std::error::Error>> {
828        use irvm::value::ConstValue;
829
830        let mut module = Module::new("prefix_test", Location::unknown());
831        let mut storage = TypeStorage::new();
832        let i32_ty = storage.add_type(Type::Int(32), None);
833
834        let func = module
835            .add_function(
836                "prefix_func",
837                &[Parameter::new(i32_ty, Location::Unknown)],
838                Some(i32_ty),
839                Location::Unknown,
840            )
841            .get_id();
842
843        {
844            let func = module.get_function_mut(func);
845            // Set prefix data to a constant integer
846            func.prefix_data = Some((ConstValue::Int(0xDEADBEEF), i32_ty));
847            let param = func.param(0)?;
848            func.entry_block()
849                .instr_ret(Some(&param), Location::Unknown);
850        }
851
852        let ir = lower_module_to_llvmir(&module, &storage)?;
853        maybe_dump_ir(&ir);
854
855        // If we get here without errors, the prefix data was successfully set
856        Ok(())
857    }
858
859    #[test]
860    fn test_prologue_data() -> Result<(), Box<dyn std::error::Error>> {
861        use irvm::value::ConstValue;
862
863        let mut module = Module::new("prologue_test", Location::unknown());
864        let mut storage = TypeStorage::new();
865        let i32_ty = storage.add_type(Type::Int(32), None);
866
867        let func = module
868            .add_function(
869                "prologue_func",
870                &[Parameter::new(i32_ty, Location::Unknown)],
871                Some(i32_ty),
872                Location::Unknown,
873            )
874            .get_id();
875
876        {
877            let func = module.get_function_mut(func);
878            // Set prologue data to a constant integer
879            func.prologue_data = Some((ConstValue::Int(0xCAFEBABE), i32_ty));
880            let param = func.param(0)?;
881            func.entry_block()
882                .instr_ret(Some(&param), Location::Unknown);
883        }
884
885        let ir = lower_module_to_llvmir(&module, &storage)?;
886        maybe_dump_ir(&ir);
887
888        // If we get here without errors, the prologue data was successfully set
889        Ok(())
890    }
891
892    #[test]
893    fn test_combined_function_and_param_attrs() -> Result<(), Box<dyn std::error::Error>> {
894        use irvm::function::{FunctionAttrs, ReturnAttrs};
895
896        let mut module = Module::new("combined_attrs", Location::unknown());
897        let mut storage = TypeStorage::new();
898        let i32_ty = storage.add_type(Type::Int(32), None);
899        let ptr_ty = storage.add_type(
900            Type::Ptr {
901                pointee: i32_ty,
902                address_space: None,
903            },
904            None,
905        );
906
907        // Create a function with both function-level and parameter-level attributes
908        let mut param = Parameter::new(ptr_ty, Location::Unknown);
909        param.nocapture = true;
910        param.readonly = true;
911
912        let func = module
913            .add_function("combined", &[param], Some(ptr_ty), Location::Unknown)
914            .get_id();
915
916        {
917            let func = module.get_function_mut(func);
918            // Set function-level attributes (only use valid function attrs)
919            func.attrs = FunctionAttrs {
920                nounwind: true,
921                willreturn: true,
922                norecurse: true,
923                ..Default::default()
924            };
925            // Set return attributes
926            func.return_attrs = ReturnAttrs {
927                noalias: true,
928                nonnull: true,
929                ..Default::default()
930            };
931
932            let param = func.param(0)?;
933            func.entry_block()
934                .instr_ret(Some(&param), Location::Unknown);
935        }
936
937        let ir = lower_module_to_llvmir(&module, &storage)?;
938        maybe_dump_ir(&ir);
939
940        Ok(())
941    }
942
943    #[test]
944    fn test_param_alignment() -> Result<(), Box<dyn std::error::Error>> {
945        let mut module = Module::new("param_align", Location::unknown());
946        let mut storage = TypeStorage::new();
947        let i32_ty = storage.add_type(Type::Int(32), None);
948        let ptr_ty = storage.add_type(
949            Type::Ptr {
950                pointee: i32_ty,
951                address_space: None,
952            },
953            None,
954        );
955
956        // Create a function with aligned parameter
957        let mut param = Parameter::new(ptr_ty, Location::Unknown);
958        param.align = Some(16); // 16-byte alignment
959
960        let func = module
961            .add_function("aligned_param", &[param], Some(i32_ty), Location::Unknown)
962            .get_id();
963
964        {
965            let func = module.get_function_mut(func);
966            let param = func.param(0)?;
967            let loaded = func
968                .entry_block()
969                .instr_load(param, None, Location::Unknown, &storage)?;
970            func.entry_block()
971                .instr_ret(Some(&loaded), Location::Unknown);
972        }
973
974        let ir = lower_module_to_llvmir(&module, &storage)?;
975        maybe_dump_ir(&ir);
976
977        Ok(())
978    }
979
980    #[test]
981    fn test_signext_zeroext_param_attrs() -> Result<(), Box<dyn std::error::Error>> {
982        let mut module = Module::new("ext_attrs", Location::unknown());
983        let mut storage = TypeStorage::new();
984        let i8_ty = storage.add_type(Type::Int(8), None);
985        let i32_ty = storage.add_type(Type::Int(32), None);
986
987        // Function with signext parameter
988        let mut param1 = Parameter::new(i8_ty, Location::Unknown);
989        param1.signext = true;
990
991        // Function with zeroext parameter
992        let mut param2 = Parameter::new(i8_ty, Location::Unknown);
993        param2.zeroext = true;
994
995        let func = module
996            .add_function(
997                "test_ext",
998                &[param1, param2],
999                Some(i32_ty),
1000                Location::Unknown,
1001            )
1002            .get_id();
1003
1004        {
1005            let func = module.get_function_mut(func);
1006            // Extend both params to i32 and add them
1007            let p1 = func.param(0)?;
1008            let p2 = func.param(1)?;
1009            let p1_ext = func
1010                .entry_block()
1011                .instr_sext(p1, i32_ty, Location::Unknown)?;
1012            let p2_ext = func
1013                .entry_block()
1014                .instr_zext(p2, i32_ty, Location::Unknown)?;
1015            let result = func
1016                .entry_block()
1017                .instr_add(&p1_ext, &p2_ext, Location::Unknown)?;
1018            func.entry_block()
1019                .instr_ret(Some(&result), Location::Unknown);
1020        }
1021
1022        let ir = lower_module_to_llvmir(&module, &storage)?;
1023        maybe_dump_ir(&ir);
1024
1025        Ok(())
1026    }
1027
1028    #[test]
1029    fn test_inreg_returned_nest_attrs() -> Result<(), Box<dyn std::error::Error>> {
1030        let mut module = Module::new("misc_attrs", Location::unknown());
1031        let mut storage = TypeStorage::new();
1032        let i32_ty = storage.add_type(Type::Int(32), None);
1033        let ptr_ty = storage.add_type(
1034            Type::Ptr {
1035                pointee: i32_ty,
1036                address_space: None,
1037            },
1038            None,
1039        );
1040
1041        // Test inreg attribute
1042        let mut param1 = Parameter::new(i32_ty, Location::Unknown);
1043        param1.inreg = true;
1044
1045        // Test returned attribute (pointer that is returned)
1046        let mut param2 = Parameter::new(ptr_ty, Location::Unknown);
1047        param2.returned = true;
1048
1049        // Test nest attribute (for nested function context pointer)
1050        let mut param3 = Parameter::new(ptr_ty, Location::Unknown);
1051        param3.nest = true;
1052
1053        let func = module
1054            .add_function(
1055                "misc_attrs",
1056                &[param1, param2, param3],
1057                Some(ptr_ty),
1058                Location::Unknown,
1059            )
1060            .get_id();
1061
1062        {
1063            let func = module.get_function_mut(func);
1064            // Return the 'returned' parameter
1065            let param = func.param(1)?;
1066            func.entry_block()
1067                .instr_ret(Some(&param), Location::Unknown);
1068        }
1069
1070        let ir = lower_module_to_llvmir(&module, &storage)?;
1071        maybe_dump_ir(&ir);
1072
1073        Ok(())
1074    }
1075
1076    #[test]
1077    fn test_nofree_param_attr() -> Result<(), Box<dyn std::error::Error>> {
1078        let mut module = Module::new("nofree_test", Location::unknown());
1079        let mut storage = TypeStorage::new();
1080        let i32_ty = storage.add_type(Type::Int(32), None);
1081        let ptr_ty = storage.add_type(
1082            Type::Ptr {
1083                pointee: i32_ty,
1084                address_space: None,
1085            },
1086            None,
1087        );
1088
1089        // nofree attribute indicates the function won't free this pointer
1090        let mut param = Parameter::new(ptr_ty, Location::Unknown);
1091        param.nofree = true;
1092        param.nocapture = true;
1093
1094        let func = module
1095            .add_function("nofree_func", &[param], Some(i32_ty), Location::Unknown)
1096            .get_id();
1097
1098        {
1099            let func = module.get_function_mut(func);
1100            let param = func.param(0)?;
1101            let loaded = func
1102                .entry_block()
1103                .instr_load(param, None, Location::Unknown, &storage)?;
1104            func.entry_block()
1105                .instr_ret(Some(&loaded), Location::Unknown);
1106        }
1107
1108        let ir = lower_module_to_llvmir(&module, &storage)?;
1109        maybe_dump_ir(&ir);
1110
1111        Ok(())
1112    }
1113
1114    #[test]
1115    fn test_prefix_and_prologue_combined() -> Result<(), Box<dyn std::error::Error>> {
1116        use irvm::value::ConstValue;
1117
1118        let mut module = Module::new("both_data", Location::unknown());
1119        let mut storage = TypeStorage::new();
1120        let i32_ty = storage.add_type(Type::Int(32), None);
1121
1122        let func = module
1123            .add_function(
1124                "both_func",
1125                &[Parameter::new(i32_ty, Location::Unknown)],
1126                Some(i32_ty),
1127                Location::Unknown,
1128            )
1129            .get_id();
1130
1131        {
1132            let func = module.get_function_mut(func);
1133            // Set both prefix and prologue data
1134            func.prefix_data = Some((ConstValue::Int(0xDEADBEEF), i32_ty));
1135            func.prologue_data = Some((ConstValue::Int(0xCAFEBABE), i32_ty));
1136            let param = func.param(0)?;
1137            func.entry_block()
1138                .instr_ret(Some(&param), Location::Unknown);
1139        }
1140
1141        let ir = lower_module_to_llvmir(&module, &storage)?;
1142        maybe_dump_ir(&ir);
1143
1144        Ok(())
1145    }
1146
1147    #[test]
1148    fn test_gc_with_function_attrs() -> Result<(), Box<dyn std::error::Error>> {
1149        use irvm::function::FunctionAttrs;
1150
1151        let mut module = Module::new("gc_attrs", Location::unknown());
1152        let mut storage = TypeStorage::new();
1153        let i32_ty = storage.add_type(Type::Int(32), None);
1154
1155        let func = module
1156            .add_function(
1157                "gc_with_attrs",
1158                &[Parameter::new(i32_ty, Location::Unknown)],
1159                Some(i32_ty),
1160                Location::Unknown,
1161            )
1162            .get_id();
1163
1164        {
1165            let func = module.get_function_mut(func);
1166            func.gc_name = Some("statepoint-example".to_string());
1167            func.attrs = FunctionAttrs {
1168                nounwind: true,
1169                ..Default::default()
1170            };
1171            let param = func.param(0)?;
1172            func.entry_block()
1173                .instr_ret(Some(&param), Location::Unknown);
1174        }
1175
1176        let ir = lower_module_to_llvmir(&module, &storage)?;
1177        maybe_dump_ir(&ir);
1178
1179        Ok(())
1180    }
1181}