1mod control_flow;
75mod error;
76mod ffi_wrappers;
77mod globals;
78mod inline;
79mod platform;
80mod program;
81mod runtime;
82mod state;
83mod statements;
84mod types;
85mod virtual_stack;
86mod words;
87
88pub use error::CodeGenError;
90pub use platform::{ffi_c_args, ffi_return_type, get_target_triple};
91pub use runtime::{BUILTIN_SYMBOLS, RUNTIME_DECLARATIONS, emit_runtime_decls};
92pub use state::CodeGen;
93
94use state::{
96 BranchResult, MAX_VIRTUAL_STACK, QuotationFunctions, TailPosition, UNREACHABLE_PREDECESSOR,
97 VirtualValue, mangle_name,
98};
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::ast::{Program, Statement, WordDef};
104 use std::collections::HashMap;
105
106 #[test]
107 fn test_codegen_hello_world() {
108 let mut codegen = CodeGen::new();
109
110 let program = Program {
111 includes: vec![],
112 unions: vec![],
113 words: vec![WordDef {
114 name: "main".to_string(),
115 effect: None,
116 body: vec![
117 Statement::StringLiteral("Hello, World!".to_string()),
118 Statement::WordCall {
119 name: "io.write-line".to_string(),
120 span: None,
121 },
122 ],
123 source: None,
124 }],
125 };
126
127 let ir = codegen
128 .codegen_program(&program, HashMap::new(), HashMap::new())
129 .unwrap();
130
131 assert!(ir.contains("define i32 @main(i32 %argc, ptr %argv)"));
132 assert!(ir.contains("define ptr @seq_main(ptr %stack)"));
134 assert!(ir.contains("call ptr @patch_seq_push_string"));
135 assert!(ir.contains("call ptr @patch_seq_write_line"));
136 assert!(ir.contains("\"Hello, World!\\00\""));
137 }
138
139 #[test]
140 fn test_codegen_io_write() {
141 let mut codegen = CodeGen::new();
143
144 let program = Program {
145 includes: vec![],
146 unions: vec![],
147 words: vec![WordDef {
148 name: "main".to_string(),
149 effect: None,
150 body: vec![
151 Statement::StringLiteral("no newline".to_string()),
152 Statement::WordCall {
153 name: "io.write".to_string(),
154 span: None,
155 },
156 ],
157 source: None,
158 }],
159 };
160
161 let ir = codegen
162 .codegen_program(&program, HashMap::new(), HashMap::new())
163 .unwrap();
164
165 assert!(ir.contains("call ptr @patch_seq_push_string"));
166 assert!(ir.contains("call ptr @patch_seq_write"));
167 assert!(ir.contains("\"no newline\\00\""));
168 }
169
170 #[test]
171 fn test_codegen_arithmetic() {
172 let mut codegen = CodeGen::new();
174
175 let program = Program {
176 includes: vec![],
177 unions: vec![],
178 words: vec![WordDef {
179 name: "main".to_string(),
180 effect: None,
181 body: vec![
182 Statement::IntLiteral(2),
183 Statement::IntLiteral(3),
184 Statement::WordCall {
185 name: "i.add".to_string(),
186 span: None,
187 },
188 ],
189 source: None,
190 }],
191 };
192
193 let ir = codegen
194 .codegen_program(&program, HashMap::new(), HashMap::new())
195 .unwrap();
196
197 assert!(ir.contains("add i64 0, 2"), "Should create SSA var for 2");
200 assert!(ir.contains("add i64 0, 3"), "Should create SSA var for 3");
201 assert!(ir.contains("add i64 %"), "Should add SSA variables");
203 }
204
205 #[test]
206 fn test_pure_inline_test_mode() {
207 let mut codegen = CodeGen::new_pure_inline_test();
208
209 let program = Program {
211 includes: vec![],
212 unions: vec![],
213 words: vec![WordDef {
214 name: "main".to_string(),
215 effect: None,
216 body: vec![
217 Statement::IntLiteral(5),
218 Statement::IntLiteral(3),
219 Statement::WordCall {
220 name: "i.add".to_string(),
221 span: None,
222 },
223 ],
224 source: None,
225 }],
226 };
227
228 let ir = codegen
229 .codegen_program(&program, HashMap::new(), HashMap::new())
230 .unwrap();
231
232 assert!(!ir.contains("call void @patch_seq_scheduler_init"));
235 assert!(!ir.contains("call i64 @patch_seq_strand_spawn"));
236
237 assert!(ir.contains("call ptr @seq_stack_new_default()"));
239 assert!(ir.contains("call ptr @seq_main(ptr %stack_base)"));
240
241 assert!(ir.contains("trunc i64 %result to i32"));
243 assert!(ir.contains("ret i32 %exit_code"));
244
245 assert!(!ir.contains("call ptr @patch_seq_push_int"));
247 assert!(ir.contains("add i64 0, 5"), "Should create SSA var for 5");
249 assert!(ir.contains("add i64 0, 3"), "Should create SSA var for 3");
250
251 assert!(!ir.contains("call ptr @patch_seq_add"));
253 assert!(ir.contains("add i64 %"), "Should add SSA variables");
254 }
255
256 #[test]
257 fn test_escape_llvm_string() {
258 assert_eq!(CodeGen::escape_llvm_string("hello").unwrap(), "hello");
259 assert_eq!(CodeGen::escape_llvm_string("a\nb").unwrap(), r"a\0Ab");
260 assert_eq!(CodeGen::escape_llvm_string("a\tb").unwrap(), r"a\09b");
261 assert_eq!(CodeGen::escape_llvm_string("a\"b").unwrap(), r"a\22b");
262 }
263
264 #[test]
265 #[allow(deprecated)] fn test_external_builtins_declared() {
267 use crate::config::{CompilerConfig, ExternalBuiltin};
268
269 let mut codegen = CodeGen::new();
270
271 let program = Program {
272 includes: vec![],
273 unions: vec![],
274 words: vec![WordDef {
275 name: "main".to_string(),
276 effect: None, body: vec![
278 Statement::IntLiteral(42),
279 Statement::WordCall {
280 name: "my-external-op".to_string(),
281 span: None,
282 },
283 ],
284 source: None,
285 }],
286 };
287
288 let config = CompilerConfig::new()
289 .with_builtin(ExternalBuiltin::new("my-external-op", "test_runtime_my_op"));
290
291 let ir = codegen
292 .codegen_program_with_config(&program, HashMap::new(), HashMap::new(), &config)
293 .unwrap();
294
295 assert!(
297 ir.contains("declare ptr @test_runtime_my_op(ptr)"),
298 "IR should declare external builtin"
299 );
300
301 assert!(
303 ir.contains("call ptr @test_runtime_my_op"),
304 "IR should call external builtin"
305 );
306 }
307
308 #[test]
309 #[allow(deprecated)] fn test_multiple_external_builtins() {
311 use crate::config::{CompilerConfig, ExternalBuiltin};
312
313 let mut codegen = CodeGen::new();
314
315 let program = Program {
316 includes: vec![],
317 unions: vec![],
318 words: vec![WordDef {
319 name: "main".to_string(),
320 effect: None, body: vec![
322 Statement::WordCall {
323 name: "actor-self".to_string(),
324 span: None,
325 },
326 Statement::WordCall {
327 name: "journal-append".to_string(),
328 span: None,
329 },
330 ],
331 source: None,
332 }],
333 };
334
335 let config = CompilerConfig::new()
336 .with_builtin(ExternalBuiltin::new("actor-self", "seq_actors_self"))
337 .with_builtin(ExternalBuiltin::new(
338 "journal-append",
339 "seq_actors_journal_append",
340 ));
341
342 let ir = codegen
343 .codegen_program_with_config(&program, HashMap::new(), HashMap::new(), &config)
344 .unwrap();
345
346 assert!(ir.contains("declare ptr @seq_actors_self(ptr)"));
348 assert!(ir.contains("declare ptr @seq_actors_journal_append(ptr)"));
349
350 assert!(ir.contains("call ptr @seq_actors_self"));
352 assert!(ir.contains("call ptr @seq_actors_journal_append"));
353 }
354
355 #[test]
356 #[allow(deprecated)] fn test_external_builtins_with_library_paths() {
358 use crate::config::{CompilerConfig, ExternalBuiltin};
359
360 let config = CompilerConfig::new()
361 .with_builtin(ExternalBuiltin::new("my-op", "runtime_my_op"))
362 .with_library_path("/custom/lib")
363 .with_library("myruntime");
364
365 assert_eq!(config.external_builtins.len(), 1);
366 assert_eq!(config.library_paths, vec!["/custom/lib"]);
367 assert_eq!(config.libraries, vec!["myruntime"]);
368 }
369
370 #[test]
371 fn test_external_builtin_full_pipeline() {
372 use crate::compile_to_ir_with_config;
375 use crate::config::{CompilerConfig, ExternalBuiltin};
376 use crate::types::{Effect, StackType, Type};
377
378 let source = r#"
379 : main ( -- Int )
380 42 my-transform
381 0
382 ;
383 "#;
384
385 let effect = Effect::new(StackType::singleton(Type::Int), StackType::Empty);
387 let config = CompilerConfig::new().with_builtin(ExternalBuiltin::with_effect(
388 "my-transform",
389 "ext_runtime_transform",
390 effect,
391 ));
392
393 let result = compile_to_ir_with_config(source, &config);
395 assert!(
396 result.is_ok(),
397 "Compilation should succeed: {:?}",
398 result.err()
399 );
400
401 let ir = result.unwrap();
402 assert!(ir.contains("declare ptr @ext_runtime_transform(ptr)"));
403 assert!(ir.contains("call ptr @ext_runtime_transform"));
404 }
405
406 #[test]
407 fn test_external_builtin_without_config_fails() {
408 use crate::compile_to_ir;
410
411 let source = r#"
412 : main ( -- Int )
413 42 unknown-builtin
414 0
415 ;
416 "#;
417
418 let result = compile_to_ir(source);
420 assert!(result.is_err());
421 assert!(result.unwrap_err().contains("unknown-builtin"));
422 }
423
424 #[test]
425 fn test_match_exhaustiveness_error() {
426 use crate::compile_to_ir;
427
428 let source = r#"
429 union Result { Ok { value: Int } Err { msg: String } }
430
431 : handle ( Variant -- Int )
432 match
433 Ok -> drop 1
434 # Missing Err arm!
435 end
436 ;
437
438 : main ( -- ) 42 Make-Ok handle drop ;
439 "#;
440
441 let result = compile_to_ir(source);
442 assert!(result.is_err());
443 let err = result.unwrap_err();
444 assert!(err.contains("Non-exhaustive match"));
445 assert!(err.contains("Result"));
446 assert!(err.contains("Err"));
447 }
448
449 #[test]
450 fn test_match_exhaustive_compiles() {
451 use crate::compile_to_ir;
452
453 let source = r#"
454 union Result { Ok { value: Int } Err { msg: String } }
455
456 : handle ( Variant -- Int )
457 match
458 Ok -> drop 1
459 Err -> drop 0
460 end
461 ;
462
463 : main ( -- ) 42 Make-Ok handle drop ;
464 "#;
465
466 let result = compile_to_ir(source);
467 assert!(
468 result.is_ok(),
469 "Exhaustive match should compile: {:?}",
470 result
471 );
472 }
473
474 #[test]
475 fn test_codegen_symbol() {
476 let mut codegen = CodeGen::new();
478
479 let program = Program {
480 includes: vec![],
481 unions: vec![],
482 words: vec![WordDef {
483 name: "main".to_string(),
484 effect: None,
485 body: vec![
486 Statement::Symbol("hello".to_string()),
487 Statement::WordCall {
488 name: "symbol->string".to_string(),
489 span: None,
490 },
491 Statement::WordCall {
492 name: "io.write-line".to_string(),
493 span: None,
494 },
495 ],
496 source: None,
497 }],
498 };
499
500 let ir = codegen
501 .codegen_program(&program, HashMap::new(), HashMap::new())
502 .unwrap();
503
504 assert!(ir.contains("call ptr @patch_seq_push_interned_symbol"));
505 assert!(ir.contains("call ptr @patch_seq_symbol_to_string"));
506 assert!(ir.contains("\"hello\\00\""));
507 }
508
509 #[test]
510 fn test_symbol_interning_dedup() {
511 let mut codegen = CodeGen::new();
513
514 let program = Program {
515 includes: vec![],
516 unions: vec![],
517 words: vec![WordDef {
518 name: "main".to_string(),
519 effect: None,
520 body: vec![
521 Statement::Symbol("hello".to_string()),
523 Statement::Symbol("hello".to_string()),
524 Statement::Symbol("world".to_string()), ],
526 source: None,
527 }],
528 };
529
530 let ir = codegen
531 .codegen_program(&program, HashMap::new(), HashMap::new())
532 .unwrap();
533
534 let sym_defs: Vec<_> = ir
537 .lines()
538 .filter(|l| l.trim().starts_with("@.sym."))
539 .collect();
540
541 assert_eq!(
543 sym_defs.len(),
544 2,
545 "Expected 2 symbol globals, got: {:?}",
546 sym_defs
547 );
548
549 let hello_uses: usize = ir.matches("@.sym.0").count();
551 assert_eq!(
552 hello_uses, 3,
553 "Expected 3 occurrences of .sym.0 (1 def + 2 uses)"
554 );
555
556 assert!(
558 ir.contains("i64 0, i8 1"),
559 "Symbol global should have capacity=0 and global=1"
560 );
561 }
562
563 #[test]
564 fn test_dup_optimization_for_int() {
565 let mut codegen = CodeGen::new();
568
569 use crate::types::Type;
570
571 let program = Program {
572 includes: vec![],
573 unions: vec![],
574 words: vec![
575 WordDef {
576 name: "test_dup".to_string(),
577 effect: None,
578 body: vec![
579 Statement::IntLiteral(42), Statement::WordCall {
581 name: "dup".to_string(),
583 span: None,
584 },
585 Statement::WordCall {
586 name: "drop".to_string(),
587 span: None,
588 },
589 Statement::WordCall {
590 name: "drop".to_string(),
591 span: None,
592 },
593 ],
594 source: None,
595 },
596 WordDef {
597 name: "main".to_string(),
598 effect: None,
599 body: vec![Statement::WordCall {
600 name: "test_dup".to_string(),
601 span: None,
602 }],
603 source: None,
604 },
605 ],
606 };
607
608 let mut statement_types = HashMap::new();
610 statement_types.insert(("test_dup".to_string(), 1), Type::Int);
611
612 let ir = codegen
613 .codegen_program(&program, HashMap::new(), statement_types)
614 .unwrap();
615
616 let func_start = ir.find("define tailcc ptr @seq_test_dup").unwrap();
618 let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
619 let test_dup_fn = &ir[func_start..func_end];
620
621 assert!(
623 test_dup_fn.contains("load %Value"),
624 "Optimized dup should use 'load %Value', got:\n{}",
625 test_dup_fn
626 );
627 assert!(
628 test_dup_fn.contains("store %Value"),
629 "Optimized dup should use 'store %Value', got:\n{}",
630 test_dup_fn
631 );
632
633 assert!(
635 !test_dup_fn.contains("@patch_seq_clone_value"),
636 "Optimized dup should NOT call clone_value for Int, got:\n{}",
637 test_dup_fn
638 );
639 }
640
641 #[test]
642 fn test_dup_optimization_after_literal() {
643 let mut codegen = CodeGen::new();
646
647 let program = Program {
648 includes: vec![],
649 unions: vec![],
650 words: vec![
651 WordDef {
652 name: "test_dup".to_string(),
653 effect: None,
654 body: vec![
655 Statement::IntLiteral(42), Statement::WordCall {
657 name: "dup".to_string(),
659 span: None,
660 },
661 Statement::WordCall {
662 name: "drop".to_string(),
663 span: None,
664 },
665 Statement::WordCall {
666 name: "drop".to_string(),
667 span: None,
668 },
669 ],
670 source: None,
671 },
672 WordDef {
673 name: "main".to_string(),
674 effect: None,
675 body: vec![Statement::WordCall {
676 name: "test_dup".to_string(),
677 span: None,
678 }],
679 source: None,
680 },
681 ],
682 };
683
684 let ir = codegen
686 .codegen_program(&program, HashMap::new(), HashMap::new())
687 .unwrap();
688
689 let func_start = ir.find("define tailcc ptr @seq_test_dup").unwrap();
691 let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
692 let test_dup_fn = &ir[func_start..func_end];
693
694 assert!(
696 test_dup_fn.contains("load %Value"),
697 "Dup after int literal should use optimized load, got:\n{}",
698 test_dup_fn
699 );
700 assert!(
701 test_dup_fn.contains("store %Value"),
702 "Dup after int literal should use optimized store, got:\n{}",
703 test_dup_fn
704 );
705 assert!(
706 !test_dup_fn.contains("@patch_seq_clone_value"),
707 "Dup after int literal should NOT call clone_value, got:\n{}",
708 test_dup_fn
709 );
710 }
711
712 #[test]
713 fn test_dup_no_optimization_after_word_call() {
714 let mut codegen = CodeGen::new();
716
717 let program = Program {
718 includes: vec![],
719 unions: vec![],
720 words: vec![
721 WordDef {
722 name: "get_value".to_string(),
723 effect: None,
724 body: vec![Statement::IntLiteral(42)],
725 source: None,
726 },
727 WordDef {
728 name: "test_dup".to_string(),
729 effect: None,
730 body: vec![
731 Statement::WordCall {
732 name: "get_value".to_string(),
734 span: None,
735 },
736 Statement::WordCall {
737 name: "dup".to_string(),
739 span: None,
740 },
741 Statement::WordCall {
742 name: "drop".to_string(),
743 span: None,
744 },
745 Statement::WordCall {
746 name: "drop".to_string(),
747 span: None,
748 },
749 ],
750 source: None,
751 },
752 WordDef {
753 name: "main".to_string(),
754 effect: None,
755 body: vec![Statement::WordCall {
756 name: "test_dup".to_string(),
757 span: None,
758 }],
759 source: None,
760 },
761 ],
762 };
763
764 let ir = codegen
766 .codegen_program(&program, HashMap::new(), HashMap::new())
767 .unwrap();
768
769 let func_start = ir.find("define tailcc ptr @seq_test_dup").unwrap();
771 let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
772 let test_dup_fn = &ir[func_start..func_end];
773
774 assert!(
776 test_dup_fn.contains("@patch_seq_clone_value"),
777 "Dup after word call should call clone_value, got:\n{}",
778 test_dup_fn
779 );
780 }
781
782 #[test]
783 fn test_roll_constant_optimization() {
784 let mut codegen = CodeGen::new();
787
788 let program = Program {
789 includes: vec![],
790 unions: vec![],
791 words: vec![
792 WordDef {
793 name: "test_roll".to_string(),
794 effect: None,
795 body: vec![
796 Statement::IntLiteral(1),
797 Statement::IntLiteral(2),
798 Statement::IntLiteral(3),
799 Statement::IntLiteral(2), Statement::WordCall {
801 name: "roll".to_string(),
803 span: None,
804 },
805 Statement::WordCall {
806 name: "drop".to_string(),
807 span: None,
808 },
809 Statement::WordCall {
810 name: "drop".to_string(),
811 span: None,
812 },
813 Statement::WordCall {
814 name: "drop".to_string(),
815 span: None,
816 },
817 ],
818 source: None,
819 },
820 WordDef {
821 name: "main".to_string(),
822 effect: None,
823 body: vec![Statement::WordCall {
824 name: "test_roll".to_string(),
825 span: None,
826 }],
827 source: None,
828 },
829 ],
830 };
831
832 let ir = codegen
833 .codegen_program(&program, HashMap::new(), HashMap::new())
834 .unwrap();
835
836 let func_start = ir.find("define tailcc ptr @seq_test_roll").unwrap();
838 let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
839 let test_roll_fn = &ir[func_start..func_end];
840
841 assert!(
844 !test_roll_fn.contains("= add i64 %"),
845 "Constant roll should use constant offset, not dynamic add, got:\n{}",
846 test_roll_fn
847 );
848
849 assert!(
851 !test_roll_fn.contains("@llvm.memmove"),
852 "2 roll should not use memmove, got:\n{}",
853 test_roll_fn
854 );
855 }
856
857 #[test]
858 fn test_pick_constant_optimization() {
859 let mut codegen = CodeGen::new();
862
863 let program = Program {
864 includes: vec![],
865 unions: vec![],
866 words: vec![
867 WordDef {
868 name: "test_pick".to_string(),
869 effect: None,
870 body: vec![
871 Statement::IntLiteral(10),
872 Statement::IntLiteral(20),
873 Statement::IntLiteral(1), Statement::WordCall {
875 name: "pick".to_string(),
877 span: None,
878 },
879 Statement::WordCall {
880 name: "drop".to_string(),
881 span: None,
882 },
883 Statement::WordCall {
884 name: "drop".to_string(),
885 span: None,
886 },
887 Statement::WordCall {
888 name: "drop".to_string(),
889 span: None,
890 },
891 ],
892 source: None,
893 },
894 WordDef {
895 name: "main".to_string(),
896 effect: None,
897 body: vec![Statement::WordCall {
898 name: "test_pick".to_string(),
899 span: None,
900 }],
901 source: None,
902 },
903 ],
904 };
905
906 let ir = codegen
907 .codegen_program(&program, HashMap::new(), HashMap::new())
908 .unwrap();
909
910 let func_start = ir.find("define tailcc ptr @seq_test_pick").unwrap();
912 let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
913 let test_pick_fn = &ir[func_start..func_end];
914
915 assert!(
918 !test_pick_fn.contains("= add i64 %"),
919 "Constant pick should use constant offset, not dynamic add, got:\n{}",
920 test_pick_fn
921 );
922
923 assert!(
925 test_pick_fn.contains("i64 -3"),
926 "1 pick should use offset -3 (-(1+2)), got:\n{}",
927 test_pick_fn
928 );
929 }
930
931 #[test]
932 fn test_small_word_marked_alwaysinline() {
933 let mut codegen = CodeGen::new();
935
936 let program = Program {
937 includes: vec![],
938 unions: vec![],
939 words: vec![
940 WordDef {
941 name: "double".to_string(), effect: None,
943 body: vec![
944 Statement::WordCall {
945 name: "dup".to_string(),
946 span: None,
947 },
948 Statement::WordCall {
949 name: "i.+".to_string(),
950 span: None,
951 },
952 ],
953 source: None,
954 },
955 WordDef {
956 name: "main".to_string(),
957 effect: None,
958 body: vec![
959 Statement::IntLiteral(21),
960 Statement::WordCall {
961 name: "double".to_string(),
962 span: None,
963 },
964 ],
965 source: None,
966 },
967 ],
968 };
969
970 let ir = codegen
971 .codegen_program(&program, HashMap::new(), HashMap::new())
972 .unwrap();
973
974 assert!(
976 ir.contains("define tailcc ptr @seq_double(ptr %stack) alwaysinline"),
977 "Small word should have alwaysinline attribute, got:\n{}",
978 ir.lines()
979 .filter(|l| l.contains("define"))
980 .collect::<Vec<_>>()
981 .join("\n")
982 );
983
984 assert!(
986 ir.contains("define ptr @seq_main(ptr %stack) {"),
987 "main should not have alwaysinline, got:\n{}",
988 ir.lines()
989 .filter(|l| l.contains("define"))
990 .collect::<Vec<_>>()
991 .join("\n")
992 );
993 }
994
995 #[test]
996 fn test_recursive_word_not_inlined() {
997 let mut codegen = CodeGen::new();
999
1000 let program = Program {
1001 includes: vec![],
1002 unions: vec![],
1003 words: vec![
1004 WordDef {
1005 name: "countdown".to_string(), effect: None,
1007 body: vec![
1008 Statement::WordCall {
1009 name: "dup".to_string(),
1010 span: None,
1011 },
1012 Statement::If {
1013 then_branch: vec![
1014 Statement::IntLiteral(1),
1015 Statement::WordCall {
1016 name: "i.-".to_string(),
1017 span: None,
1018 },
1019 Statement::WordCall {
1020 name: "countdown".to_string(), span: None,
1022 },
1023 ],
1024 else_branch: Some(vec![]),
1025 },
1026 ],
1027 source: None,
1028 },
1029 WordDef {
1030 name: "main".to_string(),
1031 effect: None,
1032 body: vec![
1033 Statement::IntLiteral(5),
1034 Statement::WordCall {
1035 name: "countdown".to_string(),
1036 span: None,
1037 },
1038 ],
1039 source: None,
1040 },
1041 ],
1042 };
1043
1044 let ir = codegen
1045 .codegen_program(&program, HashMap::new(), HashMap::new())
1046 .unwrap();
1047
1048 assert!(
1050 ir.contains("define tailcc ptr @seq_countdown(ptr %stack) {"),
1051 "Recursive word should NOT have alwaysinline, got:\n{}",
1052 ir.lines()
1053 .filter(|l| l.contains("define"))
1054 .collect::<Vec<_>>()
1055 .join("\n")
1056 );
1057 }
1058
1059 #[test]
1060 fn test_recursive_word_in_match_not_inlined() {
1061 use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
1063
1064 let mut codegen = CodeGen::new();
1065
1066 let program = Program {
1067 includes: vec![],
1068 unions: vec![UnionDef {
1069 name: "Option".to_string(),
1070 variants: vec![
1071 UnionVariant {
1072 name: "Some".to_string(),
1073 fields: vec![],
1074 source: None,
1075 },
1076 UnionVariant {
1077 name: "None".to_string(),
1078 fields: vec![],
1079 source: None,
1080 },
1081 ],
1082 source: None,
1083 }],
1084 words: vec![
1085 WordDef {
1086 name: "process".to_string(), effect: None,
1088 body: vec![Statement::Match {
1089 arms: vec![
1090 MatchArm {
1091 pattern: Pattern::Variant("Some".to_string()),
1092 body: vec![Statement::WordCall {
1093 name: "process".to_string(), span: None,
1095 }],
1096 },
1097 MatchArm {
1098 pattern: Pattern::Variant("None".to_string()),
1099 body: vec![],
1100 },
1101 ],
1102 }],
1103 source: None,
1104 },
1105 WordDef {
1106 name: "main".to_string(),
1107 effect: None,
1108 body: vec![Statement::WordCall {
1109 name: "process".to_string(),
1110 span: None,
1111 }],
1112 source: None,
1113 },
1114 ],
1115 };
1116
1117 let ir = codegen
1118 .codegen_program(&program, HashMap::new(), HashMap::new())
1119 .unwrap();
1120
1121 assert!(
1123 ir.contains("define tailcc ptr @seq_process(ptr %stack) {"),
1124 "Recursive word in match should NOT have alwaysinline, got:\n{}",
1125 ir.lines()
1126 .filter(|l| l.contains("define"))
1127 .collect::<Vec<_>>()
1128 .join("\n")
1129 );
1130 }
1131}