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