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