Fork of daniellemaywood.uk/gleam — Wasm codegen work
1

Configure Feed

Select the types of activity you want to include in your feed.

Improve support for compiling custom types

author
Danielle Maywood
committer
nandi
date (Jul 26, 2026, 12:11 PM -0700) commit a20054a3 parent 14c245a6 change-id vlrsounx
+292 -67
+78 -9
compiler-core/src/cranelift.rs
··· 1 1 use std::collections::HashMap; 2 2 3 3 use cranelift::{ 4 - codegen::{Context, ir::BlockArg}, 4 + codegen::{ 5 + Context, 6 + ir::{BlockArg, immediates::Offset32}, 7 + }, 5 8 prelude::*, 6 9 }; 7 10 use cranelift_module::{Linkage, Module, default_libcall_names}; ··· 416 419 type_: _, 417 420 } => todo!(), 418 421 ast::Expression::Struct { 419 - tag: _, 420 - items: _, 422 + tag, 423 + items, 421 424 type_: _, 422 - } => todo!(), 423 - ast::Expression::StructTag { value: _ } => todo!(), 425 + } => { 426 + let mut sig = self.module.make_signature(); 427 + sig.params.push(AbiParam::new(types::I64)); 428 + sig.returns.push(AbiParam::new(types::I64)); 429 + 430 + let id = self 431 + .module 432 + .declare_function("malloc", Linkage::Import, &sig) 433 + .expect("failed to declare malloc"); 434 + 435 + let malloc = self.module.declare_func_in_func(id, &mut self.builder.func); 436 + 437 + let size_arg = self 438 + .builder 439 + .ins() 440 + .iconst(types::I64, (items.len() as i64) * 8 + 8); 441 + 442 + let call = self.builder.ins().call(malloc, &[size_arg]); 443 + let ptr = self.builder.inst_results(call)[0]; 444 + 445 + let tag = i64::from(tag.unwrap_or_default()); 446 + let tag = self.builder.ins().iconst(types::I64, tag); 447 + _ = self 448 + .builder 449 + .ins() 450 + .store(MemFlags::new(), tag, ptr, Offset32::new(0)); 451 + 452 + for (index, item) in items.into_iter().enumerate() { 453 + let index = index as i32 + 1; 454 + 455 + let value = self.translate(item); 456 + 457 + _ = self.builder.ins().store( 458 + MemFlags::new(), 459 + value, 460 + ptr, 461 + Offset32::new(index * 8), 462 + ); 463 + } 464 + 465 + ptr 466 + } 467 + ast::Expression::StructTag { value } => { 468 + let ptr = self.translate(*value); 469 + 470 + self.builder 471 + .ins() 472 + .load(types::I64, MemFlags::new(), ptr, Offset32::new(0)) 473 + } 424 474 ast::Expression::StructAccess { 425 - value: _, 426 - index: _, 427 - type_: _, 428 - } => todo!(), 475 + value, 476 + index, 477 + type_, 478 + } => { 479 + let ptr = self.translate(*value); 480 + 481 + self.builder.ins().load( 482 + translate_type(&type_), 483 + MemFlags::new(), 484 + ptr, 485 + Offset32::new(index as i32 * 8 + 8), 486 + ) 487 + } 429 488 ast::Expression::Set { name, value } => { 430 489 let var = self.builder.declare_var(translate_type(&value.type_())); 431 490 _ = self.vars.insert(name.name, var); ··· 497 556 498 557 self.builder.inst_results(call)[0] 499 558 } 559 + ast::Expression::Panic { type_, .. } => match type_ { 560 + ast::CompleteType::Func { .. } 561 + | ast::CompleteType::Struct { .. } 562 + | ast::CompleteType::List(_) 563 + | ast::CompleteType::String 564 + | ast::CompleteType::Bool 565 + | ast::CompleteType::Int => self.builder.ins().iconst(types::I64, 0), 566 + 567 + ast::CompleteType::Float => self.builder.ins().f64const(0.0), 568 + }, 500 569 } 501 570 } 502 571 }
+177 -51
compiler-core/src/cranelift/mir.rs
··· 134 134 module.functions.push(self.translate_function(function)); 135 135 } 136 136 crate::ast::Definition::TypeAlias(_type_alias) => todo!(), 137 - crate::ast::Definition::CustomType(_custom_type) => {} 137 + crate::ast::Definition::CustomType(custom_type) => { 138 + module 139 + .functions 140 + .append(&mut self.translate_custom_type(custom_type)); 141 + } 138 142 crate::ast::Definition::Import(_import) => todo!(), 139 143 crate::ast::Definition::ModuleConstant(_module_constant) => todo!(), 140 144 } ··· 147 151 module 148 152 } 149 153 154 + fn translate_custom_type( 155 + &self, 156 + custom_type: &crate::ast::TypedCustomType, 157 + ) -> Vec<Function<IncompleteType>> { 158 + let mut functions = vec![]; 159 + 160 + for (index, constructor) in custom_type.constructors.iter().enumerate() { 161 + let parameters = constructor 162 + .arguments 163 + .iter() 164 + .enumerate() 165 + .map(|(index, argument)| FunctionParameter { 166 + name: Some(eco_format!("_{index}")), 167 + type_: Self::translate_type(&argument.type_), 168 + }) 169 + .collect(); 170 + 171 + functions.push(Function::<IncompleteType> { 172 + name: constructor.name.clone(), 173 + return_type: IncompleteType::Struct { elements: vec![] }, 174 + parameters, 175 + body: Expression::Block(vec![Expression::<IncompleteType>::Struct { 176 + tag: Some(index as u32), 177 + items: constructor 178 + .arguments 179 + .iter() 180 + .enumerate() 181 + .map(|(index, argument)| { 182 + Expression::Var(Var { 183 + name: eco_format!("_{index}"), 184 + type_: Self::translate_type(&argument.type_), 185 + }) 186 + }) 187 + .collect(), 188 + type_: IncompleteType::Struct { elements: vec![] }, 189 + }]), 190 + }); 191 + } 192 + 193 + functions 194 + } 195 + 150 196 fn translate_function(&self, function: &crate::ast::TypedFunction) -> Function<IncompleteType> { 151 197 let (_, name) = function.name.clone().expect("function should have a name"); 152 198 ··· 233 279 pub struct BodyTranslator { 234 280 variables: HashMap<EcoString, Var<IncompleteType>>, 235 281 variable_count: usize, 282 + 283 + decision_map: HashMap<usize, Var<IncompleteType>>, 236 284 } 237 285 238 286 #[derive(Debug, Clone, Copy, PartialEq, Eq)] ··· 271 319 let snapshot = self.variables.clone(); 272 320 let result = func(self); 273 321 self.variables = snapshot; 322 + result 323 + } 324 + 325 + fn in_decision_scope<T>(&mut self, func: impl Fn(&mut Self) -> T) -> T { 326 + let snapshot = self.decision_map.clone(); 327 + let result = func(self); 328 + self.decision_map = snapshot; 274 329 result 275 330 } 276 331 ··· 517 572 subjects, 518 573 clauses, 519 574 compiled_case, 520 - } => { 575 + } => self.in_decision_scope(move |this| { 521 576 let mut block = vec![]; 522 577 523 - let subject_vars: Vec<_> = Iterator::collect(subjects.iter().map(|subject| { 524 - let name = self.make_tmp_var(Translator::translate_type(&subject.type_())); 525 - let value = self.translate_expression(subject); 578 + for (index, subject) in subjects.iter().enumerate() { 579 + let name = this.make_tmp_var(Translator::translate_type(&subject.type_())); 580 + let value = this.translate_expression(subject); 581 + 582 + _ = this.decision_map.insert(index, name.clone()); 526 583 527 584 block.push(Expression::Set { 528 585 name: name.clone(), 529 586 value: Box::new(value), 530 587 }); 588 + } 531 589 532 - return name; 533 - })); 534 - 535 - block.push(self.translate_decision( 590 + block.push(this.translate_decision( 536 591 DecisionKind::Case, 537 - &subject_vars, 538 592 clauses, 539 593 &compiled_case.tree, 540 594 )); 541 595 542 596 Expression::Block(block) 543 - } 597 + }), 544 598 crate::ast::TypedExpr::RecordAccess { 545 599 location: _, 546 600 field_start: _, ··· 654 708 &mut self, 655 709 assignment: &crate::ast::TypedAssignment, 656 710 ) -> Expression<IncompleteType> { 657 - let mut block = vec![]; 711 + self.in_decision_scope(|this| { 712 + let mut block = vec![]; 658 713 659 - let value_var = self.make_tmp_var(Translator::translate_type(&assignment.value.type_())); 660 - let value = self.translate_expression(&assignment.value); 714 + let value_var = 715 + this.make_tmp_var(Translator::translate_type(&assignment.value.type_())); 716 + let value = this.translate_expression(&assignment.value); 661 717 662 - block.push(Expression::Set { 663 - name: value_var.clone(), 664 - value: Box::new(value), 665 - }); 718 + block.push(Expression::Set { 719 + name: value_var.clone(), 720 + value: Box::new(value), 721 + }); 666 722 667 - block.push(self.translate_decision( 668 - DecisionKind::Let, 669 - &[value_var.clone()], 670 - &[], 671 - &assignment.compiled_case.tree, 672 - )); 723 + _ = this.decision_map.insert(0, value_var.clone()); 724 + 725 + block.push(this.translate_decision( 726 + DecisionKind::Let, 727 + &[], 728 + &assignment.compiled_case.tree, 729 + )); 673 730 674 - block.push(Expression::Var(value_var)); 731 + block.push(Expression::Var(value_var)); 675 732 676 - Expression::Block(block) 733 + Expression::Block(block) 734 + }) 677 735 } 678 736 679 737 fn translate_bound_value_type(&mut self, value: &exhaustiveness::BoundValue) -> IncompleteType { ··· 694 752 fn translate_decision( 695 753 &mut self, 696 754 kind: DecisionKind, 697 - subjects: &[Var<IncompleteType>], 698 755 clauses: &[crate::ast::TypedClause], 699 756 decision: &exhaustiveness::Decision, 700 757 ) -> Expression<IncompleteType> { ··· 706 763 let binding_type = self.translate_bound_value_type(value); 707 764 let binding = self.make_var(binding.clone(), binding_type); 708 765 709 - block.push(self.translate_binding(subjects, binding.clone(), value)); 766 + block.push(self.translate_binding(binding.clone(), value)); 710 767 } 711 768 712 769 if kind == DecisionKind::Case { ··· 738 795 let binding_type = self.translate_bound_value_type(value); 739 796 let binding = self.make_var(binding.clone(), binding_type); 740 797 741 - block.push(self.translate_binding(subjects, binding, value)); 798 + block.push(self.translate_binding(binding, value)); 742 799 } 743 800 744 801 let cond = self.translate_guard(guard); ··· 750 807 let binding_type = self.translate_bound_value_type(value); 751 808 let binding = self.make_var(binding.clone(), binding_type); 752 809 753 - block.push(self.translate_binding(subjects, binding.clone(), value)); 810 + block.push(self.translate_binding(binding.clone(), value)); 754 811 } 755 812 756 813 block.push(self.translate_expression(&clauses[if_true.clause_index].then)); 757 814 block 758 815 }; 759 816 760 - let if_false = self.translate_decision(kind, subjects, clauses, if_false); 817 + let if_false = self.translate_decision(kind, clauses, if_false); 761 818 762 819 block.push(Expression::If { 763 820 cond: Box::new(cond), ··· 771 828 var, 772 829 choices, 773 830 fallback, 774 - fallback_check: _, 831 + fallback_check, 775 832 } => { 776 - let fallback = self.translate_decision(kind, subjects, clauses, fallback); 833 + let fallback = 834 + self.translate_fallback(kind, var, clauses, fallback, fallback_check); 777 835 778 836 DoubleEndedIterator::rfold(choices.iter(), fallback, |fallback, (check, then)| { 779 837 Expression::If { 780 - cond: Box::new(self.translate_runtime_check( 781 - check, 782 - Expression::Var(subjects[var.id].clone()), 783 - )), 784 - then: Box::new(self.translate_decision(kind, subjects, clauses, then)), 838 + cond: Box::new( 839 + self.translate_runtime_check( 840 + check, 841 + Expression::Var( 842 + self.decision_map 843 + .get(&var.id) 844 + .cloned() 845 + .expect("expected variable to be defined"), 846 + ), 847 + ), 848 + ), 849 + then: Box::new(self.translate_decision(kind, clauses, then)), 785 850 else_: Box::new(fallback), 786 851 } 787 852 }) 788 853 } 789 854 exhaustiveness::Decision::Fail => todo!(), 855 + } 856 + } 857 + 858 + fn translate_fallback( 859 + &mut self, 860 + kind: DecisionKind, 861 + var: &exhaustiveness::Variable, 862 + clauses: &[crate::ast::TypedClause], 863 + decision: &exhaustiveness::Decision, 864 + check: &exhaustiveness::FallbackCheck, 865 + ) -> Expression<IncompleteType> { 866 + match check { 867 + exhaustiveness::FallbackCheck::InfiniteCatchAll 868 + | exhaustiveness::FallbackCheck::CatchAll { .. } => { 869 + self.translate_decision(kind, clauses, decision) 870 + } 871 + exhaustiveness::FallbackCheck::RuntimeCheck { check } => { 872 + let cond = self.translate_runtime_check( 873 + check, 874 + Expression::Var( 875 + self.decision_map 876 + .get(&var.id) 877 + .cloned() 878 + .expect("expected variable to be defined"), 879 + ), 880 + ); 881 + 882 + let then = self.translate_decision(kind, clauses, decision); 883 + 884 + Expression::If { 885 + cond: Box::new(cond), 886 + then: Box::new(then.clone()), 887 + else_: Box::new(Expression::Panic { 888 + type_: then.type_().clone(), 889 + }), 890 + } 891 + } 790 892 } 791 893 } 792 894 ··· 1096 1198 1097 1199 fn translate_binding( 1098 1200 &mut self, 1099 - subjects: &[Var<IncompleteType>], 1100 1201 binding: Var<IncompleteType>, 1101 1202 value: &exhaustiveness::BoundValue, 1102 1203 ) -> Expression<IncompleteType> { 1103 1204 let value = match value { 1104 - exhaustiveness::BoundValue::Variable(variable) => { 1105 - Expression::Var(subjects[variable.id].clone()) 1106 - } 1205 + exhaustiveness::BoundValue::Variable(variable) => Expression::Var( 1206 + self.decision_map 1207 + .get(&variable.id) 1208 + .cloned() 1209 + .expect("expected variable to be defined"), 1210 + ), 1107 1211 exhaustiveness::BoundValue::LiteralString(_eco_string) => todo!(), 1108 1212 exhaustiveness::BoundValue::LiteralInt(big_int) => Expression::Int { 1109 1213 value: big_int.clone(), ··· 1157 1261 match_: _, 1158 1262 index, 1159 1263 labels: _, 1160 - fields: _, 1161 - } => Expression::Equals { 1162 - lhs: Box::new(Expression::Int { 1163 - value: BigInt::from(*index), 1164 - }), 1165 - rhs: Box::new(Expression::StructTag { 1166 - value: Box::new(against), 1167 - }), 1168 - }, 1264 + fields, 1265 + } => { 1266 + let mut block = vec![]; 1267 + 1268 + for (index, field) in fields.iter().enumerate() { 1269 + let var_type = Translator::translate_type(&field.type_); 1270 + let var = self.make_tmp_var(var_type.clone()); 1271 + 1272 + _ = self.decision_map.insert(field.id, var.clone()); 1273 + 1274 + block.push(Expression::Set { 1275 + name: var, 1276 + value: Box::new(Expression::StructAccess { 1277 + value: Box::new(against.clone()), 1278 + index: index as u64, 1279 + type_: var_type, 1280 + }), 1281 + }); 1282 + } 1283 + 1284 + block.push(Expression::Equals { 1285 + lhs: Box::new(Expression::Int { 1286 + value: BigInt::from(*index), 1287 + }), 1288 + rhs: Box::new(Expression::StructTag { 1289 + value: Box::new(against), 1290 + }), 1291 + }); 1292 + 1293 + Expression::Block(block) 1294 + } 1169 1295 exhaustiveness::RuntimeCheck::NonEmptyList { first: _, rest: _ } => todo!(), 1170 1296 exhaustiveness::RuntimeCheck::EmptyList => todo!(), 1171 1297 }
+6
compiler-core/src/cranelift/mir/ast.rs
··· 229 229 args: Vec<Expression<T>>, 230 230 type_: T, 231 231 }, 232 + 233 + Panic { 234 + type_: T, 235 + }, 232 236 } 233 237 234 238 impl Expression<IncompleteType> { ··· 271 275 Expression::Set { value, .. } => value.type_(), 272 276 Expression::If { then, .. } => then.type_(), 273 277 Expression::Call { type_, .. } => type_.clone(), 278 + Expression::Panic { type_ } => type_.clone(), 274 279 } 275 280 } 276 281 } ··· 315 320 Expression::Set { value, .. } => value.type_(), 316 321 Expression::If { then, .. } => then.type_(), 317 322 Expression::Call { type_, .. } => type_.clone(), 323 + Expression::Panic { type_ } => type_.clone(), 318 324 } 319 325 } 320 326 }
+6
compiler-core/src/cranelift/mir/lower.rs
··· 330 330 .collect(), 331 331 type_: self.translate_type(type_), 332 332 }, 333 + Expression::Panic { type_ } => Expression::Panic { 334 + type_: self.translate_type(type_), 335 + }, 333 336 } 334 337 } 335 338 ··· 601 604 .into_iter() 602 605 .map(Expression::try_from) 603 606 .collect::<Result<Vec<_>, _>>()?, 607 + type_: type_.try_into()?, 608 + }), 609 + Expression::Panic { type_ } => Ok(Expression::Panic { 604 610 type_: type_.try_into()?, 605 611 }), 606 612 }
+11
compiler-core/src/cranelift/mir/visit.rs
··· 262 262 ) { 263 263 visit_expression_call(self, target, args, type_); 264 264 } 265 + 266 + fn visit_expression_panic(&mut self, type_: &'mir mut T) { 267 + visit_expression_panic(self, type_); 268 + } 265 269 } 266 270 267 271 pub fn visit_module<'mir, T, V>(v: &mut V, module: &'mir mut Module<T>) ··· 332 336 args, 333 337 type_, 334 338 } => v.visit_expression_call(target, args, type_), 339 + Expression::Panic { type_ } => v.visit_expression_panic(type_), 335 340 } 336 341 } 337 342 ··· 688 693 v.visit_expression(arg); 689 694 } 690 695 } 696 + 697 + pub fn visit_expression_panic<'mir, T, V>(_v: &mut V, _type_: &'mir mut T) 698 + where 699 + V: Visit<'mir, T> + ?Sized, 700 + { 701 + }
test/project_cranelift/a.out

This is a binary file and will not be displayed.

+1 -1
test/project_cranelift/main.c
··· 1 1 #include <stdio.h> 2 2 3 - int M17project_cranelift_4main_A0_RI(); 3 + unsigned long long M17project_cranelift_4main_A0_RI(); 4 4 5 5 int main() { 6 6 unsigned long long outcome = M17project_cranelift_4main_A0_RI();
+13 -6
test/project_cranelift/src/project_cranelift.gleam
··· 1 - fn fib(n: Int) -> Int { 2 - case n { 3 - 0 -> 0 4 - 1 -> 1 5 - _ -> fib(n - 1) + fib(n - 2) 1 + type Wibble { 2 + Wibble(x: Int, y: Int) 3 + Wobble(x: Int) 4 + } 5 + 6 + fn sum(w: Wibble) -> Int { 7 + case w { 8 + Wibble(x:, y:) -> x + y 9 + Wobble(x:) -> x 6 10 } 7 11 } 8 12 9 13 pub fn main() { 10 - fib(40) 14 + let wibble = Wibble(x: 30, y: 35) 15 + let wobble = Wobble(x: 40) 16 + 17 + sum(wibble) + sum(wobble) 11 18 }