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

Configure Feed

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

gleam / compiler-core / src / inline.rs
73 kB 2114 lines
1//! This module implements the function inlining optimisation. This allows 2//! function calls to be inlined at the callsite, and replaced with the contents 3//! of the function which is being called. 4//! 5//! Function inlining is useful for two main reasons: 6//! - It removes the overhead of calling other functions and jumping around 7//! execution too much 8//! - It removes the barrier of the function call between the code around the 9//! call, and the code inside the called function. 10//! 11//! For example, the following Gleam code make heavy use of `use` sugar and higher 12//! order functions: 13//! 14//! ```gleam 15//! pub fn try_sum(list: List(Result(String, Nil)), sum: Int) -> Result(Int, Nil) { 16//! use <- bool.guard(when: sum >= 1000, return: Ok(sum)) 17//! case list { 18//! [] -> Ok(sum) 19//! [first, ..rest] -> { 20//! use number <- result.try(int.parse(first)) 21//! try_sum(rest, sum + number) 22//! } 23//! } 24//! } 25//! ``` 26//! 27//! This can make the code easier to read, but it normally would have a performance 28//! cost. There are two called functions, and two implicit anonymous functions. 29//! This function is also not tail recursive, as it uses higher order functions 30//! inside its body. 31//! 32//! However, with function inlining, the above code can be optimised to: 33//! 34//! ```gleam 35//! pub fn try_sum(list: List(Result(String, Nil)), sum: Int) -> Result(Int, Nil) { 36//! case sum >= 1000 { 37//! True -> Ok(sum) 38//! False -> case list { 39//! [] -> Ok(sum) 40//! [first, ..rest] -> { 41//! case int.parse(first) { 42//! Ok(number) -> try_sum(rest, sum + number) 43//! Error(error) -> Error(error) 44//! } 45//! } 46//! } 47//! } 48//! } 49//! ``` 50//! 51//! Which now has no extra function calls, and is tail recursive! 52//! 53//! The process of function inlining is quite simple really. It is implemented 54//! using an AST folder, which traverses each node of the AST, and potentially 55//! alters it as it goes. 56//! 57//! Every time we encounter a function call, we decide whether or not we can 58//! inline it. For now, the criteria for inlining is very simple, although a 59//! more complex heuristic-based approach will likely be implemented in the 60//! future. For now though, a function can be inlined if: 61//! It is a standard library function within the hardcoded list - which can be 62//! found in the `inline_function` function - or, it is an anonymous function. 63//! 64//! Inlining anonymous functions allows us to: 65//! - Remove calls to parameters of higher-order functions once those higher- 66//! order functions have been inlined. For example, the following example using 67//! `result.map`: 68//! ```gleam 69//! result.map(Ok(10), fn(x) { x + 1 }) 70//! ``` 71//! 72//! Without inlining of anonymous function would be turned into: 73//! ```gleam 74//! case Ok(10) { 75//! Ok(value) -> Ok(fn(x) { x + 1 }(value)) 76//! Error(error) -> Error(error) 77//! } 78//! ``` 79//! 80//! However if we inline anonymous functions also, we remove every call, and 81//! so it becomes: 82//! 83//! ```gleam 84//! case Ok(10) { 85//! Ok(value) -> Ok(value + 1) 86//! Error(error) -> Error(error) 87//! } 88//! ``` 89//! 90//! - Remove calls to anonymous functions in pipelines. Sometimes, an anonymous 91//! function is used in a pipeline, which can sometimes be the result of an 92//! expanded function capture. For example: 93//! 94//! ```gleam 95//! "10" |> int.parse |> result.unwrap(0) |> fn(x) { x * x } |> something_else 96//! ``` 97//! 98//! This can now be desugared to: 99//! ```gleam 100//! let _pipe1 = "10" 101//! let _pipe2 = int.parse(_pipe1) 102//! let _pipe3 = result.unwrap(_pipe2, 0) 103//! let _pipe4 = _pipe3 * _pipe3 104//! something_else(_pipe4) 105//! ``` 106//! 107//! See documentation of individual functions to explain better how the process 108//! works. 109//! 110 111#![allow(dead_code)] 112 113use std::{ 114 collections::{HashMap, HashSet}, 115 sync::Arc, 116}; 117 118use ecow::{EcoString, eco_format}; 119use itertools::Itertools; 120use vec1::Vec1; 121 122use crate::{ 123 STDLIB_PACKAGE_NAME, 124 analyse::Inferred, 125 ast::{ 126 self, ArgNames, Assert, AssignName, Assignment, AssignmentKind, BitArrayOption, 127 BitArraySegment, BitArraySize, CallArg, Clause, FunctionLiteralKind, Pattern, 128 PipelineAssignmentKind, Publicity, SrcSpan, Statement, TailPattern, TypedArg, TypedAssert, 129 TypedAssignment, TypedBitArraySize, TypedClause, TypedDefinitions, TypedExpr, 130 TypedExprBitArraySegment, TypedFunction, TypedModule, TypedPattern, 131 TypedPipelineAssignment, TypedStatement, TypedUse, visit::Visit, 132 }, 133 exhaustiveness::{Body, CompiledCase, Decision}, 134 type_::{ 135 self, Deprecation, ModuleInterface, ModuleValueConstructor, PRELUDE_MODULE_NAME, 136 PatternConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, 137 collapse_links, 138 error::VariableOrigin, 139 expression::{Implementations, Purity}, 140 }, 141}; 142 143/// Perform function inlining across an entire module, applying it to each 144/// individual function. 145pub fn module( 146 mut module: TypedModule, 147 modules: &im::HashMap<EcoString, ModuleInterface>, 148) -> TypedModule { 149 let mut inliner = Inliner::new(modules); 150 151 module.definitions = TypedDefinitions { 152 functions: module 153 .definitions 154 .functions 155 .into_iter() 156 .map(|function| inliner.function(function)) 157 .collect(), 158 ..module.definitions 159 }; 160 161 module 162} 163 164struct Inliner<'a> { 165 /// Importable modules, containing information about functions which can be 166 /// inlined 167 modules: &'a im::HashMap<EcoString, ModuleInterface>, 168 /// Any variables which can be inlined. This is used when inlining the body 169 /// of function calls. Let's look at an example inlinable function: 170 /// ```gleam 171 /// pub fn add(a, b) { 172 /// a + b 173 /// } 174 /// ``` 175 /// If it is called - `add(1, 2)` - it can be inlined to the following: 176 /// ```gleam 177 /// { 178 /// let a = 1 179 /// let b = 2 180 /// a + b 181 /// } 182 /// ``` 183 /// 184 /// However, this can be inlined further. Since `a` and `b` are only used 185 /// once each in the body, the whole expression can be reduced to `1 + 2`. 186 /// 187 /// In the above example, this variable would contain `{a: 1, b: 2}`, 188 /// indicating the names of the variables to be inlined, as well as the 189 /// values to replace them with. 190 inline_variables: HashMap<EcoString, TypedExpr>, 191 192 /// The number we append to variable names in order to ensure uniqueness. 193 variable_number: usize, 194 /// Set of in-scope variables, used to determine when a conflict between 195 /// variable names occurs during inlining. 196 in_scope: HashSet<EcoString>, 197 /// If two variables conflict in names during inlining, we need to rename 198 /// one to avoid the conflict. Any variables renamed this way are stored 199 /// here. 200 renamed_variables: im::HashMap<EcoString, EcoString>, 201 /// The current position, whether we are inside the body of an inlined 202 /// function or not. 203 position: Position, 204} 205 206#[derive(Debug, Clone, Copy, PartialEq)] 207enum Position { 208 RegularFunction, 209 InlinedFunction, 210} 211 212impl Inliner<'_> { 213 fn new(modules: &im::HashMap<EcoString, ModuleInterface>) -> Inliner<'_> { 214 Inliner { 215 modules, 216 inline_variables: HashMap::new(), 217 variable_number: 0, 218 renamed_variables: im::HashMap::new(), 219 in_scope: HashSet::new(), 220 position: Position::RegularFunction, 221 } 222 } 223 224 /// Defines a variable in the current scope, renaming it if necessary. 225 /// Currently, this duplicates work performed in the code generators, where 226 /// variables are renamed in a similar way. But since inlining can change 227 /// scope boundaries, it needs to be performed here too. Ideally, we would 228 /// move all the deduplicating logic from the code generators to here where 229 /// we perform inlining, but that is a fairly large item of work. 230 fn define_variable(&mut self, name: EcoString) -> EcoString { 231 let unique_in_scope = self.in_scope.insert(name.clone()); 232 // If the variable name is already defined, and we are inlining a function, 233 // that means there is a potential conflict in names and we need to rename 234 // the variable. 235 if !unique_in_scope && self.position == Position::InlinedFunction { 236 // Prefixing the variable name with `_inline_` ensures it does 237 // not conflict with other defined variables. 238 let new_name = eco_format!("_inline_{name}_{}", self.variable_number); 239 self.variable_number += 1; 240 _ = self.renamed_variables.insert(name, new_name.clone()); 241 new_name 242 } else { 243 name 244 } 245 } 246 247 /// Get the name we are using for a variable, in case it is renamed. 248 fn variable_name(&self, name: EcoString) -> EcoString { 249 self.renamed_variables.get(&name).cloned().unwrap_or(name) 250 } 251 252 fn function(&mut self, mut function: TypedFunction) -> TypedFunction { 253 for argument in function.arguments.iter() { 254 match &argument.names { 255 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => {} 256 ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => { 257 _ = self.in_scope.insert(name.clone()); 258 } 259 } 260 } 261 262 function.body = function 263 .body 264 .into_iter() 265 .map(|statement| self.statement(statement)) 266 .collect_vec(); 267 function 268 } 269 270 fn statement(&mut self, statement: TypedStatement) -> TypedStatement { 271 match statement { 272 Statement::Expression(expression_ast) => { 273 Statement::Expression(self.expression(expression_ast)) 274 } 275 Statement::Assignment(assignment_ast) => { 276 Statement::Assignment(Box::new(self.assignment(*assignment_ast))) 277 } 278 Statement::Use(use_ast) => Statement::Use(self.use_(use_ast)), 279 Statement::Assert(assert_ast) => Statement::Assert(self.assert(assert_ast)), 280 } 281 } 282 283 fn assert(&mut self, assert: TypedAssert) -> TypedAssert { 284 let Assert { 285 location, 286 value, 287 message, 288 } = assert; 289 290 Assert { 291 location, 292 value: self.expression(value), 293 message: message.map(|expression| self.expression(expression)), 294 } 295 } 296 297 fn use_(&mut self, mut use_: TypedUse) -> TypedUse { 298 use_.call = self.boxed_expression(use_.call); 299 use_ 300 } 301 302 fn assignment(&mut self, assignment: TypedAssignment) -> TypedAssignment { 303 let Assignment { 304 location, 305 value, 306 pattern, 307 kind, 308 annotation, 309 compiled_case, 310 } = assignment; 311 312 Assignment { 313 location, 314 value: self.expression(value), 315 pattern: self.register_pattern_variables(pattern), 316 kind: self.assignment_kind(kind), 317 annotation, 318 compiled_case, 319 } 320 } 321 322 /// Register variables defined in a pattern so we correctly keep track of 323 /// the scope, and rename any which conflict with existing variables. 324 fn register_pattern_variables(&mut self, pattern: TypedPattern) -> TypedPattern { 325 match pattern { 326 Pattern::Int { .. } 327 | Pattern::Float { .. } 328 | Pattern::String { .. } 329 | Pattern::Discard { .. } 330 | Pattern::Invalid { .. } => pattern, 331 332 Pattern::Variable { 333 location, 334 name, 335 type_, 336 origin, 337 } => Pattern::Variable { 338 location, 339 name: self.define_variable(name), 340 type_, 341 origin, 342 }, 343 Pattern::BitArraySize(size) => Pattern::BitArraySize(self.bit_array_size(size)), 344 Pattern::Assign { 345 name, 346 location, 347 pattern, 348 } => Pattern::Assign { 349 name: self.define_variable(name), 350 location, 351 pattern: Box::new(self.register_pattern_variables(*pattern)), 352 }, 353 Pattern::List { 354 location, 355 elements, 356 tail, 357 type_, 358 } => Pattern::List { 359 location, 360 elements: elements 361 .into_iter() 362 .map(|element| self.register_pattern_variables(element)) 363 .collect(), 364 tail: tail.map(|tail| { 365 Box::new(TailPattern { 366 location: tail.location, 367 pattern: self.register_pattern_variables(tail.pattern), 368 }) 369 }), 370 type_, 371 }, 372 Pattern::Constructor { 373 location, 374 name_location, 375 name, 376 arguments, 377 module, 378 constructor, 379 spread, 380 type_, 381 } => Pattern::Constructor { 382 location, 383 name_location, 384 name, 385 arguments: arguments 386 .into_iter() 387 .map( 388 |CallArg { 389 label, 390 location, 391 value, 392 implicit, 393 }| CallArg { 394 label, 395 location, 396 value: self.register_pattern_variables(value), 397 implicit, 398 }, 399 ) 400 .collect(), 401 module, 402 constructor, 403 spread, 404 type_, 405 }, 406 Pattern::Tuple { location, elements } => Pattern::Tuple { 407 location, 408 elements: elements 409 .into_iter() 410 .map(|element| self.register_pattern_variables(element)) 411 .collect(), 412 }, 413 Pattern::BitArray { location, segments } => Pattern::BitArray { 414 location, 415 segments: segments 416 .into_iter() 417 .map(|segment| { 418 self.bit_array_segment(segment, Self::register_pattern_variables) 419 }) 420 .collect(), 421 }, 422 Pattern::StringPrefix { 423 location, 424 left_location, 425 left_side_assignment, 426 right_location, 427 left_side_string, 428 right_side_assignment, 429 } => Pattern::StringPrefix { 430 location, 431 left_location, 432 left_side_assignment: left_side_assignment 433 .map(|(name, location)| (self.define_variable(name), location)), 434 right_location, 435 left_side_string, 436 right_side_assignment: match right_side_assignment { 437 AssignName::Variable(name) => AssignName::Variable(self.define_variable(name)), 438 AssignName::Discard(name) => AssignName::Discard(name), 439 }, 440 }, 441 } 442 } 443 444 fn bit_array_size(&mut self, size: TypedBitArraySize) -> TypedBitArraySize { 445 match size { 446 BitArraySize::Int { .. } => size, 447 BitArraySize::Variable { 448 location, 449 name, 450 constructor, 451 type_, 452 } => BitArraySize::Variable { 453 location, 454 name: self.variable_name(name), 455 constructor, 456 type_, 457 }, 458 BitArraySize::BinaryOperator { 459 location, 460 operator, 461 left, 462 right, 463 } => BitArraySize::BinaryOperator { 464 location, 465 operator, 466 left: Box::new(self.bit_array_size(*left)), 467 right: Box::new(self.bit_array_size(*right)), 468 }, 469 BitArraySize::Block { location, inner } => BitArraySize::Block { 470 location, 471 inner: Box::new(self.bit_array_size(*inner)), 472 }, 473 } 474 } 475 476 fn assignment_kind(&mut self, kind: AssignmentKind<TypedExpr>) -> AssignmentKind<TypedExpr> { 477 match kind { 478 AssignmentKind::Let | AssignmentKind::Generated => kind, 479 AssignmentKind::Assert { 480 location, 481 assert_keyword_start, 482 message, 483 } => AssignmentKind::Assert { 484 location, 485 assert_keyword_start, 486 message: message.map(|expression| self.expression(expression)), 487 }, 488 } 489 } 490 491 fn boxed_expression(&mut self, boxed: Box<TypedExpr>) -> Box<TypedExpr> { 492 Box::new(self.expression(*boxed)) 493 } 494 495 fn expressions(&mut self, expressions: Vec<TypedExpr>) -> Vec<TypedExpr> { 496 expressions 497 .into_iter() 498 .map(|expression| self.expression(expression)) 499 .collect() 500 } 501 502 /// Perform inlining over an expression. This function is recursive, as 503 /// expressions can be deeply nested. Most expressions just recursively 504 /// call this function on each of their component parts, but some have 505 /// special handling. 506 fn expression(&mut self, mut expression: TypedExpr) -> TypedExpr { 507 match expression { 508 TypedExpr::Int { .. } 509 | TypedExpr::Float { .. } 510 | TypedExpr::String { .. } 511 | TypedExpr::Fn { .. } 512 | TypedExpr::ModuleSelect { .. } 513 | TypedExpr::Invalid { .. } => expression, 514 515 TypedExpr::Var { 516 ref constructor, 517 ref mut name, 518 .. 519 } => match &constructor.variant { 520 // If this variable can be inlined, replace it with its value. 521 // See the `inline_variables` documentation for an explanation. 522 ValueConstructorVariant::LocalVariable { .. } => { 523 // We remove the variable as inlined variables can only be 524 // inlined once. `inline_variables` only contains variables 525 // which we have already checked are possible to inline, as 526 // we check for variables which are only used once when converting 527 // to an `InlinableFunction`. 528 match self.inline_variables.remove(name) { 529 Some(inlined_expression) => inlined_expression, 530 None => match self.renamed_variables.get(name) { 531 Some(new_name) => { 532 *name = new_name.clone(); 533 expression 534 } 535 None => expression, 536 }, 537 } 538 } 539 ValueConstructorVariant::ModuleConstant { .. } 540 | ValueConstructorVariant::ModuleFn { .. } 541 | ValueConstructorVariant::Record { .. } => expression, 542 }, 543 544 TypedExpr::Block { 545 location, 546 statements, 547 } => TypedExpr::Block { 548 location, 549 statements: statements.mapped(|statement| self.statement(statement)), 550 }, 551 552 TypedExpr::NegateBool { location, value } => TypedExpr::NegateBool { 553 location, 554 value: self.boxed_expression(value), 555 }, 556 557 TypedExpr::NegateInt { location, value } => TypedExpr::NegateInt { 558 location, 559 value: self.boxed_expression(value), 560 }, 561 562 TypedExpr::Pipeline { 563 location, 564 first_value, 565 assignments, 566 finally, 567 finally_kind, 568 } => self.pipeline(location, first_value, assignments, finally, finally_kind), 569 570 TypedExpr::List { 571 location, 572 type_, 573 elements, 574 tail, 575 } => TypedExpr::List { 576 location, 577 type_, 578 elements: self.expressions(elements), 579 tail: tail.map(|boxed_expression| self.boxed_expression(boxed_expression)), 580 }, 581 582 TypedExpr::Call { 583 location, 584 type_, 585 fun, 586 arguments, 587 } => self.call(location, type_, fun, arguments), 588 589 TypedExpr::BinOp { 590 location, 591 type_, 592 name, 593 name_location, 594 left, 595 right, 596 } => TypedExpr::BinOp { 597 location, 598 type_, 599 name, 600 name_location, 601 left: self.boxed_expression(left), 602 right: self.boxed_expression(right), 603 }, 604 605 TypedExpr::Case { 606 location, 607 type_, 608 subjects, 609 clauses, 610 compiled_case, 611 } => self.case(location, type_, subjects, clauses, compiled_case), 612 613 TypedExpr::RecordAccess { 614 location, 615 field_start, 616 type_, 617 label, 618 index, 619 record, 620 documentation, 621 } => TypedExpr::RecordAccess { 622 location, 623 field_start, 624 type_, 625 label, 626 index, 627 record: self.boxed_expression(record), 628 documentation, 629 }, 630 631 TypedExpr::PositionalAccess { 632 location, 633 type_, 634 index, 635 record, 636 } => TypedExpr::PositionalAccess { 637 location, 638 type_, 639 index, 640 record: self.boxed_expression(record), 641 }, 642 643 TypedExpr::Tuple { 644 location, 645 type_, 646 elements, 647 } => TypedExpr::Tuple { 648 location, 649 type_, 650 elements: self.expressions(elements), 651 }, 652 653 TypedExpr::TupleIndex { 654 location, 655 type_, 656 index, 657 tuple, 658 } => TypedExpr::TupleIndex { 659 location, 660 type_, 661 index, 662 tuple: self.boxed_expression(tuple), 663 }, 664 665 TypedExpr::Todo { 666 location, 667 message, 668 kind, 669 type_, 670 } => TypedExpr::Todo { 671 location, 672 message: message.map(|boxed_expression| self.boxed_expression(boxed_expression)), 673 kind, 674 type_, 675 }, 676 677 TypedExpr::Panic { 678 location, 679 message, 680 type_, 681 } => TypedExpr::Panic { 682 location, 683 message: message.map(|boxed_expression| self.boxed_expression(boxed_expression)), 684 type_, 685 }, 686 687 TypedExpr::Echo { 688 location, 689 type_, 690 expression, 691 message, 692 } => TypedExpr::Echo { 693 location, 694 expression: expression.map(|expression| self.boxed_expression(expression)), 695 message: message.map(|message| self.boxed_expression(message)), 696 type_, 697 }, 698 699 TypedExpr::BitArray { 700 location, 701 type_, 702 segments, 703 } => self.bit_array(location, type_, segments), 704 705 TypedExpr::RecordUpdate { 706 location, 707 type_, 708 record_assignment, 709 constructor, 710 arguments, 711 } => TypedExpr::RecordUpdate { 712 location, 713 type_, 714 record_assignment: record_assignment 715 .map(|assignment| Box::new(self.assignment(*assignment))), 716 constructor: self.boxed_expression(constructor), 717 arguments: self.arguments(arguments), 718 }, 719 } 720 } 721 722 fn arguments(&mut self, arguments: Vec<TypedCallArg>) -> Vec<TypedCallArg> { 723 arguments 724 .into_iter() 725 .map( 726 |TypedCallArg { 727 label, 728 location, 729 value, 730 implicit, 731 }| TypedCallArg { 732 label, 733 location, 734 value: self.expression(value), 735 implicit, 736 }, 737 ) 738 .collect() 739 } 740 741 /// Where the magic happens. First, we check the left-hand side of the call 742 /// to see if it's something we can inline. If not, we continue to walk the 743 /// tree like all the other expressions do. If it can be inlined, we follow 744 /// a three-step process: 745 /// 746 /// - Inlining: Here, we replace the reference to the function with an 747 /// anonymous function with the same contents. If the left-hand side is 748 /// already an anonymous function, we skip this step. 749 /// 750 /// - Beta reduction: The call to the anonymous function it transformed into 751 /// a block with assignments for each argument at the beginning 752 /// 753 /// - Optimisation: We then recursively optimise the block. This allows us 754 /// to, for example, inline anonymous functions passed to higher-order 755 /// functions. 756 /// 757 /// Here is an example of inlining `result.map`: 758 /// 759 /// Initial code: 760 /// ```gleam 761 /// let x = Ok(10) 762 /// result.map(x, fn(x) { 763 /// let y = x + 4 764 /// int.to_string(y) 765 /// }) 766 /// ``` 767 /// 768 /// After inlining: 769 /// ```gleam 770 /// let x = Ok(10) 771 /// fn(result, function) { 772 /// case result { 773 /// Ok(value) -> Ok(function(value)) 774 /// Error(error) -> Error(error) 775 /// } 776 /// }(x, fn(x) { 777 /// let y = x + 4 778 /// int.to_string(y) 779 /// }) 780 /// ``` 781 /// 782 /// After beta reduction: 783 /// ```gleam 784 /// let x = Ok(10) 785 /// { 786 /// let result = x 787 /// let function = fn(x) { 788 /// let y = x + 4 789 /// int.to_string(y) 790 /// } 791 /// case result { 792 /// Ok(value) -> Ok(function(value)) 793 /// Error(error) -> Error(error) 794 /// } 795 /// } 796 /// ``` 797 /// 798 /// And finally, after the final optimising pass, where this inlining process 799 /// is repeated: 800 /// ```gleam 801 /// let x = Ok(10) 802 /// case x { 803 /// Ok(value) -> Ok({ 804 /// let y = x + 4 805 /// int.to_string(y) 806 /// }) 807 /// Error(error) -> Error(error) 808 /// } 809 /// ``` 810 /// 811 fn call( 812 &mut self, 813 location: SrcSpan, 814 type_: Arc<Type>, 815 function: Box<TypedExpr>, 816 arguments: Vec<TypedCallArg>, 817 ) -> TypedExpr { 818 let arguments = self.arguments(arguments); 819 820 // First, we traverse the left-hand side of this call. If this is called 821 // inside another inlined function, this could potentially inline an 822 // argument, allowing further inlining. 823 let function = self.expression(*function); 824 825 // If the left-hand side is in a block for some reason, for example 826 // `{ fn(x) { x + 1 } }(10)`, we still want to be able to inline it. 827 let function = expand_block(function); 828 829 let function = match function { 830 TypedExpr::Var { 831 ref constructor, 832 ref name, 833 .. 834 } => match &constructor.variant { 835 ValueConstructorVariant::ModuleFn { module, .. } => { 836 // If the function is in the list of inlinable functions in 837 // the module it belongs to, we can inline it! 838 if let Some(function) = self 839 .modules 840 .get(module) 841 .and_then(|module| module.inline_functions.get(name)) 842 { 843 // First, we do the actual inlining, by converting it to 844 // an anonymous function. 845 let (parameters, body) = function.to_anonymous_function(); 846 // Then, we perform beta reduction, inlining the call to 847 // the anonymous function. 848 return self.inline_anonymous_function_call( 849 &parameters, 850 arguments, 851 body, 852 &function.inlinable_parameters, 853 ); 854 } else { 855 function 856 } 857 } 858 // We cannot inline local variables or constants, as we do not 859 // have enough information to inline them. Records are not actually 860 // function calls, so they also cannot be inlined. 861 ValueConstructorVariant::LocalVariable { .. } 862 | ValueConstructorVariant::ModuleConstant { .. } 863 | ValueConstructorVariant::Record { .. } => function, 864 }, 865 TypedExpr::ModuleSelect { 866 ref constructor, 867 label: ref name, 868 ref module_name, 869 .. 870 } => match constructor { 871 // We use the same logic here as for `TypedExpr::Var` above. 872 ModuleValueConstructor::Fn { .. } => { 873 if let Some(function) = self 874 .modules 875 .get(module_name) 876 .and_then(|module| module.inline_functions.get(name)) 877 { 878 let (parameters, body) = function.to_anonymous_function(); 879 return self.inline_anonymous_function_call( 880 &parameters, 881 arguments, 882 body, 883 &function.inlinable_parameters, 884 ); 885 } else { 886 function 887 } 888 } 889 ModuleValueConstructor::Record { .. } | ModuleValueConstructor::Constant { .. } => { 890 function 891 } 892 }, 893 // Direct calls to anonymous functions can always be inlined 894 TypedExpr::Fn { 895 arguments: parameters, 896 body, 897 .. 898 } => { 899 let inlinable_parameters = find_inlinable_parameters(&parameters, &body); 900 return self.inline_anonymous_function_call( 901 &parameters, 902 arguments, 903 body, 904 &inlinable_parameters, 905 ); 906 } 907 TypedExpr::Int { .. } 908 | TypedExpr::Float { .. } 909 | TypedExpr::String { .. } 910 | TypedExpr::Block { .. } 911 | TypedExpr::Pipeline { .. } 912 | TypedExpr::List { .. } 913 | TypedExpr::Call { .. } 914 | TypedExpr::BinOp { .. } 915 | TypedExpr::Case { .. } 916 | TypedExpr::RecordAccess { .. } 917 | TypedExpr::PositionalAccess { .. } 918 | TypedExpr::Tuple { .. } 919 | TypedExpr::TupleIndex { .. } 920 | TypedExpr::Todo { .. } 921 | TypedExpr::Panic { .. } 922 | TypedExpr::Echo { .. } 923 | TypedExpr::BitArray { .. } 924 | TypedExpr::RecordUpdate { .. } 925 | TypedExpr::NegateBool { .. } 926 | TypedExpr::NegateInt { .. } 927 | TypedExpr::Invalid { .. } => function, 928 }; 929 930 TypedExpr::Call { 931 location, 932 type_, 933 fun: Box::new(function), 934 arguments, 935 } 936 } 937 938 /// Turn a call to an anonymous function into a block with assignments. 939 fn inline_anonymous_function_call( 940 &mut self, 941 parameters: &[TypedArg], 942 arguments: Vec<TypedCallArg>, 943 body: Vec1<TypedStatement>, 944 inlinable_parameters: &[EcoString], 945 ) -> TypedExpr { 946 // Arguments to this call that can be inlined, and do not need an assignment. 947 let mut inline = HashMap::new(); 948 949 // We start by collecting all the assignments for parameters which cannot 950 // be inlined. 951 let mut statements = parameters 952 .iter() 953 .zip(arguments) 954 .filter_map(|(parameter, argument)| { 955 let name = parameter.get_variable_name().cloned().unwrap_or("_".into()); 956 957 // An argument can be inlined if it is only used once (stored in 958 // the `InlineFunction` structure), and it is pure. Sometime impure 959 // arguments can be inlined, but for simplicity we avoid inlining 960 // all impure arguments for now. This heuristic can be improved 961 // later. 962 if inlinable_parameters.contains(&name) 963 && argument.value.is_pure_value_constructor() 964 { 965 _ = inline.insert(name, argument.value); 966 return None; 967 } 968 969 let type_ = argument.value.type_(); 970 971 // Register the variable in scope, so that it is renamed if 972 // necessary. 973 let name = self.define_variable(name); 974 975 // Otherwise, we make an assignment which assigns the value of 976 // the argument to the correct parameter name. 977 Some(Statement::Assignment(Box::new(Assignment { 978 location: BLANK_LOCATION, 979 value: argument.value, 980 pattern: TypedPattern::Variable { 981 location: BLANK_LOCATION, 982 name: name.clone(), 983 type_: type_.clone(), 984 origin: VariableOrigin::generated(), 985 }, 986 kind: AssignmentKind::Generated, 987 compiled_case: CompiledCase::simple_variable_assignment(name, type_), 988 annotation: None, 989 }))) 990 }) 991 .collect_vec(); 992 993 // If we are performing inlining within an already inlined function, there 994 // might be inlinable variables in the outer scope. However, these cannot be 995 // inlined inside a nested function, so they are saved and restored afterwards. 996 let inline_variables = std::mem::replace(&mut self.inline_variables, inline); 997 let position = self.position; 998 self.position = Position::InlinedFunction; 999 let variables = self.renamed_variables.clone(); 1000 1001 // Perform inlining on each of the statements in this function's body, 1002 // potentially inlining parameters and function calls inside this function. 1003 statements.extend(body.into_iter().map(|statement| self.statement(statement))); 1004 1005 // Restore scope 1006 self.inline_variables = inline_variables; 1007 self.position = position; 1008 self.renamed_variables = variables; 1009 1010 // We try to expand this block, so a function which is inlined as a 1011 // single expression does not get wrapped unnecessarily 1012 expand_block(TypedExpr::Block { 1013 location: BLANK_LOCATION, 1014 statements: statements 1015 .try_into() 1016 .expect("Type checking ensures there is at least one statement"), 1017 }) 1018 } 1019 1020 fn pipeline( 1021 &mut self, 1022 location: SrcSpan, 1023 first_value: TypedPipelineAssignment, 1024 assignments: Vec<(TypedPipelineAssignment, PipelineAssignmentKind)>, 1025 finally: Box<TypedExpr>, 1026 finally_kind: PipelineAssignmentKind, 1027 ) -> TypedExpr { 1028 let first_value = self.pipeline_assignment(first_value); 1029 let assignments = assignments 1030 .into_iter() 1031 .map(|(assignment, kind)| (self.pipeline_assignment(assignment), kind)) 1032 .collect(); 1033 let finally = self.boxed_expression(finally); 1034 1035 TypedExpr::Pipeline { 1036 location, 1037 first_value, 1038 assignments, 1039 finally, 1040 finally_kind, 1041 } 1042 } 1043 1044 fn pipeline_assignment( 1045 &mut self, 1046 assignment: TypedPipelineAssignment, 1047 ) -> TypedPipelineAssignment { 1048 let TypedPipelineAssignment { 1049 location, 1050 name, 1051 value, 1052 } = assignment; 1053 1054 TypedPipelineAssignment { 1055 location, 1056 name, 1057 value: self.boxed_expression(value), 1058 } 1059 } 1060 1061 fn bit_array( 1062 &mut self, 1063 location: SrcSpan, 1064 type_: Arc<Type>, 1065 segments: Vec<TypedExprBitArraySegment>, 1066 ) -> TypedExpr { 1067 let segments = segments 1068 .into_iter() 1069 .map(|segment| self.bit_array_segment(segment, Self::expression)) 1070 .collect(); 1071 1072 TypedExpr::BitArray { 1073 location, 1074 type_, 1075 segments, 1076 } 1077 } 1078 1079 fn bit_array_segment<Value>( 1080 &mut self, 1081 segment: BitArraySegment<Value, Arc<Type>>, 1082 function: fn(&mut Self, Value) -> Value, 1083 ) -> BitArraySegment<Value, Arc<Type>> { 1084 let BitArraySegment { 1085 location, 1086 value, 1087 options, 1088 type_, 1089 } = segment; 1090 1091 BitArraySegment { 1092 location, 1093 value: Box::new(function(self, *value)), 1094 options: options 1095 .into_iter() 1096 .map(|option| self.bit_array_option(option, function)) 1097 .collect(), 1098 type_, 1099 } 1100 } 1101 1102 fn bit_array_option<Value>( 1103 &mut self, 1104 option: BitArrayOption<Value>, 1105 function: fn(&mut Self, Value) -> Value, 1106 ) -> BitArrayOption<Value> { 1107 match option { 1108 BitArrayOption::Bytes { .. } 1109 | BitArrayOption::Int { .. } 1110 | BitArrayOption::Float { .. } 1111 | BitArrayOption::Bits { .. } 1112 | BitArrayOption::Utf8 { .. } 1113 | BitArrayOption::Utf16 { .. } 1114 | BitArrayOption::Utf32 { .. } 1115 | BitArrayOption::Utf8Codepoint { .. } 1116 | BitArrayOption::Utf16Codepoint { .. } 1117 | BitArrayOption::Utf32Codepoint { .. } 1118 | BitArrayOption::Signed { .. } 1119 | BitArrayOption::Unsigned { .. } 1120 | BitArrayOption::Big { .. } 1121 | BitArrayOption::Little { .. } 1122 | BitArrayOption::Native { .. } 1123 | BitArrayOption::Unit { .. } => option, 1124 BitArrayOption::Size { 1125 location, 1126 value, 1127 short_form, 1128 } => BitArrayOption::Size { 1129 location, 1130 value: Box::new(function(self, *value)), 1131 short_form, 1132 }, 1133 } 1134 } 1135 1136 fn case( 1137 &mut self, 1138 location: SrcSpan, 1139 type_: Arc<Type>, 1140 subjects: Vec<TypedExpr>, 1141 clauses: Vec<TypedClause>, 1142 compiled_case: CompiledCase, 1143 ) -> TypedExpr { 1144 let subjects = self.expressions(subjects); 1145 let clauses = clauses 1146 .into_iter() 1147 .map(|clause| self.case_clause(clause)) 1148 .collect(); 1149 1150 // Since JavaScript code generation uses the decision tree to generate 1151 // code for `case` expressions, we need to rename the variables bound 1152 // in the decision tree too. Because we have already renamed the variables 1153 // in the pattern, we can simply look up the rebound names. 1154 let compiled_case = CompiledCase { 1155 tree: self.decision(compiled_case.tree), 1156 subject_variables: compiled_case.subject_variables, 1157 }; 1158 1159 TypedExpr::Case { 1160 location, 1161 type_, 1162 subjects, 1163 clauses, 1164 compiled_case, 1165 } 1166 } 1167 1168 fn case_clause(&mut self, clause: TypedClause) -> TypedClause { 1169 let Clause { 1170 location, 1171 pattern, 1172 alternative_patterns, 1173 guard, 1174 then, 1175 } = clause; 1176 1177 let pattern = pattern 1178 .into_iter() 1179 .map(|pattern| self.register_pattern_variables(pattern)) 1180 .collect(); 1181 1182 let alternative_patterns = alternative_patterns 1183 .into_iter() 1184 .map(|patterns| { 1185 patterns 1186 .into_iter() 1187 .map(|pattern| self.register_pattern_variables(pattern)) 1188 .collect() 1189 }) 1190 .collect(); 1191 1192 let then = self.expression(then); 1193 1194 Clause { 1195 location, 1196 pattern, 1197 alternative_patterns, 1198 guard, 1199 then, 1200 } 1201 } 1202 1203 fn decision(&self, decision: Decision) -> Decision { 1204 match decision { 1205 Decision::Run { body } => Decision::Run { 1206 body: self.case_body(body), 1207 }, 1208 Decision::Guard { 1209 guard, 1210 if_true, 1211 if_false, 1212 } => Decision::Guard { 1213 guard, 1214 if_true: self.case_body(if_true), 1215 if_false: Box::new(self.decision(*if_false)), 1216 }, 1217 Decision::Switch { 1218 var, 1219 choices, 1220 fallback, 1221 fallback_check, 1222 } => Decision::Switch { 1223 var, 1224 choices: choices 1225 .into_iter() 1226 .map(|(check, decision)| (check, self.decision(decision))) 1227 .collect(), 1228 fallback: Box::new(self.decision(*fallback)), 1229 fallback_check, 1230 }, 1231 Decision::Fail => Decision::Fail, 1232 } 1233 } 1234 1235 fn case_body(&self, body: Body) -> Body { 1236 let Body { 1237 bindings, 1238 clause_index, 1239 } = body; 1240 1241 let bindings = bindings 1242 .into_iter() 1243 // We do this after renaming the variables in the pattern, so we can 1244 // just lookup the new name, rather than renaming again. 1245 .map(|(name, value)| (self.variable_name(name), value)) 1246 .collect(); 1247 1248 Body { 1249 bindings, 1250 clause_index, 1251 } 1252 } 1253} 1254 1255fn find_inlinable_parameters(parameters: &[TypedArg], body: &[TypedStatement]) -> Vec<EcoString> { 1256 let mut parameter_map = HashMap::new(); 1257 for parameter in parameters { 1258 let (name, location) = match &parameter.names { 1259 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => continue, 1260 ArgNames::Named { name, location } 1261 | ArgNames::NamedLabelled { 1262 name, 1263 name_location: location, 1264 .. 1265 } => (name, location), 1266 }; 1267 _ = parameter_map.insert((name.clone(), *location), false); 1268 } 1269 1270 let mut finder = FindInlinableParameters { 1271 parameters: parameter_map, 1272 position: FunctionPosition::Body, 1273 }; 1274 for statement in body { 1275 finder.visit_typed_statement(statement); 1276 } 1277 1278 // Inlinable parameters are those that are used exactly once. Any parameters 1279 // used more than once will be removed from this map and so will not be 1280 // considered inlinable. 1281 finder 1282 .parameters 1283 .into_iter() 1284 .filter_map(|((name, _), used)| used.then_some(name)) 1285 .collect() 1286} 1287 1288/// A struct for finding the inlinable parameters of an anonymous function. Since 1289/// we want to inline all anonymous functions, not just a subset of them like we 1290/// do with regular functions, this must be implemented slightly differently, but 1291/// it means we can take advantage of the AST visitor, since we don't need to 1292/// transform the anonymous function into an intermediate representation. 1293struct FindInlinableParameters { 1294 parameters: HashMap<(EcoString, SrcSpan), bool>, 1295 position: FunctionPosition, 1296} 1297 1298#[derive(Debug, Clone, Copy)] 1299enum FunctionPosition { 1300 Body, 1301 NestedFunction, 1302} 1303 1304impl FindInlinableParameters { 1305 fn register_reference(&mut self, name: &EcoString, location: SrcSpan) { 1306 let key = (name.clone(), location); 1307 1308 match self.position { 1309 FunctionPosition::Body => {} 1310 // We don't inline any parameters which are referenced in nested 1311 // anonymous function, as our system for inlining parameters cannot 1312 // properly handle that case; it requires more complex rewriting of 1313 // the code in some cases. 1314 FunctionPosition::NestedFunction => { 1315 _ = self.parameters.remove(&key); 1316 return; 1317 } 1318 } 1319 1320 match self.parameters.get_mut(&key) { 1321 Some(true) => _ = self.parameters.remove(&key), 1322 Some(used @ false) => { 1323 *used = true; 1324 } 1325 None => {} 1326 } 1327 } 1328} 1329 1330impl<'ast> Visit<'ast> for FindInlinableParameters { 1331 fn visit_typed_expr_var( 1332 &mut self, 1333 _location: &'ast SrcSpan, 1334 constructor: &'ast ValueConstructor, 1335 name: &'ast EcoString, 1336 ) { 1337 if let ValueConstructorVariant::LocalVariable { location, .. } = constructor.variant { 1338 self.register_reference(name, location); 1339 } 1340 } 1341 1342 fn visit_typed_clause_guard_var( 1343 &mut self, 1344 _location: &'ast SrcSpan, 1345 name: &'ast EcoString, 1346 _type_: &'ast Arc<Type>, 1347 location: &'ast SrcSpan, 1348 ) { 1349 self.register_reference(name, *location); 1350 } 1351 1352 fn visit_typed_bit_array_size_variable( 1353 &mut self, 1354 _location: &'ast SrcSpan, 1355 name: &'ast EcoString, 1356 constructor: &'ast Option<Box<ValueConstructor>>, 1357 _type_: &'ast Arc<Type>, 1358 ) { 1359 let variant = match constructor { 1360 Some(constructor) => &constructor.variant, 1361 None => return, 1362 }; 1363 if let ValueConstructorVariant::LocalVariable { location, .. } = variant { 1364 self.register_reference(name, *location); 1365 } 1366 } 1367 1368 fn visit_typed_expr_fn( 1369 &mut self, 1370 location: &'ast SrcSpan, 1371 type_: &'ast Arc<Type>, 1372 kind: &'ast FunctionLiteralKind, 1373 args: &'ast [TypedArg], 1374 body: &'ast Vec1<TypedStatement>, 1375 return_annotation: &'ast Option<ast::TypeAst>, 1376 ) { 1377 let previous_position = self.position; 1378 self.position = FunctionPosition::NestedFunction; 1379 1380 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); 1381 1382 self.position = previous_position; 1383 } 1384} 1385 1386/// Removes any blocks which are acting as brackets (they hold a single expression) 1387fn expand_block(expression: TypedExpr) -> TypedExpr { 1388 match expression { 1389 TypedExpr::Block { 1390 location, 1391 statements, 1392 } if statements.len() == 1 => { 1393 let (first, _rest) = statements.split_off_first(); 1394 1395 match first { 1396 // If this is several blocks inside each other, we want to 1397 // expand them all. 1398 Statement::Expression(inner) => expand_block(inner), 1399 Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => { 1400 TypedExpr::Block { 1401 location, 1402 statements: Vec1::new(first), 1403 } 1404 } 1405 } 1406 } 1407 _ => expression, 1408 } 1409} 1410 1411/// Converts a function from the Gleam AST into a special "inlinable function", 1412/// which is a simplified version, containing just enough information for us to 1413/// perform inlining, while keeping the cache files to a minimum size. 1414/// 1415/// This function also determines whether a function is inlinable. Currently this 1416/// just checks it against a list of stdlib functions we want to prioritise 1417/// inlining, but later it will be changed to a more complicated heuristic. 1418/// 1419pub fn function_to_inlinable( 1420 package: &str, 1421 module: &str, 1422 function: &TypedFunction, 1423) -> Option<InlinableFunction> { 1424 let (_, name) = function.name.as_ref()?; 1425 1426 if !is_inlinable(package, module, name) { 1427 return None; 1428 } 1429 1430 let parameters = function 1431 .arguments 1432 .iter() 1433 .map(|argument| match &argument.names { 1434 ArgNames::Discard { name, .. } | ArgNames::Named { name, .. } => InlinableParameter { 1435 label: None, 1436 name: name.clone(), 1437 }, 1438 ArgNames::LabelledDiscard { label, name, .. } 1439 | ArgNames::NamedLabelled { label, name, .. } => InlinableParameter { 1440 label: Some(label.clone()), 1441 name: name.clone(), 1442 }, 1443 }) 1444 .collect(); 1445 1446 let mut converter = FunctionToInlinable::new(&function.arguments); 1447 1448 let body = function 1449 .body 1450 .iter() 1451 .map(|statement| converter.statement(statement)) 1452 .collect::<Option<_>>()?; 1453 1454 // Figure out which parameters can be inlined within the body of this function. 1455 // When we inline a function, we convert it to a block with assignments for 1456 // the parameters. Then, if those parameters contain no side effects and are 1457 // only referenced once within the body, they can be inlined into the place 1458 // they are referenced. 1459 // 1460 // This code checks for the parameters which are used exactly once, which 1461 // can then be inlined. We can't inline parameters which are never used, as 1462 // there is nowhere to inline them to, so it doesn't make sense. We still 1463 // need to evaluate them though, as there could be side effects caused by 1464 // the values passed to them. 1465 let inlinable_parameters = converter 1466 .parameter_references 1467 .into_iter() 1468 .filter_map(|((name, _), used)| used.then_some(name)) 1469 .collect(); 1470 1471 Some(InlinableFunction { 1472 parameters, 1473 body, 1474 inlinable_parameters, 1475 }) 1476} 1477 1478/// The heuristic to determine whether a function is inlinable. For now, this 1479/// just checks against a list of standard library functions. 1480fn is_inlinable(package: &str, module: &str, name: &str) -> bool { 1481 // For now we only offer inlining of standard library functions 1482 if package != STDLIB_PACKAGE_NAME { 1483 return false; 1484 } 1485 1486 match (module, name) { 1487 // These are the functions which we currently inline 1488 ("gleam/bool", "guard") => true, 1489 ("gleam/bool", "lazy_guard") => true, 1490 ("gleam/result", "try") => true, 1491 ("gleam/result", "map") => true, 1492 ("gleam/result", "map_error") => true, 1493 // For testing purposes it's useful to have a function which will always 1494 // be inlined, which we can define however we want. We only inline this 1495 // when we are in test mode though, because we wouldn't want this detail 1496 // leaking out into real-world code and causing unexpected behaviour. 1497 ("testing", "always_inline") => cfg!(test), 1498 _ => false, 1499 } 1500} 1501 1502/// Holds state for converting a `TypedFunction` into an `InlinableFunction`. 1503struct FunctionToInlinable { 1504 /// A map of parameters to a boolean of whether they have been used. Since 1505 /// Gleam has variable shadowing, we must also store the definition location 1506 /// of each parameter to ensure that it is not a variable shadowing the parameter 1507 /// name. 1508 /// If a parameter is used more than once, it is removed from the map, so it 1509 /// is no longer tracked as an inlinable parameter. 1510 parameter_references: HashMap<(EcoString, SrcSpan), bool>, 1511} 1512 1513impl FunctionToInlinable { 1514 fn new(arguments: &[TypedArg]) -> Self { 1515 let parameter_references = arguments 1516 .iter() 1517 .filter_map(|argument| { 1518 let (name, location) = match &argument.names { 1519 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => return None, 1520 ArgNames::Named { name, location } => (name.clone(), *location), 1521 ArgNames::NamedLabelled { 1522 name, 1523 name_location, 1524 .. 1525 } => (name.clone(), *name_location), 1526 }; 1527 1528 Some(((name, location), false)) 1529 }) 1530 .collect(); 1531 1532 Self { 1533 parameter_references, 1534 } 1535 } 1536 1537 fn statement(&mut self, statement: &TypedStatement) -> Option<InlinableExpression> { 1538 match statement { 1539 Statement::Expression(expression) => self.expression(expression), 1540 Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => None, 1541 } 1542 } 1543 1544 /// Converts an expression to an `InlinableExpression`. We only convert a 1545 /// small subset of the AST for now, enough to compile our desired inlinable 1546 /// stdlib functions. Anything else returns `None`, indicating a function 1547 /// cannot be inlined. 1548 fn expression(&mut self, expression: &TypedExpr) -> Option<InlinableExpression> { 1549 match expression { 1550 TypedExpr::Case { 1551 subjects, 1552 clauses, 1553 compiled_case, 1554 type_, 1555 .. 1556 } => { 1557 let subjects = subjects 1558 .iter() 1559 .map(|expression| self.expression(expression)) 1560 .collect::<Option<_>>()?; 1561 let clauses = clauses 1562 .iter() 1563 .map(|clause| self.clause(clause)) 1564 .collect::<Option<_>>()?; 1565 1566 Some(InlinableExpression::Case { 1567 subjects, 1568 clauses, 1569 compiled_case: Box::new(compiled_case.clone()), 1570 type_: self.type_(type_), 1571 }) 1572 } 1573 TypedExpr::Var { 1574 constructor, name, .. 1575 } => { 1576 match &constructor.variant { 1577 ValueConstructorVariant::LocalVariable { location, .. } => { 1578 let key = (name.clone(), *location); 1579 match self.parameter_references.get_mut(&key) { 1580 Some(true) => { 1581 _ = self.parameter_references.remove(&key); 1582 } 1583 Some(usage) => *usage = true, 1584 None => {} 1585 } 1586 } 1587 ValueConstructorVariant::ModuleConstant { .. } 1588 | ValueConstructorVariant::ModuleFn { .. } 1589 | ValueConstructorVariant::Record { .. } => {} 1590 } 1591 1592 Some(InlinableExpression::Variable { 1593 name: name.clone(), 1594 constructor: self.value_constructor(constructor)?, 1595 type_: self.type_(&constructor.type_), 1596 }) 1597 } 1598 TypedExpr::Call { 1599 fun, 1600 arguments, 1601 type_, 1602 .. 1603 } => { 1604 let function = self.expression(fun)?; 1605 let arguments = arguments 1606 .iter() 1607 .map(|argument| { 1608 Some(InlinableArgument { 1609 label: argument.label.clone(), 1610 value: self.expression(&argument.value)?, 1611 }) 1612 }) 1613 .collect::<Option<_>>()?; 1614 1615 Some(InlinableExpression::Call { 1616 function: Box::new(function), 1617 arguments, 1618 type_: self.type_(type_), 1619 }) 1620 } 1621 1622 TypedExpr::Int { .. } 1623 | TypedExpr::Float { .. } 1624 | TypedExpr::String { .. } 1625 | TypedExpr::Block { .. } 1626 | TypedExpr::Pipeline { .. } 1627 | TypedExpr::Fn { .. } 1628 | TypedExpr::List { .. } 1629 | TypedExpr::BinOp { .. } 1630 | TypedExpr::RecordAccess { .. } 1631 | TypedExpr::PositionalAccess { .. } 1632 | TypedExpr::ModuleSelect { .. } 1633 | TypedExpr::Tuple { .. } 1634 | TypedExpr::TupleIndex { .. } 1635 | TypedExpr::Todo { .. } 1636 | TypedExpr::Panic { .. } 1637 | TypedExpr::Echo { .. } 1638 | TypedExpr::BitArray { .. } 1639 | TypedExpr::RecordUpdate { .. } 1640 | TypedExpr::NegateBool { .. } 1641 | TypedExpr::NegateInt { .. } 1642 | TypedExpr::Invalid { .. } => None, 1643 } 1644 } 1645 1646 fn type_(&self, type_: &Arc<Type>) -> InlinableType { 1647 match collapse_links(type_.clone()).as_ref() { 1648 Type::Fn { arguments, return_ } => InlinableType::Function { 1649 arguments: arguments 1650 .iter() 1651 .map(|argument| self.type_(argument)) 1652 .collect(), 1653 return_: Box::new(self.type_(return_)), 1654 }, 1655 Type::Named { 1656 module, 1657 name, 1658 arguments, 1659 .. 1660 } if module == PRELUDE_MODULE_NAME => self.prelude_type(name, arguments), 1661 Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => InlinableType::Other, 1662 } 1663 } 1664 1665 fn prelude_type(&self, name: &str, arguments: &[Arc<Type>]) -> InlinableType { 1666 match (name, arguments) { 1667 ("BitArray", _) => InlinableType::BitArray, 1668 ("Bool", _) => InlinableType::Bool, 1669 ("Float", _) => InlinableType::Float, 1670 ("Int", _) => InlinableType::Int, 1671 ("List", [element]) => InlinableType::List(Box::new(self.type_(element))), 1672 ("Nil", _) => InlinableType::Nil, 1673 ("Result", [ok, error]) => InlinableType::Result { 1674 ok: Box::new(self.type_(ok)), 1675 error: Box::new(self.type_(error)), 1676 }, 1677 ("String", _) => InlinableType::String, 1678 ("UtfCodepoint", _) => InlinableType::UtfCodepoint, 1679 _ => InlinableType::Other, 1680 } 1681 } 1682 1683 fn value_constructor( 1684 &mut self, 1685 constructor: &ValueConstructor, 1686 ) -> Option<InlinableValueConstructor> { 1687 match &constructor.variant { 1688 ValueConstructorVariant::LocalVariable { .. } => { 1689 Some(InlinableValueConstructor::LocalVariable) 1690 } 1691 ValueConstructorVariant::ModuleConstant { .. } => None, 1692 ValueConstructorVariant::ModuleFn { name, module, .. } => { 1693 Some(InlinableValueConstructor::Function { 1694 name: name.clone(), 1695 module: module.clone(), 1696 }) 1697 } 1698 ValueConstructorVariant::Record { name, module, .. } => { 1699 Some(InlinableValueConstructor::Record { 1700 name: name.clone(), 1701 module: module.clone(), 1702 }) 1703 } 1704 } 1705 } 1706 1707 fn clause(&mut self, clause: &TypedClause) -> Option<InlinableClause> { 1708 let pattern = clause 1709 .pattern 1710 .iter() 1711 .map(Self::pattern) 1712 .collect::<Option<_>>()?; 1713 let body = self.expression(&clause.then)?; 1714 Some(InlinableClause { pattern, body }) 1715 } 1716 1717 fn pattern(pattern: &TypedPattern) -> Option<InlinablePattern> { 1718 match pattern { 1719 TypedPattern::Variable { name, .. } => { 1720 Some(InlinablePattern::Variable { name: name.clone() }) 1721 } 1722 TypedPattern::Constructor { 1723 name, 1724 arguments, 1725 constructor: Inferred::Known(inferred), 1726 .. 1727 } => { 1728 let arguments = arguments 1729 .iter() 1730 .map(|argument| { 1731 Some(InlinableArgument { 1732 label: argument.label.clone(), 1733 value: Self::pattern(&argument.value)?, 1734 }) 1735 }) 1736 .collect::<Option<_>>()?; 1737 1738 Some(InlinablePattern::Constructor { 1739 name: name.clone(), 1740 module: inferred.module.clone(), 1741 arguments, 1742 }) 1743 } 1744 1745 TypedPattern::Constructor { 1746 constructor: Inferred::Unknown, 1747 .. 1748 } => None, 1749 1750 TypedPattern::Int { .. } 1751 | TypedPattern::Float { .. } 1752 | TypedPattern::String { .. } 1753 | TypedPattern::BitArraySize { .. } 1754 | TypedPattern::Assign { .. } 1755 | TypedPattern::Discard { .. } 1756 | TypedPattern::List { .. } 1757 | TypedPattern::Tuple { .. } 1758 | TypedPattern::BitArray { .. } 1759 | TypedPattern::StringPrefix { .. } 1760 | TypedPattern::Invalid { .. } => None, 1761 } 1762 } 1763} 1764 1765/// A simplified version of a `TypedFunction`. 1766#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1767pub struct InlinableFunction { 1768 pub parameters: Vec<InlinableParameter>, 1769 pub body: Vec<InlinableExpression>, 1770 /// A list of parameters which are only referenced once and can therefore 1771 /// be inlined within the body of this function. 1772 pub inlinable_parameters: Vec<EcoString>, 1773} 1774 1775/// Location information is not stored for inlinable functions, to reduce cache 1776/// size. The only reason we should need location information is for generating 1777/// code for panicking keywords, like `panic` or `todo`. 1778/// 1779/// Those are not supported yet, and when they are they will likely require some 1780/// more thought as to how they are implemented, as inlining a function completely 1781/// changes its location in the codebase. 1782const BLANK_LOCATION: SrcSpan = SrcSpan { start: 0, end: 0 }; 1783 1784impl InlinableFunction { 1785 /// Converts an `InlinableFunction` to an anonymous function, which can then 1786 /// be inlined within another function. 1787 fn to_anonymous_function(&self) -> (Vec<TypedArg>, Vec1<TypedStatement>) { 1788 let parameters = self 1789 .parameters 1790 .iter() 1791 .map(|parameter| parameter.to_typed_arg()) 1792 .collect(); 1793 1794 let body = self 1795 .body 1796 .iter() 1797 .map(|ast| Statement::Expression(ast.to_expression())) 1798 .collect_vec(); 1799 1800 ( 1801 parameters, 1802 body.try_into() 1803 .expect("Type-checking ensured that the body has at least 1 statement"), 1804 ) 1805 } 1806} 1807 1808#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1809pub enum InlinableExpression { 1810 Case { 1811 subjects: Vec<InlinableExpression>, 1812 clauses: Vec<InlinableClause>, 1813 compiled_case: Box<CompiledCase>, 1814 type_: InlinableType, 1815 }, 1816 1817 Variable { 1818 name: EcoString, 1819 constructor: InlinableValueConstructor, 1820 type_: InlinableType, 1821 }, 1822 1823 Call { 1824 function: Box<InlinableExpression>, 1825 arguments: Vec<InlinableArgument<InlinableExpression>>, 1826 type_: InlinableType, 1827 }, 1828} 1829 1830impl InlinableExpression { 1831 fn to_expression(&self) -> TypedExpr { 1832 match self { 1833 InlinableExpression::Case { 1834 subjects, 1835 clauses, 1836 compiled_case, 1837 type_, 1838 } => TypedExpr::Case { 1839 location: BLANK_LOCATION, 1840 type_: type_.to_type(), 1841 subjects: subjects 1842 .iter() 1843 .map(|subject| subject.to_expression()) 1844 .collect(), 1845 clauses: clauses 1846 .iter() 1847 .map(|clause| clause.to_typed_clause()) 1848 .collect(), 1849 compiled_case: compiled_case.as_ref().clone(), 1850 }, 1851 InlinableExpression::Variable { 1852 name, 1853 constructor, 1854 type_, 1855 } => TypedExpr::Var { 1856 location: BLANK_LOCATION, 1857 constructor: constructor.to_value_constructor(type_.to_type()), 1858 name: name.clone(), 1859 }, 1860 InlinableExpression::Call { 1861 function, 1862 arguments, 1863 type_, 1864 } => TypedExpr::Call { 1865 location: BLANK_LOCATION, 1866 type_: type_.to_type(), 1867 fun: Box::new(function.to_expression()), 1868 arguments: arguments 1869 .iter() 1870 .map(|argument| argument.to_call_arg(Self::to_expression)) 1871 .collect(), 1872 }, 1873 } 1874 } 1875} 1876 1877#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1878pub struct InlinableClause { 1879 pub pattern: Vec<InlinablePattern>, 1880 pub body: InlinableExpression, 1881} 1882 1883impl InlinableClause { 1884 fn to_typed_clause(&self) -> TypedClause { 1885 TypedClause { 1886 location: BLANK_LOCATION, 1887 pattern: self 1888 .pattern 1889 .iter() 1890 .map(|pattern| pattern.to_typed_pattern()) 1891 .collect(), 1892 alternative_patterns: Vec::new(), 1893 guard: None, 1894 then: self.body.to_expression(), 1895 } 1896 } 1897} 1898 1899#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1900pub enum InlinablePattern { 1901 Constructor { 1902 name: EcoString, 1903 module: EcoString, 1904 arguments: Vec<InlinableArgument<InlinablePattern>>, 1905 }, 1906 1907 Variable { 1908 name: EcoString, 1909 }, 1910} 1911 1912impl InlinablePattern { 1913 fn to_typed_pattern(&self) -> TypedPattern { 1914 match self { 1915 InlinablePattern::Constructor { 1916 name, 1917 module, 1918 arguments, 1919 } => TypedPattern::Constructor { 1920 location: BLANK_LOCATION, 1921 name_location: BLANK_LOCATION, 1922 name: name.clone(), 1923 arguments: arguments 1924 .iter() 1925 .map(|argument| argument.to_call_arg(Self::to_typed_pattern)) 1926 .collect(), 1927 module: None, 1928 constructor: Inferred::Known(PatternConstructor { 1929 name: name.clone(), 1930 field_map: None, 1931 documentation: None, 1932 module: module.clone(), 1933 location: BLANK_LOCATION, 1934 constructor_index: 0, 1935 }), 1936 spread: None, 1937 type_: unknown_type(), 1938 }, 1939 InlinablePattern::Variable { name } => TypedPattern::Variable { 1940 location: BLANK_LOCATION, 1941 name: name.clone(), 1942 type_: unknown_type(), 1943 origin: VariableOrigin::generated(), 1944 }, 1945 } 1946 } 1947} 1948 1949#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1950pub enum InlinableValueConstructor { 1951 LocalVariable, 1952 Function { name: EcoString, module: EcoString }, 1953 Record { name: EcoString, module: EcoString }, 1954} 1955 1956impl InlinableValueConstructor { 1957 fn to_value_constructor(&self, type_: Arc<Type>) -> ValueConstructor { 1958 let variant = match self { 1959 InlinableValueConstructor::LocalVariable => ValueConstructorVariant::LocalVariable { 1960 location: BLANK_LOCATION, 1961 origin: VariableOrigin::generated(), 1962 }, 1963 InlinableValueConstructor::Function { name, module } => { 1964 ValueConstructorVariant::ModuleFn { 1965 name: name.clone(), 1966 field_map: None, 1967 module: module.clone(), 1968 arity: 0, 1969 location: BLANK_LOCATION, 1970 documentation: None, 1971 implementations: Implementations::supporting_all(), 1972 external_erlang: None, 1973 external_javascript: None, 1974 purity: Purity::Unknown, 1975 } 1976 } 1977 InlinableValueConstructor::Record { name, module } => ValueConstructorVariant::Record { 1978 name: name.clone(), 1979 arity: 0, 1980 field_map: None, 1981 location: BLANK_LOCATION, 1982 module: module.clone(), 1983 variants_count: 0, 1984 variant_index: 0, 1985 documentation: None, 1986 }, 1987 }; 1988 ValueConstructor { 1989 publicity: Publicity::Private, 1990 deprecation: Deprecation::NotDeprecated, 1991 variant, 1992 type_, 1993 } 1994 } 1995} 1996 1997#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1998pub struct InlinableArgument<T> { 1999 pub label: Option<EcoString>, 2000 pub value: T, 2001} 2002 2003impl<T> InlinableArgument<T> { 2004 fn to_call_arg<U, F>(&self, convert_value: F) -> CallArg<U> 2005 where 2006 F: FnOnce(&T) -> U, 2007 { 2008 CallArg { 2009 label: self.label.clone(), 2010 location: BLANK_LOCATION, 2011 value: convert_value(&self.value), 2012 implicit: None, 2013 } 2014 } 2015} 2016 2017#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 2018pub struct InlinableParameter { 2019 pub label: Option<EcoString>, 2020 pub name: EcoString, 2021} 2022 2023impl InlinableParameter { 2024 fn to_typed_arg(&self) -> TypedArg { 2025 let is_discard = self.name.starts_with('_'); 2026 2027 let names = match &self.label { 2028 Some(label) if is_discard => ArgNames::LabelledDiscard { 2029 label: label.clone(), 2030 label_location: BLANK_LOCATION, 2031 name: self.name.clone(), 2032 name_location: BLANK_LOCATION, 2033 }, 2034 Some(label) => ArgNames::NamedLabelled { 2035 label: label.clone(), 2036 label_location: BLANK_LOCATION, 2037 name: self.name.clone(), 2038 name_location: BLANK_LOCATION, 2039 }, 2040 None if is_discard => ArgNames::Discard { 2041 name: self.name.clone(), 2042 location: BLANK_LOCATION, 2043 }, 2044 None => ArgNames::Named { 2045 name: self.name.clone(), 2046 location: BLANK_LOCATION, 2047 }, 2048 }; 2049 2050 TypedArg { 2051 names, 2052 location: BLANK_LOCATION, 2053 annotation: None, 2054 type_: unknown_type(), 2055 } 2056 } 2057} 2058 2059/// A simplified version of `Type`, which only cares about prelude types. Code 2060/// generation needs this type information, as some prelude types are handled 2061/// specially in certain cases. Custom type don't matter though, so they all get 2062/// reduced into a single value, which decreases cache size. 2063#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 2064pub enum InlinableType { 2065 BitArray, 2066 Bool, 2067 Float, 2068 Int, 2069 List(Box<InlinableType>), 2070 Nil, 2071 Result { 2072 ok: Box<InlinableType>, 2073 error: Box<InlinableType>, 2074 }, 2075 String, 2076 UtfCodepoint, 2077 2078 Function { 2079 arguments: Vec<InlinableType>, 2080 return_: Box<InlinableType>, 2081 }, 2082 2083 Other, 2084} 2085 2086fn unknown_type() -> Arc<Type> { 2087 type_::generic_var(0) 2088} 2089 2090impl InlinableType { 2091 fn to_type(&self) -> Arc<Type> { 2092 match self { 2093 InlinableType::BitArray => type_::bit_array(), 2094 InlinableType::Bool => type_::bool(), 2095 InlinableType::Float => type_::float(), 2096 InlinableType::Int => type_::int(), 2097 InlinableType::List(element) => type_::list(element.to_type()), 2098 InlinableType::Nil => type_::nil(), 2099 InlinableType::Result { ok, error } => type_::result(ok.to_type(), error.to_type()), 2100 InlinableType::String => type_::string(), 2101 InlinableType::UtfCodepoint => type_::utf_codepoint(), 2102 2103 InlinableType::Function { arguments, return_ } => type_::fn_( 2104 arguments.iter().map(Self::to_type).collect(), 2105 return_.to_type(), 2106 ), 2107 2108 // Code generation doesn't care about custom types at all, only 2109 // prelude types are handled specially, so we treat custom types as 2110 // opaque generic type variables. 2111 InlinableType::Other => unknown_type(), 2112 } 2113 } 2114}