diff --git a/ast/src/lexer.rs b/ast/src/lexer.rs index 64a950772..d82c940b8 100644 --- a/ast/src/lexer.rs +++ b/ast/src/lexer.rs @@ -166,6 +166,7 @@ pub enum TokenKind { While, Whitespace, Extern, + Inline, } impl TokenKind { @@ -268,6 +269,7 @@ impl TokenKind { TokenKind::Nil => "the 'nil' keyword", TokenKind::Replace => "a '=:'", TokenKind::Extern => "the 'extern' keyword", + TokenKind::Inline => "the 'inline' keyword", } } } @@ -335,6 +337,7 @@ impl Token { | TokenKind::Case | TokenKind::Enum | TokenKind::Extern + | TokenKind::Inline ) } @@ -997,6 +1000,7 @@ impl Lexer { "return" => TokenKind::Return, "static" => TokenKind::Static, "extern" => TokenKind::Extern, + "inline" => TokenKind::Inline, _ => TokenKind::Identifier, }, 7 => match value.as_str() { @@ -1337,6 +1341,7 @@ mod tests { assert!(tok(TokenKind::While, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Recover, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Nil, "", 1..=1, 1..=1).is_keyword()); + assert!(tok(TokenKind::Inline, "", 1..=1, 1..=1).is_keyword()); } #[test] @@ -1978,6 +1983,7 @@ mod tests { assert_token!("return", Return, "return", 1..=1, 1..=6); assert_token!("static", Static, "static", 1..=1, 1..=6); assert_token!("extern", Extern, "extern", 1..=1, 1..=6); + assert_token!("inline", Inline, "inline", 1..=1, 1..=6); assert_token!("builtin", Builtin, "builtin", 1..=1, 1..=7); assert_token!("recover", Recover, "recover", 1..=1, 1..=7); diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index 0a1f1bd73..6369be943 100644 --- a/ast/src/nodes.rs +++ b/ast/src/nodes.rs @@ -417,6 +417,7 @@ pub enum MethodKind { #[derive(Debug, PartialEq, Eq)] pub struct DefineMethod { + pub inline: bool, pub public: bool, pub kind: MethodKind, pub operator: bool, diff --git a/ast/src/parser.rs b/ast/src/parser.rs index d677ef2a0..3b49786fd 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -826,6 +826,15 @@ impl Parser { Ok(BlockArgument { name, value_type, location }) } + fn optional_inline_keyword(&mut self) -> bool { + if let TokenKind::Inline = self.peek().kind { + self.next(); + true + } else { + false + } + } + fn optional_return_type(&mut self) -> Result, ParseError> { if self.peek().kind != TokenKind::Arrow { return Ok(None); @@ -844,6 +853,7 @@ impl Parser { ) -> Result { let public = self.next_is_public(); let mut allow_variadic = false; + let inline = self.optional_inline_keyword(); let kind = match self.peek().kind { TokenKind::Extern => { self.next(); @@ -883,6 +893,7 @@ impl Parser { ); Ok(TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline, public, operator, name, @@ -900,6 +911,7 @@ impl Parser { start: Token, ) -> Result { let public = self.next_is_public(); + let inline = self.optional_inline_keyword(); let kind = match self.peek().kind { TokenKind::Async => { self.next(); @@ -936,6 +948,7 @@ impl Parser { SourceLocation::start_end(&start.location, &body.location); Ok(DefineMethod { + inline, public, operator, name, @@ -953,6 +966,7 @@ impl Parser { start: Token, ) -> Result { let public = self.next_is_public(); + let inline = self.optional_inline_keyword(); let kind = match self.peek().kind { TokenKind::Move => { self.next(); @@ -975,6 +989,7 @@ impl Parser { SourceLocation::start_end(&start.location, &body.location); Ok(DefineMethod { + inline, public, operator, name, @@ -1458,6 +1473,7 @@ impl Parser { start: Token, ) -> Result { let public = self.next_is_public(); + let inline = self.optional_inline_keyword(); let kind = match self.peek().kind { TokenKind::Move => { self.next(); @@ -1489,6 +1505,7 @@ impl Parser { let location = SourceLocation::start_end(&start.location, end_loc); Ok(DefineMethod { + inline, public, operator, name, @@ -4114,6 +4131,7 @@ mod tests { assert_eq!( top(parse("fn foo {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4132,9 +4150,32 @@ mod tests { })) ); + assert_eq!( + top(parse("fn inline foo {}")), + TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: true, + public: false, + operator: false, + kind: MethodKind::Instance, + name: Identifier { + name: "foo".to_string(), + location: cols(11, 13) + }, + type_parameters: None, + arguments: None, + return_type: None, + body: Some(Expressions { + values: Vec::new(), + location: cols(15, 16) + }), + location: cols(1, 16) + })) + ); + assert_eq!( top(parse("fn FOO {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4156,6 +4197,7 @@ mod tests { assert_eq!( top(parse("fn pub foo {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: true, operator: false, kind: MethodKind::Instance, @@ -4174,9 +4216,32 @@ mod tests { })) ); + assert_eq!( + top(parse("fn pub inline foo {}")), + TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: true, + public: true, + operator: false, + kind: MethodKind::Instance, + name: Identifier { + name: "foo".to_string(), + location: cols(15, 17) + }, + type_parameters: None, + arguments: None, + return_type: None, + body: Some(Expressions { + values: Vec::new(), + location: cols(19, 20) + }), + location: cols(1, 20) + })) + ); + assert_eq!( top(parse("fn 123 {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4198,6 +4263,7 @@ mod tests { assert_eq!( top(parse("fn ab= {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4219,6 +4285,7 @@ mod tests { assert_eq!( top(parse("fn 12= {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4240,6 +4307,7 @@ mod tests { assert_eq!( top(parse("fn let {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4264,6 +4332,7 @@ mod tests { assert_eq!( top(parse("fn foo [T] {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4296,6 +4365,7 @@ mod tests { assert_eq!( top(parse("fn foo [T: A + B] {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4353,6 +4423,7 @@ mod tests { assert_eq!( top(parse("fn foo (a: A, b: B) {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4414,6 +4485,7 @@ mod tests { assert_eq!( top(parse("fn foo -> A {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4446,6 +4518,7 @@ mod tests { assert_eq!( top(parse("fn foo { 10 }")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4473,6 +4546,7 @@ mod tests { assert_eq!( top(parse("fn extern foo")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Extern, @@ -4491,6 +4565,7 @@ mod tests { assert_eq!( top(parse("fn extern foo {}")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Extern, @@ -4512,6 +4587,7 @@ mod tests { assert_eq!( top(parse("fn extern foo(...)")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Extern, @@ -4534,6 +4610,7 @@ mod tests { assert_eq!( top(parse("fn extern foo(...,)")), TopLevelExpression::DefineMethod(Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Extern, @@ -4668,6 +4745,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Async, @@ -4705,6 +4783,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::AsyncMutable, @@ -4844,6 +4923,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -4881,6 +4961,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: true, operator: false, kind: MethodKind::Instance, @@ -4921,6 +5002,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Moving, @@ -4945,6 +5027,47 @@ mod tests { ) } + #[test] + fn test_class_with_inline_method() { + assert_eq!( + top(parse("class A { fn inline foo {} }")), + TopLevelExpression::DefineClass(Box::new(DefineClass { + public: false, + name: Constant { + source: None, + name: "A".to_string(), + location: cols(7, 7) + }, + kind: ClassKind::Regular, + type_parameters: None, + body: ClassExpressions { + values: vec![ClassExpression::DefineMethod(Box::new( + DefineMethod { + inline: true, + public: false, + operator: false, + kind: MethodKind::Instance, + name: Identifier { + name: "foo".to_string(), + location: cols(21, 23) + }, + type_parameters: None, + arguments: None, + return_type: None, + body: Some(Expressions { + values: Vec::new(), + location: cols(25, 26) + }), + location: cols(11, 26) + } + ))], + location: cols(9, 28) + }, + location: cols(1, 28) + })) + ) + } + #[test] fn test_class_with_mutating_method() { assert_eq!( @@ -4961,6 +5084,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Mutable, @@ -5001,6 +5125,7 @@ mod tests { body: ClassExpressions { values: vec![ClassExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Static, @@ -5280,6 +5405,7 @@ mod tests { body: ImplementationExpressions { values: vec![ImplementationExpression::DefineMethod( Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5335,6 +5461,7 @@ mod tests { body: ImplementationExpressions { values: vec![ImplementationExpression::DefineMethod( Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5370,6 +5497,7 @@ mod tests { body: ImplementationExpressions { values: vec![ImplementationExpression::DefineMethod( Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Async, @@ -5470,6 +5598,7 @@ mod tests { body: ImplementationExpressions { values: vec![ImplementationExpression::DefineMethod( Box::new(DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Static, @@ -5495,6 +5624,45 @@ mod tests { ); } + #[test] + fn test_reopen_with_inline_method() { + assert_eq!( + top(parse("impl A { fn inline foo {} }")), + TopLevelExpression::ReopenClass(Box::new(ReopenClass { + class_name: Constant { + source: None, + name: "A".to_string(), + location: cols(6, 6) + }, + body: ImplementationExpressions { + values: vec![ImplementationExpression::DefineMethod( + Box::new(DefineMethod { + inline: true, + public: false, + operator: false, + kind: MethodKind::Instance, + name: Identifier { + name: "foo".to_string(), + location: cols(20, 22) + }, + type_parameters: None, + arguments: None, + return_type: None, + body: Some(Expressions { + values: Vec::new(), + location: cols(24, 25) + }), + location: cols(10, 25) + }) + )], + location: cols(8, 27) + }, + bounds: None, + location: cols(1, 27) + })) + ); + } + #[test] fn test_invalid_implementations() { assert_error!("impl {}", cols(6, 6)); @@ -5691,6 +5859,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5728,6 +5897,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5765,6 +5935,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5812,6 +5983,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5870,6 +6042,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5918,6 +6091,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Instance, @@ -5942,6 +6116,47 @@ mod tests { ); } + #[test] + fn test_trait_with_inline_method() { + assert_eq!( + top(parse("trait A { fn inline foo {} }")), + TopLevelExpression::DefineTrait(Box::new(DefineTrait { + public: false, + name: Constant { + source: None, + name: "A".to_string(), + location: cols(7, 7) + }, + type_parameters: None, + requirements: None, + body: TraitExpressions { + values: vec![TraitExpression::DefineMethod(Box::new( + DefineMethod { + inline: true, + public: false, + operator: false, + kind: MethodKind::Instance, + name: Identifier { + name: "foo".to_string(), + location: cols(21, 23) + }, + type_parameters: None, + arguments: None, + return_type: None, + body: Some(Expressions { + values: Vec::new(), + location: cols(25, 26) + }), + location: cols(11, 26) + } + ))], + location: cols(9, 28) + }, + location: cols(1, 28) + })) + ); + } + #[test] fn test_trait_with_default_moving_method() { assert_eq!( @@ -5958,6 +6173,7 @@ mod tests { body: TraitExpressions { values: vec![TraitExpression::DefineMethod(Box::new( DefineMethod { + inline: false, public: false, operator: false, kind: MethodKind::Moving, diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index 34ec1d97e..8f196121c 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -1,11 +1,12 @@ use crate::config::{BuildDirectories, Output}; -use crate::config::{Config, SOURCE, SOURCE_EXT, TESTS}; +use crate::config::{Config, Opt, SOURCE, SOURCE_EXT, TESTS}; use crate::docs::{ Config as DocsConfig, DefineDocumentation, GenerateDocumentation, }; use crate::hir; use crate::linker::link; use crate::llvm; +use crate::mir::inline::InlineMethod; use crate::mir::passes as mir; use crate::mir::printer::to_dot; use crate::mir::specialize::Specialize; @@ -137,6 +138,8 @@ struct Timings { hir: Duration, type_check: Duration, mir: Duration, + specialize_mir: Duration, + inline_mir: Duration, optimize_mir: Duration, llvm: Duration, llvm_modules: Vec<(ModuleName, Duration)>, @@ -151,6 +154,8 @@ impl Timings { hir: Duration::from_secs(0), type_check: Duration::from_secs(0), mir: Duration::from_secs(0), + specialize_mir: Duration::from_secs(0), + inline_mir: Duration::from_secs(0), optimize_mir: Duration::from_secs(0), llvm: Duration::from_secs(0), llvm_modules: Vec::new(), @@ -283,22 +288,30 @@ impl Compiler { // to still get the diagnostics without these timings messing things up. println!( "\ -Compilation stages: - -\x1b[1mStage\x1b[0m \x1b[1mTime\x1b[0m -Source to AST {ast} -AST to HIR {hir} -Type check {type_check} -HIR to MIR {mir} -Optimize MIR {optimize} -Generate LLVM {llvm} -Link {link} -Total {total}\ +Frontend: + Parse {ast} + AST to HIR {hir} + Type check {type_check} + HIR to MIR {mir} + +Optimizations: + Specialize {specialize} + Inline {inline} + Total {optimize} + +Backend: + LLVM {llvm} + Linker {link} + +Total: {total}\ ", ast = format_timing(self.timings.ast, Some(total)), hir = format_timing(self.timings.hir, Some(total)), type_check = format_timing(self.timings.type_check, Some(total)), mir = format_timing(self.timings.mir, Some(total)), + specialize = + format_timing(self.timings.specialize_mir, Some(total)), + inline = format_timing(self.timings.inline_mir, Some(total)), optimize = format_timing(self.timings.optimize_mir, Some(total)), llvm = format_timing(self.timings.llvm, Some(total)), link = format_timing(self.timings.link, Some(total)), @@ -471,8 +484,31 @@ LLVM module timings: fn optimise_mir(&mut self, mir: &mut Mir) { let start = Instant::now(); - Specialize::run_all(&mut self.state, mir); + { + let start = Instant::now(); + + Specialize::run_all(&mut self.state, mir); + self.timings.specialize_mir = start.elapsed(); + } + + // This pass runs before the others such that they can assume each block + // has an explicit terminator instruction. mir::clean_up_basic_blocks(mir); + + // The following passes are optional and thus only enabled if + // optimizations are enabled. + if let Opt::None = self.state.config.opt { + self.timings.optimize_mir = start.elapsed(); + return; + } + + { + let start = Instant::now(); + + InlineMethod::run_all(&mut self.state, mir); + self.timings.inline_mir = start.elapsed(); + } + self.timings.optimize_mir = start.elapsed(); } @@ -484,8 +520,11 @@ LLVM module timings: directories.create_dot().map_err(CompileError::Internal)?; for module in mir.modules.values() { - let methods: Vec<_> = - module.methods.iter().map(|m| &mir.methods[m]).collect(); + let methods: Vec<_> = module + .methods + .iter() + .map(|m| mir.methods.get(m).unwrap()) + .collect(); let output = to_dot(&self.state.db, mir, &methods); let name = module.id.name(&self.state.db).normalized_name(); diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index 482abcb6a..663e55ffb 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -969,6 +969,19 @@ impl Diagnostics { ); } + pub(crate) fn invalid_inline_method( + &mut self, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidMethod, + "the 'inline' keyword can't be used for this type of method", + file, + location, + ); + } + pub(crate) fn iter(&self) -> impl Iterator { self.values.iter() } diff --git a/compiler/src/format.rs b/compiler/src/format.rs index fb4b6953c..44e16614d 100644 --- a/compiler/src/format.rs +++ b/compiler/src/format.rs @@ -1069,8 +1069,13 @@ impl Document { nodes::MethodKind::Extern => " extern ", }; let kw = if node.public { "fn pub" } else { "fn" }; - let mut header = - vec![Node::text(kw), Node::text(kind), Node::text(&node.name.name)]; + let inline = if node.inline { " inline" } else { "" }; + let mut header = vec![ + Node::text(kw), + Node::text(inline), + Node::text(kind), + Node::text(&node.name.name), + ]; if let Some(nodes) = node.type_parameters.as_ref().filter(|v| !v.values.is_empty()) diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index a186f6ce3..1cb21cce7 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -285,6 +285,7 @@ impl MethodKind { pub(crate) struct DefineInstanceMethod { pub(crate) documentation: String, pub(crate) public: bool, + pub(crate) inline: bool, pub(crate) kind: MethodKind, pub(crate) name: Identifier, pub(crate) type_parameters: Vec, @@ -299,6 +300,7 @@ pub(crate) struct DefineInstanceMethod { pub(crate) struct DefineModuleMethod { pub(crate) documentation: String, pub(crate) public: bool, + pub(crate) inline: bool, pub(crate) c_calling_convention: bool, pub(crate) name: Identifier, pub(crate) type_parameters: Vec, @@ -338,6 +340,7 @@ pub(crate) struct DefineRequiredMethod { pub(crate) struct DefineStaticMethod { pub(crate) documentation: String, pub(crate) public: bool, + pub(crate) inline: bool, pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, @@ -1278,6 +1281,7 @@ impl<'a> LowerToHir<'a> { })) } else { TopLevelExpression::ModuleMethod(Box::new(DefineModuleMethod { + inline: node.inline, documentation, public: node.public, c_calling_convention: external, @@ -1464,6 +1468,7 @@ impl<'a> LowerToHir<'a> { self.operator_method_not_allowed(node.operator, &node.location); Box::new(DefineStaticMethod { + inline: node.inline, documentation, public: node.public, name: self.identifier(node.name), @@ -1483,6 +1488,7 @@ impl<'a> LowerToHir<'a> { documentation: String, ) -> Box { self.operator_method_not_allowed(node.operator, &node.location); + self.disallow_inline_method(&node); Box::new(DefineAsyncMethod { documentation, @@ -1505,6 +1511,7 @@ impl<'a> LowerToHir<'a> { documentation: String, ) -> DefineInstanceMethod { DefineInstanceMethod { + inline: node.inline, documentation, public: node.public, kind: match node.kind { @@ -1528,6 +1535,8 @@ impl<'a> LowerToHir<'a> { node: ast::DefineMethod, documentation: String, ) -> Box { + self.disallow_inline_method(&node); + Box::new(DefineRequiredMethod { documentation, public: node.public, @@ -3206,6 +3215,14 @@ impl<'a> LowerToHir<'a> { location.clone(), ); } + + fn disallow_inline_method(&mut self, node: &ast::DefineMethod) { + if node.inline { + self.state + .diagnostics + .invalid_inline_method(self.file(), node.location.clone()); + } + } } #[cfg(test)] @@ -3663,6 +3680,7 @@ mod tests { assert_eq!( hir, TopLevelExpression::ModuleMethod(Box::new(DefineModuleMethod { + inline: false, documentation: String::new(), public: false, c_calling_convention: false, @@ -3727,6 +3745,32 @@ mod tests { ); } + #[test] + fn test_lower_inline_module_method() { + let (hir, diags) = lower_top_expr("fn inline foo {}"); + + assert_eq!(diags, 0); + assert_eq!( + hir, + TopLevelExpression::ModuleMethod(Box::new(DefineModuleMethod { + inline: true, + documentation: String::new(), + public: false, + c_calling_convention: false, + name: Identifier { + name: "foo".to_string(), + location: cols(11, 13) + }, + type_parameters: Vec::new(), + arguments: Vec::new(), + return_type: None, + body: Vec::new(), + method_id: None, + location: cols(1, 16), + })), + ); + } + #[test] fn test_lower_extern_function() { let (hir, diags) = lower_top_expr("fn extern foo"); @@ -3760,6 +3804,7 @@ mod tests { assert_eq!( hir, TopLevelExpression::ModuleMethod(Box::new(DefineModuleMethod { + inline: false, documentation: String::new(), public: false, c_calling_convention: true, @@ -4083,6 +4128,7 @@ mod tests { type_parameters: Vec::new(), body: vec![ClassExpression::StaticMethod(Box::new( DefineStaticMethod { + inline: false, documentation: String::new(), public: false, name: Identifier { @@ -4229,6 +4275,7 @@ mod tests { type_parameters: Vec::new(), body: vec![ClassExpression::InstanceMethod(Box::new( DefineInstanceMethod { + inline: false, documentation: String::new(), public: false, kind: MethodKind::Regular, @@ -4287,6 +4334,42 @@ mod tests { ); } + #[test] + fn test_lower_class_with_inline_method() { + let hir = lower_top_expr("class A { fn inline foo {} }").0; + + assert_eq!( + hir, + TopLevelExpression::Class(Box::new(DefineClass { + documentation: String::new(), + public: false, + class_id: None, + kind: ClassKind::Regular, + name: Constant { name: "A".to_string(), location: cols(7, 7) }, + type_parameters: Vec::new(), + body: vec![ClassExpression::InstanceMethod(Box::new( + DefineInstanceMethod { + inline: true, + documentation: String::new(), + public: false, + kind: MethodKind::Regular, + name: Identifier { + name: "foo".to_string(), + location: cols(21, 23) + }, + type_parameters: Vec::new(), + arguments: Vec::new(), + return_type: None, + body: Vec::new(), + method_id: None, + location: cols(11, 26) + } + ))], + location: cols(1, 28) + })), + ); + } + #[test] fn test_lower_static_operator_method() { let diags = lower_top_expr("class A { fn static + {} }").1; @@ -4477,6 +4560,7 @@ mod tests { type_parameters: Vec::new(), body: vec![TraitExpression::InstanceMethod(Box::new( DefineInstanceMethod { + inline: false, documentation: String::new(), public: false, kind: MethodKind::Moving, @@ -4512,6 +4596,7 @@ mod tests { type_parameters: Vec::new(), body: vec![TraitExpression::InstanceMethod(Box::new( DefineInstanceMethod { + inline: false, documentation: String::new(), public: false, kind: MethodKind::Regular, @@ -4570,6 +4655,42 @@ mod tests { ); } + #[test] + fn test_lower_trait_with_inline_method() { + let hir = lower_top_expr("trait A { fn inline foo {} }").0; + + assert_eq!( + hir, + TopLevelExpression::Trait(Box::new(DefineTrait { + documentation: String::new(), + public: false, + trait_id: None, + name: Constant { name: "A".to_string(), location: cols(7, 7) }, + requirements: Vec::new(), + type_parameters: Vec::new(), + body: vec![TraitExpression::InstanceMethod(Box::new( + DefineInstanceMethod { + inline: true, + documentation: String::new(), + public: false, + kind: MethodKind::Regular, + name: Identifier { + name: "foo".to_string(), + location: cols(21, 23) + }, + type_parameters: Vec::new(), + arguments: Vec::new(), + return_type: None, + body: Vec::new(), + method_id: None, + location: cols(11, 26) + } + ))], + location: cols(1, 28) + })) + ); + } + #[test] fn test_lower_reopen_empty_class() { assert_eq!( @@ -4623,6 +4744,7 @@ mod tests { }, body: vec![ReopenClassExpression::InstanceMethod(Box::new( DefineInstanceMethod { + inline: false, documentation: String::new(), public: false, kind: MethodKind::Regular, @@ -4658,6 +4780,7 @@ mod tests { }, body: vec![ReopenClassExpression::StaticMethod(Box::new( DefineStaticMethod { + inline: false, documentation: String::new(), public: false, name: Identifier { @@ -4887,6 +5010,7 @@ mod tests { }, bounds: Vec::new(), body: vec![DefineInstanceMethod { + inline: false, documentation: String::new(), public: false, kind: MethodKind::Regular, @@ -4931,6 +5055,7 @@ mod tests { }, bounds: Vec::new(), body: vec![DefineInstanceMethod { + inline: false, documentation: String::new(), public: false, kind: MethodKind::Moving, diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index 99906f59e..9b2b12e08 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -187,6 +187,20 @@ fn check_object_cache( hasher.update(name.as_bytes()); } + // We include the list of inlined methods in the hash such that if this + // changes, we flush the cache. + let mut inlined: Vec<_> = module + .inlined_methods + .iter() + .map(|id| &symbol_names.methods[id]) + .collect(); + + inlined.sort(); + + for name in inlined { + hasher.update(name.as_bytes()); + } + // The module may contain dynamic dispatch call sites. If the need for // probing changes, we need to update the module's code accordingly. We // do this by hashing the collision states of all dynamic calls in the @@ -194,7 +208,7 @@ fn check_object_cache( for &mid in &module.methods { hasher.update(&methods.info[mid.0 as usize].hash.to_le_bytes()); - for block in &mir.methods[&mid].body.blocks { + for block in &mir.methods.get(&mid).unwrap().body.blocks { for ins in &block.instructions { if let Instruction::CallDynamic(op) = ins { let val = methods.info[op.method.0 as usize].collision; @@ -708,7 +722,7 @@ impl<'shared, 'module, 'ctx> LowerModule<'shared, 'module, 'ctx> { self.shared, self.layouts, self.module, - &self.shared.mir.methods[method], + self.shared.mir.methods.get(method).unwrap(), ) .run(); } diff --git a/compiler/src/mir/inline.rs b/compiler/src/mir/inline.rs new file mode 100644 index 000000000..fab880694 --- /dev/null +++ b/compiler/src/mir/inline.rs @@ -0,0 +1,590 @@ +use crate::mir::{ + BlockId, Goto, Graph, Instruction, LocationId, Method, Mir, MoveRegister, + RegisterId, Registers, +}; +use crate::state::State; +use std::collections::{HashMap, HashSet}; +use std::mem::swap; +use types::{Database, Inline, MethodId}; + +/// The maximum number of call sites for a large method before we stop inlining +/// it anyway. +/// +/// The purpose of this setting is to ensure large but rarely used methods (e.g. +/// a private helper method) are still inlined. +const INLINE_ANYWAY_CALLS: usize = 2; + +/// The maximum number of inline passes to perform over all methods. +const MAX_PASSES: usize = 10; + +/// The maximum weight of a method we consider for inlining. +const MAX_WEIGHT: usize = 500; + +fn instruction_weight(db: &Database, instruction: &Instruction) -> usize { + // The weights are mostly arbitrary and are meant to be a rough resemblance + // of complexity and thus code size. A weight of 0 means the instruction + // typically only translates to a single or a few very simple machine + // instructions. + match instruction { + Instruction::Allocate(ins) if ins.class.kind(db).is_extern() => 0, + Instruction::Allocate(_) => 1, + Instruction::Spawn(_) => 1, + Instruction::CallInstance(_) => 1, + Instruction::CallStatic(_) => 1, + Instruction::CallClosure(_) => 1, + Instruction::CheckRefs(_) => 1, + Instruction::DecrementAtomic(_) => 1, + Instruction::Branch(_) => 2, + Instruction::Send(_) => 2, + Instruction::CallDynamic(_) => 2, + Instruction::Switch(ins) => ins.blocks.len(), + _ => 0, + } +} + +fn method_weight(db: &Database, method: &Method) -> usize { + let mut weight = 0; + + for block in &method.body.blocks { + weight += block + .instructions + .iter() + .map(|v| instruction_weight(db, v)) + .sum::(); + } + + weight +} + +struct CallSite { + /// The register to store the return value in. + target: RegisterId, + + /// The basic block in which the call instruction resides. + block: BlockId, + + /// The index of the call instruction. + instruction: usize, + + /// The registers defined by the callee. + registers: Registers, + + /// The unprocessed/raw body of the callee. + body: Graph, + + /// The registers containing the arguments of the caller. + /// + /// For calls to instance methods, the receiver is passed as the first + /// argument, such that the number of caller and callee arguments matches. + caller_arguments: Vec, + + /// The registers used for arguments by the callee. + callee_arguments: Vec, + + /// The source location of the call. + location: LocationId, +} + +impl CallSite { + fn new( + target: RegisterId, + block: BlockId, + instruction: usize, + receiver: Option, + arguments: &[RegisterId], + caller: &Method, + location: LocationId, + ) -> CallSite { + let mut caller_args = + if let Some(rec) = receiver { vec![rec] } else { Vec::new() }; + + caller_args.extend(arguments); + CallSite { + target, + block, + instruction, + registers: caller.registers.clone(), + body: caller.body.clone(), + caller_arguments: caller_args, + callee_arguments: caller.arguments.clone(), + location, + } + } +} + +impl CallSite { + fn inline_into(mut self, caller: &mut Method, after_call: BlockId) { + let loc = self.location; + let reg_start = caller.registers.len(); + let blk_start = caller.body.blocks.len(); + + for reg in &mut self.callee_arguments { + *reg += reg_start; + } + + caller.registers.append(&mut self.registers); + caller.body.blocks.append(&mut self.body.blocks); + + // Now that the registers and blocks have been added to the caller, we + // need to update the references accordingly. Since both are stored as a + // Vec, we just need to "shift" the IDs to the right. + for blk_idx in blk_start..caller.body.blocks.len() { + let block = &mut caller.body.blocks[blk_idx]; + + for id in + block.predecessors.iter_mut().chain(block.successors.iter_mut()) + { + *id += blk_start; + } + + let mut add_goto = None; + + for ins in &mut block.instructions { + match ins { + Instruction::Branch(ins) => { + ins.condition += reg_start; + ins.if_true += blk_start; + ins.if_false += blk_start; + } + Instruction::Switch(ins) => { + ins.register += reg_start; + ins.blocks.iter_mut().for_each(|b| *b += blk_start); + } + Instruction::True(ins) => { + ins.register += reg_start; + } + Instruction::False(ins) => { + ins.register += reg_start; + } + Instruction::Float(ins) => { + ins.register += reg_start; + } + Instruction::Goto(ins) => { + ins.block += blk_start; + } + Instruction::Int(ins) => { + ins.register += reg_start; + } + Instruction::MoveRegister(ins) => { + ins.source += reg_start; + ins.target += reg_start; + } + Instruction::Nil(ins) => { + ins.register += reg_start; + } + Instruction::Return(ret) => { + let reg = ret.register + reg_start; + let loc = ret.location; + + *ins = + Instruction::MoveRegister(Box::new(MoveRegister { + source: reg, + target: self.target, + location: loc, + })); + + // Return is a terminal instruction and is thus the last + // instruction in the block. This means this option + // should never be set multiple times. + debug_assert!(add_goto.is_none()); + add_goto = Some(loc); + } + Instruction::String(ins) => { + ins.register += reg_start; + } + Instruction::CallStatic(ins) => { + ins.register += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::CallInstance(ins) => { + ins.register += reg_start; + ins.receiver += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::CallExtern(ins) => { + ins.register += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::CallDynamic(ins) => { + ins.register += reg_start; + ins.receiver += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::CallClosure(ins) => { + ins.register += reg_start; + ins.receiver += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::CallDropper(ins) => { + ins.register += reg_start; + ins.receiver += reg_start; + } + Instruction::CallBuiltin(ins) => { + ins.register += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::Send(ins) => { + ins.receiver += reg_start; + ins.arguments.iter_mut().for_each(|r| *r += reg_start); + } + Instruction::GetField(ins) => { + ins.register += reg_start; + ins.receiver += reg_start; + } + Instruction::SetField(ins) => { + ins.receiver += reg_start; + ins.value += reg_start; + } + Instruction::CheckRefs(ins) => { + ins.register += reg_start; + } + Instruction::Drop(ins) => { + ins.register += reg_start; + } + Instruction::Free(ins) => { + ins.register += reg_start; + } + Instruction::Reference(ins) => { + ins.register += reg_start; + ins.value += reg_start; + } + Instruction::Increment(ins) => { + ins.register += reg_start; + } + Instruction::Decrement(ins) => { + ins.register += reg_start; + } + Instruction::IncrementAtomic(ins) => { + ins.register += reg_start; + } + Instruction::DecrementAtomic(ins) => { + ins.register += reg_start; + ins.if_true += blk_start; + ins.if_false += blk_start; + } + Instruction::Allocate(ins) => { + ins.register += reg_start; + } + Instruction::Spawn(ins) => { + ins.register += reg_start; + } + Instruction::GetConstant(ins) => { + ins.register += reg_start; + } + Instruction::Cast(ins) => { + ins.register += reg_start; + ins.source += reg_start; + } + Instruction::Pointer(ins) => { + ins.register += reg_start; + ins.value += reg_start; + } + Instruction::ReadPointer(ins) => { + ins.register += reg_start; + ins.pointer += reg_start; + } + Instruction::WritePointer(ins) => { + ins.pointer += reg_start; + ins.value += reg_start; + } + Instruction::FieldPointer(ins) => { + ins.register += reg_start; + ins.receiver += reg_start; + } + Instruction::MethodPointer(ins) => { + ins.register += reg_start; + } + Instruction::SizeOf(ins) => { + ins.register += reg_start; + } + Instruction::Preempt(_) => {} + Instruction::Finish(_) => {} + } + } + + // If the block ended in a return instruction, we replace it with a + // goto that jumps to the block that occurs _after_ the inlined + // code. + if let Some(location) = add_goto { + block.instructions.push(Instruction::Goto(Box::new(Goto { + block: after_call, + location, + }))); + caller.body.add_edge(BlockId(blk_idx), after_call); + } + } + + // At this point the call instruction is guaranteed to be the last + // instruction in the basic block, so we can just pop it from the block. + caller.body.block_mut(self.block).instructions.pop(); + + for (&from, &to) in + self.caller_arguments.iter().zip(&self.callee_arguments) + { + caller.body.block_mut(self.block).move_register(to, from, loc); + } + + let inline_start = self.body.start_id + blk_start; + + // Fix up the successor and predecossor blocks of the call block and + // its successor blocks, ensuring the CFG remains correct. + let mut succ = Vec::new(); + + swap(&mut caller.body.block_mut(self.block).successors, &mut succ); + + for id in succ { + caller.body.remove_predecessor(id, self.block); + caller.body.add_edge(after_call, id); + } + + caller.body.block_mut(self.block).goto(inline_start, self.location); + caller.body.add_edge(self.block, inline_start); + } +} + +#[derive(Default, Clone)] +struct Stat { + /// The inlining weight of the method. + weight: usize, + + /// The number of call sites of the method. + calls: usize, +} + +/// A compiler pass that inlines static method calls into their call sites. +pub(crate) struct InlineMethod<'a, 'b, 'c> { + state: &'a mut State, + mir: &'b mut Mir, + stats: &'c mut HashMap, + method: usize, +} + +impl<'a, 'b, 'c> InlineMethod<'a, 'b, 'c> { + pub(crate) fn run_all(state: &'a mut State, mir: &'a mut Mir) { + let mut inlined = false; + let mut stats: HashMap = HashMap::new(); + + for m in mir.methods.values() { + stats.entry(m.id).or_default().weight = method_weight(&state.db, m); + + for block in &m.body.blocks { + for ins in &block.instructions { + let id = match ins { + Instruction::CallInstance(i) => i.method, + Instruction::CallStatic(i) => i.method, + _ => continue, + }; + + stats.entry(id).or_default().calls += 1; + } + } + } + + let before_stats = stats.clone(); + let mut iters = 0; + + // Inlining a method into its call site may result in more code needing + // inlining, so we repeat this process. However, we need to ensure the + // work terminates when dealing with deep call chains or recursive + // calls, so we enforce an upper bound on the number of iterations. + while iters < MAX_PASSES { + for method in 0..mir.methods.len() { + inlined |= + InlineMethod { state, mir, method, stats: &mut stats } + .run(); + } + + if inlined { + iters += 1; + inlined = false; + } else { + break; + } + } + + let before = before_stats.values().map(|v| v.weight).sum::(); + let after = stats.values().map(|v| v.weight).sum::(); + + // TODO: remove + for method in mir.methods.values() { + if method.id.name(&state.db) == "parse" + && method.id.source_module(&state.db).name(&state.db).as_str() + == "std.int" + { + println!("Int.parse weight = {}", stats[&method.id].weight); + println!("Int.parse calls = {}", stats[&method.id].calls); + } + } + + println!("rounds: {}", iters); + println!("before: {}", before); + println!("after: {}", after); + println!("increase: {:.2}x", after as f64 / before as f64); + } + + fn run(mut self) -> bool { + let mut work = self.inline_call_sites(); + let inlined = !work.is_empty(); + let method = &mut self.mir.methods[self.method]; + + // We process the work list in reverse order so that modifying the + // basic blocks doesn't invalidate instruction indexes, ensuring we only + // need a single pass to determine which instructions need inlining. + while let Some(call) = work.pop() { + let after = method.body.add_block(); + + // Blocks are guaranteed to have a terminator instruction at this + // point, meaning a call to inline is never the last instruction in + // the block. + method.body.block_mut(after).instructions = method + .body + .block_mut(call.block) + .instructions + .split_off(call.instruction + 1); + + call.inline_into(method, after); + } + + inlined + } + + fn inline_call_sites(&mut self) -> Vec { + let caller = &self.mir.methods[self.method]; + let mut sites = Vec::new(); + let caller_mod_id = caller.id.source_module(&self.state.db); + let caller_mod_node = self + .state + .dependency_graph + .add_module(caller_mod_id.name(&self.state.db).clone()); + let mut inlined = HashSet::new(); + let mut caller_weight = self.stats[&caller.id].weight; + + for (blk_idx, block) in caller.body.blocks.iter().enumerate() { + for (ins_idx, ins) in block.instructions.iter().enumerate() { + let (callee, callee_weight) = match ins { + // TODO: handle other cases + Instruction::CallStatic(ins) => { + let callee = self.mir.methods.get(&ins.method).unwrap(); + let weight = self.stats[&ins.method].weight; + + if self.should_inline(caller_weight, callee, weight) { + sites.push(CallSite::new( + ins.register, + BlockId(blk_idx), + ins_idx, + None, + &ins.arguments, + callee, + ins.location, + )); + (ins.method, weight) + } else { + continue; + } + } + Instruction::CallInstance(ins) => { + let callee = self.mir.methods.get(&ins.method).unwrap(); + let weight = self.stats[&ins.method].weight; + + if self.should_inline(caller_weight, callee, weight) { + sites.push(CallSite::new( + ins.register, + BlockId(blk_idx), + ins_idx, + Some(ins.receiver), + &ins.arguments, + callee, + ins.location, + )); + (ins.method, weight) + } else { + continue; + } + } + _ => continue, + }; + + caller_weight += callee_weight; + + // The calling module might not directly depend on the module + // that defines the method. To ensure incremental caches are + // flushed when needed, we record the dependency of the caller's + // module on the callee's module. + let callee_mod_id = callee.source_module(&self.state.db); + let callee_mod_node = self + .state + .dependency_graph + .add_module(callee_mod_id.name(&self.state.db).clone()); + + self.state + .dependency_graph + .add_depending(caller_mod_node, callee_mod_node); + + // Even if the dependencies remain the same, it's possible that + // the state of inlining changes. For example, two methods + // called may originate from the same module, but the decision + // to inline one of them may change between compilations. As + // such we need to not only record the module dependencies, but + // also the list of inlined methods. + if caller_mod_id != callee_mod_id { + inlined.insert(callee); + } + } + } + + self.stats.get_mut(&caller.id).unwrap().weight = caller_weight; + self.mir + .modules + .get_mut(&caller_mod_id) + .unwrap() + .inlined_methods + .extend(inlined); + + sites + } + + // TODO: take the number of call sites into account. + // TODO: always inline a method with only a single call site + fn should_inline( + &self, + caller_weight: usize, + callee: &Method, + callee_weight: usize, + ) -> bool { + match callee.id.inline(&self.state.db) { + Inline::Always => true, + Inline::Infer => { + // TODO: the call count is initially already wrong, but this + // also doesn't take into account specializations resulting in + // more distinct calls. + if callee_weight >= MAX_WEIGHT { + if self.stats[&callee.id].calls <= INLINE_ANYWAY_CALLS { + println!( + "{}.{} (ID {}) too large, weight = {}, calls = {}, caller = {}.{}, shapes = {:?}", + types::format::format_type( + &self.state.db, + callee.id.receiver_id(&self.state.db) + ), + callee.id.name(&self.state.db), + callee.id.0, + callee_weight, + self.stats[&callee.id].calls, + types::format::format_type( + &self.state.db, + self.mir.methods[self.method].id.receiver_id(&self.state.db), + ), + self.mir.methods[self.method].id.name(&self.state.db), + callee.id.shapes(&self.state.db), + ); + true + } else { + false + } + } else { + caller_weight + callee_weight <= MAX_WEIGHT + } + } + Inline::Never => false, + } + } +} diff --git a/compiler/src/mir/mod.rs b/compiler/src/mir/mod.rs index a77f8bc44..a7de0e875 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -2,6 +2,7 @@ //! //! MIR is used for various optimisations, analysing moves of values, compiling //! pattern matching into decision trees, and more. +pub(crate) mod inline; pub(crate) mod passes; pub(crate) mod pattern_matching; pub(crate) mod printer; @@ -11,6 +12,7 @@ use ast::source_location::SourceLocation; use std::collections::{HashMap, HashSet}; use std::fmt; use std::hash::{Hash, Hasher}; +use std::ops::{Add, AddAssign}; use types::collections::IndexMap; use types::{ Database, ForeignType, Intrinsic, MethodId, Shape, Sign, TypeArguments, @@ -18,7 +20,7 @@ use types::{ }; /// The register ID of the register that stores `self`. -pub(crate) const SELF_ID: u32 = 0; +pub(crate) const SELF_ID: usize = 0; fn method_name(db: &Database, id: MethodId) -> String { format!("{}#{}", id.name(db), id.0,) @@ -46,11 +48,11 @@ impl Registers { } pub(crate) fn get(&self, register: RegisterId) -> &Register { - &self.values[register.0 as usize] + &self.values[register.0] } pub(crate) fn get_mut(&mut self, register: RegisterId) -> &mut Register { - &mut self.values[register.0 as usize] + &mut self.values[register.0] } pub(crate) fn value_type(&self, register: RegisterId) -> types::TypeRef { @@ -64,6 +66,10 @@ impl Registers { pub(crate) fn iter_mut(&mut self) -> impl Iterator { self.values.iter_mut() } + + pub(crate) fn append(&mut self, other: &mut Registers) { + self.values.append(&mut other.values); + } } /// A directed control-flow graph. @@ -141,6 +147,20 @@ impl Graph { #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] pub(crate) struct BlockId(pub(crate) usize); +impl Add for BlockId { + type Output = BlockId; + + fn add(self, rhs: usize) -> Self::Output { + BlockId(self.0 + rhs) + } +} + +impl AddAssign for BlockId { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + /// A basic block in a control flow graph. #[derive(Clone)] pub(crate) struct Block { @@ -755,7 +775,21 @@ pub(crate) struct Register { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) struct RegisterId(pub(crate) u32); +pub(crate) struct RegisterId(pub(crate) usize); + +impl Add for RegisterId { + type Output = RegisterId; + + fn add(self, rhs: usize) -> Self::Output { + RegisterId(self.0 + rhs) + } +} + +impl AddAssign for RegisterId { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} #[derive(Clone)] pub(crate) struct Branch { @@ -1443,6 +1477,11 @@ pub(crate) struct Module { pub(crate) classes: Vec, pub(crate) constants: Vec, pub(crate) methods: Vec, + + /// The methods inlined into this module. + /// + /// This is used to flush incremental compilation caches when necessary. + pub(crate) inlined_methods: HashSet, } impl Module { @@ -1452,6 +1491,7 @@ impl Module { classes: Vec::new(), constants: Vec::new(), methods: Vec::new(), + inlined_methods: HashSet::new(), } } } @@ -1485,7 +1525,7 @@ pub(crate) struct Mir { pub(crate) constants: HashMap, pub(crate) modules: IndexMap, pub(crate) classes: HashMap, - pub(crate) methods: HashMap, + pub(crate) methods: IndexMap, /// Externally defined methods/functions that are called at some point. /// @@ -1519,7 +1559,7 @@ impl Mir { constants: HashMap::new(), modules: IndexMap::new(), classes: HashMap::new(), - methods: HashMap::new(), + methods: IndexMap::new(), extern_methods: HashSet::new(), type_arguments: Vec::new(), dynamic_calls: HashMap::new(), diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 1b21309bc..bff183a88 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -4365,7 +4365,7 @@ impl<'a> LowerMethod<'a> { } fn register_kind(&self, register: RegisterId) -> RegisterKind { - self.register_kinds[register.0 as usize] + self.register_kinds[register.0] } fn register_is_available(&mut self, register: RegisterId) -> bool { diff --git a/compiler/src/mir/specialize.rs b/compiler/src/mir/specialize.rs index 99d8644ca..8d5b7573c 100644 --- a/compiler/src/mir/specialize.rs +++ b/compiler/src/mir/specialize.rs @@ -5,6 +5,7 @@ use crate::mir::{ use crate::state::State; use std::collections::{HashMap, HashSet, VecDeque}; use std::mem::swap; +use types::collections::IndexMap; use types::specialize::{ordered_shapes_from_map, TypeSpecializer}; use types::{ Block as _, ClassId, ClassInstance, Database, MethodId, Shape, @@ -270,7 +271,15 @@ impl<'a, 'b> Specialize<'a, 'b> { // them as-is could result in incorrect code being generated. As such, // we end up removing all methods we haven't processed (i.e they're // unused). - mir.methods.retain(|id, _| work.done.contains(id)); + let mut old = IndexMap::new(); + + swap(&mut mir.methods, &mut old); + + for method in old.into_values() { + if work.done.contains(&method.id) { + mir.methods.insert(method.id, method); + } + } // The specialization source is also set for regular classes that we // encounter. Thus, this filters out any classes that we don't encounter @@ -597,7 +606,7 @@ impl<'a, 'b> Specialize<'a, 'b> { } for &(old, new) in &self.specialized_methods { - let mut method = mir.methods[&old].clone(); + let mut method = mir.methods.get(&old).unwrap().clone(); method.id = new; mir.methods.insert(new, method); diff --git a/compiler/src/type_check/methods.rs b/compiler/src/type_check/methods.rs index a436d1ca6..6dd804ec0 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -306,6 +306,10 @@ impl<'a> DefineModuleMethodNames<'a> { MethodKind::Static, ); + if node.inline { + method.always_inline(self.db_mut()); + } + if node.c_calling_convention { method.use_c_calling_convention(self.db_mut()); } @@ -635,6 +639,10 @@ impl<'a> DefineMethods<'a> { MethodKind::Static, ); + if node.inline { + method.always_inline(self.db_mut()); + } + method.set_receiver(self.db_mut(), receiver); let scope = TypeScope::new(self.module, self_type, Some(method)); @@ -712,6 +720,10 @@ impl<'a> DefineMethods<'a> { kind, ); + if node.inline { + method.always_inline(self.db_mut()); + } + if !method.is_mutable(self.db()) { bounds.make_immutable(self.db_mut()); } @@ -961,6 +973,10 @@ impl<'a> DefineMethods<'a> { kind, ); + if node.inline { + method.always_inline(self.db_mut()); + } + self.define_type_parameters(&mut node.type_parameters, method, self_id); let rules = Rules { @@ -1034,6 +1050,10 @@ impl<'a> DefineMethods<'a> { MethodKind::Constructor, ); + // Constructor methods just set a bunch of fields so we can and should + // always inline them. + method.always_inline(self.db_mut()); + let constructor = class_id.constructor(self.db(), &node.name.name).unwrap(); @@ -1334,6 +1354,10 @@ impl<'a> ImplementTraitMethods<'a> { method_kind(node.kind), ); + if node.inline { + method.always_inline(self.db_mut()); + } + if !method.is_mutable(self.db()) { bounds.make_immutable(self.db_mut()); } diff --git a/std/fixtures/diagnostics/inline_methods.inko b/std/fixtures/diagnostics/inline_methods.inko new file mode 100644 index 000000000..32de37c9f --- /dev/null +++ b/std/fixtures/diagnostics/inline_methods.inko @@ -0,0 +1,55 @@ +fn inline module_method1 {} +fn pub inline module_method2 {} + +class A { + fn inline instance_method1 {} + fn inline mut instance_method2 {} + fn inline move instance_method3 {} + + fn pub inline instance_method4 {} + fn pub inline mut instance_method5 {} + fn pub inline move instance_method6 {} +} + +impl A { + fn inline instance_method7 {} + fn inline mut instance_method8 {} + fn inline move instance_method9 {} + + fn pub inline instance_method10 {} + fn pub inline mut instance_method11 {} + fn pub inline move instance_method12 {} +} + +trait B { + fn inline invalid1 + fn inline mut invalid2 + fn inline move invalid3 + + fn inline valid1 {} + fn inline mut valid2 {} + fn inline move valid3 {} + + fn pub inline valid4 {} + fn pub inline mut valid5 {} + fn pub inline move valid6 {} +} + +class async C { + fn inline async invalid1 {} + fn inline async mut invalid2 {} + + fn pub inline async invalid3 {} + fn pub inline async mut invalid4 {} + + fn inline valid1 {} + fn inline mut valid2 {} +} + +# inline_methods.inko:25:3 error(invalid-method): the 'inline' keyword can't be used for this type of method +# inline_methods.inko:26:3 error(invalid-method): the 'inline' keyword can't be used for this type of method +# inline_methods.inko:27:3 error(invalid-method): the 'inline' keyword can't be used for this type of method +# inline_methods.inko:39:3 error(invalid-method): the 'inline' keyword can't be used for this type of method +# inline_methods.inko:40:3 error(invalid-method): the 'inline' keyword can't be used for this type of method +# inline_methods.inko:42:3 error(invalid-method): the 'inline' keyword can't be used for this type of method +# inline_methods.inko:43:3 error(invalid-method): the 'inline' keyword can't be used for this type of method diff --git a/std/src/std/array.inko b/std/src/std/array.inko index c0dc835fd..c2e2c0f4c 100644 --- a/std/src/std/array.inko +++ b/std/src/std/array.inko @@ -13,7 +13,10 @@ import std.rand (Shuffle) # The capacity to use when resizing an array for the first time. let START_CAPACITY = 4 -fn stable_sort[T](array: mut Array[T], compare: mut fn (ref T, ref T) -> Bool) { +fn inline stable_sort[T]( + array: mut Array[T], + compare: mut fn (ref T, ref T) -> Bool, +) { let len = array.size # The algorithm here is the recursive merge sort algorithm. While faster @@ -31,7 +34,7 @@ fn stable_sort[T](array: mut Array[T], compare: mut fn (ref T, ref T) -> Bool) { merge_sort(tmp, array, start: 0, end: len, compare: compare) } -fn merge_sort[T]( +fn inline merge_sort[T]( a: mut Array[T], b: mut Array[T], start: Int, @@ -71,7 +74,7 @@ fn merge_sort[T]( # # Panics # # This method panics if the index is out of bounds. -fn pub bounds_check(index: Int, size: Int) { +fn pub inline bounds_check(index: Int, size: Int) { if index >= 0 and index < size { return } panic('The index ${index} is out of bounds (size: ${size})') diff --git a/std/src/std/float.inko b/std/src/std/float.inko index 0be16e269..5ef7e372f 100644 --- a/std/src/std/float.inko +++ b/std/src/std/float.inko @@ -25,17 +25,17 @@ trait pub ToFloat { # A 64-bit floating point number. class builtin Float { # Returns a NaN. - fn pub static not_a_number -> Float { + fn pub inline static not_a_number -> Float { 0.0 / 0.0 } # Returns the positive infinity value. - fn pub static infinity -> Float { + fn pub inline static infinity -> Float { 1.0 / 0.0 } # Returns the negative infinity value. - fn pub static negative_infinity -> Float { + fn pub inline static negative_infinity -> Float { -1.0 / 0.0 } @@ -46,7 +46,7 @@ class builtin Float { # ```inko # Float.from_bits(0x4029000000000000) # => 12.5 # ``` - fn pub static from_bits(bits: Int) -> Float { + fn pub inline static from_bits(bits: Int) -> Float { _INKO.float_from_bits(bits) } @@ -88,7 +88,7 @@ class builtin Float { # 42.0.absolute # => 42 # -42.0.absolute # => 42 # ``` - fn pub absolute -> Float { + fn pub inline absolute -> Float { Float.from_bits(to_bits & MAX) } @@ -100,7 +100,7 @@ class builtin Float { # 42.0.opposite # => -42 # -42.0.opposite # => 42 # ``` - fn pub opposite -> Float { + fn pub inline opposite -> Float { Float.from_bits(to_bits ^ MIN) } @@ -119,7 +119,7 @@ class builtin Float { # ```inko # Float.not_a_number.not_a_number? # => true # ``` - fn pub not_a_number? -> Bool { + fn pub inline not_a_number? -> Bool { _INKO.float_is_nan(self) } @@ -138,7 +138,7 @@ class builtin Float { # ```inko # (10.0 / 0.0).infinite? # => true # ``` - fn pub infinite? -> Bool { + fn pub inline infinite? -> Bool { _INKO.float_is_inf(self) } @@ -157,7 +157,7 @@ class builtin Float { # ```inko # Float.not_a_number.floor.not_a_number? # => true # ``` - fn pub floor -> Float { + fn pub inline floor -> Float { _INKO.float_floor(self) } @@ -176,7 +176,7 @@ class builtin Float { # ```inko # Float.not_a_number.ceil.not_a_number? # => true # ``` - fn pub ceil -> Float { + fn pub inline ceil -> Float { _INKO.float_ceil(self) } @@ -211,7 +211,7 @@ class builtin Float { # ```inko # Float.not_a_number.round.not_a_number? # => true # ``` - fn pub round(decimals: Int) -> Float { + fn pub inline round(decimals: Int) -> Float { if decimals <= 0 { return _INKO.float_round(self) } if decimals > 4_294_967_295 { return self } @@ -231,7 +231,7 @@ class builtin Float { # ```inko # 3.5.fract => 0.5 # ``` - fn pub fractional -> Float { + fn pub inline fractional -> Float { absolute % 1.0 } @@ -247,71 +247,77 @@ class builtin Float { # ```inko # 1.0.to_bits # => 4607182418800017408 # ``` - fn pub to_bits -> Int { + fn pub inline to_bits -> Int { _INKO.float_to_bits(self) } # Returns `true` if `self` has a negative sign, including `-0.0`, NaNs with a # negative sign bit, and negative infinity. - fn pub negative_sign? -> Bool { + fn pub inline negative_sign? -> Bool { to_bits & MIN != 0 } # Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with a # positive sign bit, and positive infinity. - fn pub positive_sign? -> Bool { + fn pub inline positive_sign? -> Bool { negative_sign?.false? } } impl ToInt for Float { - fn pub to_int -> Int { + fn pub inline to_int -> Int { self as Int } } impl ToFloat for Float { - fn pub to_float -> Float { + fn pub inline to_float -> Float { clone } } impl Clone[Float] for Float { - fn pub clone -> Float { + fn pub inline clone -> Float { self } } impl Add[Float, Float] for Float { - fn pub +(other: ref Float) -> Float { + fn pub inline +(other: ref Float) -> Float { _INKO.float_add(self, other) } } impl Subtract[Float, Float] for Float { - fn pub -(other: ref Float) -> Float { + fn pub inline -(other: ref Float) -> Float { _INKO.float_sub(self, other) } } impl Divide[Float, Float] for Float { - fn pub /(other: ref Float) -> Float { + fn pub inline /(other: ref Float) -> Float { _INKO.float_div(self, other) } } impl Multiply[Float, Float] for Float { - fn pub *(other: ref Float) -> Float { + fn pub inline *(other: ref Float) -> Float { _INKO.float_mul(self, other) } } impl Modulo[Float, Float] for Float { - fn pub %(other: ref Float) -> Float { + fn pub inline %(other: ref Float) -> Float { _INKO.float_mod(self, other) } } +impl Power[Int, Float] for Float { + fn pub inline **(other: ref Int) -> Float { + _INKO.float_powi(self, other) + } +} + impl Compare[Float] for Float { # Return the ordering between `self` and `other`. # @@ -330,7 +336,7 @@ impl Compare[Float] for Float { # - positive infinity # - positive signaling NaN # - positive quiet NaN - fn pub cmp(other: ref Float) -> Ordering { + fn pub inline cmp(other: ref Float) -> Ordering { let mut lhs = to_bits let mut rhs = other.to_bits @@ -340,19 +346,19 @@ impl Compare[Float] for Float { lhs.cmp(rhs) } - fn pub <(other: ref Float) -> Bool { + fn pub inline <(other: ref Float) -> Bool { _INKO.float_lt(self, other) } - fn pub <=(other: ref Float) -> Bool { + fn pub inline <=(other: ref Float) -> Bool { _INKO.float_le(self, other) } - fn pub >(other: ref Float) -> Bool { + fn pub inline >(other: ref Float) -> Bool { _INKO.float_gt(self, other) } - fn pub >=(other: ref Float) -> Bool { + fn pub inline >=(other: ref Float) -> Bool { _INKO.float_ge(self, other) } } @@ -369,7 +375,7 @@ impl Equal[ref Float] for Float { # # See https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ # for more details on how ULPs work. - fn pub ==(other: ref Float) -> Bool { + fn pub inline ==(other: ref Float) -> Bool { # Handle simple comparisons such as `1.2 == 1.2` and `0.0 == -0.0`. if _INKO.float_eq(self, other) { return true } @@ -417,7 +423,7 @@ impl ToString for Float { } impl Hash for Float { - fn pub hash[H: mut + Hasher](hasher: mut H) { + fn pub inline hash[H: mut + Hasher](hasher: mut H) { hasher.write(to_bits) } } @@ -427,9 +433,3 @@ impl Format for Float { formatter.write(to_string) } } - -impl Power[Int, Float] for Float { - fn pub **(other: ref Int) -> Float { - _INKO.float_powi(self, other) - } -} diff --git a/std/src/std/int.inko b/std/src/std/int.inko index 010e8f9ab..2851be1d0 100644 --- a/std/src/std/int.inko +++ b/std/src/std/int.inko @@ -297,7 +297,7 @@ class builtin Int { # 4.absolute # => 4 # -9_223_372_036_854_775808.absolute # => -9_223_372_036_854_775808 # ``` - fn pub absolute -> Int { + fn pub inline absolute -> Int { _INKO.int_absolute(self) } @@ -309,7 +309,7 @@ class builtin Int { # -42.opposite # => 42 # 42.opposite # => -42 # ``` - fn pub opposite -> Int { + fn pub inline opposite -> Int { 0 - self } @@ -386,7 +386,7 @@ class builtin Int { # ```inko # 12.not # => -13 # ``` - fn pub not -> Int { + fn pub inline not -> Int { _INKO.int_bit_not(self) } @@ -398,7 +398,7 @@ class builtin Int { # ```inko # 0xaa00000000006e1.rotate_left(12) # => 0x6e10aa # ``` - fn pub rotate_left(amount: Int) -> Int { + fn pub inline rotate_left(amount: Int) -> Int { _INKO.int_rotate_left(self, amount) } @@ -410,7 +410,7 @@ class builtin Int { # ```inko # 0x6e10aa.rotate_right(12) # => 0xaa00000000006e1 # ``` - fn pub rotate_right(amount: Int) -> Int { + fn pub inline rotate_right(amount: Int) -> Int { _INKO.int_rotate_right(self, amount) } @@ -424,7 +424,7 @@ class builtin Int { # 1.wrapping_add(1) # => 2 # MAX.wrapping_add(1) # => MIN # ``` - fn pub wrapping_add(other: Int) -> Int { + fn pub inline wrapping_add(other: Int) -> Int { _INKO.int_wrapping_add(self, other) } @@ -438,7 +438,7 @@ class builtin Int { # 1.wrapping_sub(1) # => 0 # MIN.wrapping_sub(1) # => MAX # ``` - fn pub wrapping_sub(other: Int) -> Int { + fn pub inline wrapping_sub(other: Int) -> Int { _INKO.int_wrapping_sub(self, other) } @@ -452,7 +452,7 @@ class builtin Int { # 1.wrapping_mul(2) # => 2 # MAX.wrapping_mul(2) # => -2 # ``` - fn pub wrapping_mul(other: Int) -> Int { + fn pub inline wrapping_mul(other: Int) -> Int { _INKO.int_wrapping_mul(self, other) } @@ -466,7 +466,7 @@ class builtin Int { # 1.checked_add(5) # => Option.Some(6) # MAX.checked_add(1) # => Option.None # ``` - fn pub checked_add(other: Int) -> Option[Int] { + fn pub inline checked_add(other: Int) -> Option[Int] { let res = _INKO.int_checked_add(self, other) if res.tag as Int == 0 { @@ -486,7 +486,7 @@ class builtin Int { # 1.checked_sub(1) # => Option.Some(0) # MIN.checked_sub(1) # => Option.None # ``` - fn pub checked_sub(other: Int) -> Option[Int] { + fn pub inline checked_sub(other: Int) -> Option[Int] { let res = _INKO.int_checked_sub(self, other) if res.tag as Int == 0 { @@ -506,7 +506,7 @@ class builtin Int { # 1.checked_mul(2) # => Option.Some(2) # MAX.checked_mul(2) # => Option.None # ``` - fn pub checked_mul(other: Int) -> Option[Int] { + fn pub inline checked_mul(other: Int) -> Option[Int] { let res = _INKO.int_checked_mul(self, other) if res.tag as Int == 0 { @@ -527,7 +527,7 @@ class builtin Int { # 10.checked_div(0) # => Option.None # 10.checked_div(2) # => Option.Some(5) # ``` - fn pub checked_div(other: Int) -> Option[Int] { + fn pub inline checked_div(other: Int) -> Option[Int] { if other == 0 or (self == MIN and other == -1) { Option.None } else { @@ -545,7 +545,7 @@ class builtin Int { # 2.checked_pow(2) # => Option.Some(4) # MAX.checked_pow(2) # => Option.None # ``` - fn pub checked_pow(other: Int) -> Option[Int] { + fn pub inline checked_pow(other: Int) -> Option[Int] { let res = inko_int_checked_pow(self, other) if res.tag as Int == 0 { @@ -563,11 +563,11 @@ class builtin Int { # 12345.swap_bytes # => 4120793659044003840 # 4120793659044003840.swap_bytes # => 12345 # ``` - fn pub swap_bytes -> Int { + fn pub inline swap_bytes -> Int { _INKO.int_swap_bytes(self) } - fn unchecked_div(other: Int) -> Int { + fn inline unchecked_div(other: Int) -> Int { # This implements floored division, rather than rounding towards zero. This # makes division work more natural when using negative numbers. # @@ -581,19 +581,19 @@ class builtin Int { } impl ToInt for Int { - fn pub to_int -> Int { + fn pub inline to_int -> Int { clone } } impl ToFloat for Int { - fn pub to_float -> Float { + fn pub inline to_float -> Float { self as Float } } impl Compare[Int] for Int { - fn pub cmp(other: ref Int) -> Ordering { + fn pub inline cmp(other: ref Int) -> Ordering { if self > other { Ordering.Greater } else if self < other { @@ -603,31 +603,31 @@ impl Compare[Int] for Int { } } - fn pub <(other: ref Int) -> Bool { + fn pub inline <(other: ref Int) -> Bool { _INKO.int_lt(self, other) } - fn pub <=(other: ref Int) -> Bool { + fn pub inline <=(other: ref Int) -> Bool { _INKO.int_le(self, other) } - fn pub >(other: ref Int) -> Bool { + fn pub inline >(other: ref Int) -> Bool { _INKO.int_gt(self, other) } - fn pub >=(other: ref Int) -> Bool { + fn pub inline >=(other: ref Int) -> Bool { _INKO.int_ge(self, other) } } impl Equal[ref Int] for Int { - fn pub ==(other: ref Int) -> Bool { + fn pub inline ==(other: ref Int) -> Bool { _INKO.int_eq(self, other) } } impl Clone[Int] for Int { - fn pub clone -> Int { + fn pub inline clone -> Int { self } } @@ -639,7 +639,7 @@ impl ToString for Int { } impl Add[Int, Int] for Int { - fn pub +(other: ref Int) -> Int { + fn pub inline +(other: ref Int) -> Int { let res = _INKO.int_checked_add(self, other) if res.tag as Int == 0 { @@ -651,7 +651,7 @@ impl Add[Int, Int] for Int { } impl Subtract[Int, Int] for Int { - fn pub -(other: ref Int) -> Int { + fn pub inline -(other: ref Int) -> Int { let res = _INKO.int_checked_sub(self, other) if res.tag as Int == 0 { @@ -663,7 +663,7 @@ impl Subtract[Int, Int] for Int { } impl Divide[Int, Int] for Int { - fn pub /(other: ref Int) -> Int { + fn pub inline /(other: ref Int) -> Int { if other == 0 or (self == MIN and other == -1) { overflow(self, '-', other) } @@ -673,7 +673,7 @@ impl Divide[Int, Int] for Int { } impl Multiply[Int, Int] for Int { - fn pub *(other: ref Int) -> Int { + fn pub inline *(other: ref Int) -> Int { let res = _INKO.int_checked_mul(self, other) if res.tag as Int == 0 { @@ -685,7 +685,7 @@ impl Multiply[Int, Int] for Int { } impl Modulo[Int, Int] for Int { - fn pub %(other: ref Int) -> Int { + fn pub inline %(other: ref Int) -> Int { if other == 0 or (self == MIN and other == -1) { overflow(self, '%', other) } @@ -701,25 +701,25 @@ impl Modulo[Int, Int] for Int { } impl BitAnd[Int, Int] for Int { - fn pub &(other: ref Int) -> Int { + fn pub inline &(other: ref Int) -> Int { _INKO.int_bit_and(self, other) } } impl BitOr[Int, Int] for Int { - fn pub |(other: ref Int) -> Int { + fn pub inline |(other: ref Int) -> Int { _INKO.int_bit_or(self, other) } } impl BitXor[Int, Int] for Int { - fn pub ^(other: ref Int) -> Int { + fn pub inline ^(other: ref Int) -> Int { _INKO.int_bit_xor(self, other) } } impl ShiftLeft[Int, Int] for Int { - fn pub <<(other: ref Int) -> Int { + fn pub inline <<(other: ref Int) -> Int { if other >= BITS { overflow(self, '<<', other) } _INKO.int_shl(self, other) @@ -727,7 +727,7 @@ impl ShiftLeft[Int, Int] for Int { } impl ShiftRight[Int, Int] for Int { - fn pub >>(other: ref Int) -> Int { + fn pub inline >>(other: ref Int) -> Int { if other >= BITS { overflow(self, '>>', other) } _INKO.int_shr(self, other) @@ -735,7 +735,7 @@ impl ShiftRight[Int, Int] for Int { } impl UnsignedShiftRight[Int, Int] for Int { - fn pub >>>(other: ref Int) -> Int { + fn pub inline >>>(other: ref Int) -> Int { if other >= BITS { overflow(self, '>>>', other) } _INKO.int_unsigned_shr(self, other) @@ -743,7 +743,7 @@ impl UnsignedShiftRight[Int, Int] for Int { } impl Power[Int, Int] for Int { - fn pub **(other: ref Int) -> Int { + fn pub inline **(other: ref Int) -> Int { let res = inko_int_checked_pow(self, other) if res.tag as Int == 0 { @@ -755,7 +755,7 @@ impl Power[Int, Int] for Int { } impl Hash for Int { - fn pub hash[H: mut + Hasher](hasher: mut H) { + fn pub inline hash[H: mut + Hasher](hasher: mut H) { hasher.write(clone) } } diff --git a/types/src/collections.rs b/types/src/collections.rs index 86bfd7ba5..52027c7ac 100644 --- a/types/src/collections.rs +++ b/types/src/collections.rs @@ -73,6 +73,10 @@ impl IndexMap { &mut self.values } + pub fn into_values(self) -> Vec { + self.values + } + pub fn contains_key(&self, k: &Q) -> bool where K: Borrow, diff --git a/types/src/lib.rs b/types/src/lib.rs index 4ecd17695..2dfac570d 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -2231,6 +2231,19 @@ impl CallConvention { } } +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum Inline { + /// The method must never be inlined. + Never, + + /// The need for inlining should be determined based on some set of + /// heuristics. + Infer, + + /// The method must be inlined into every static call site. + Always, +} + /// A static or instance method. #[derive(Clone)] pub struct Method { @@ -2241,6 +2254,7 @@ pub struct Method { kind: MethodKind, call_convention: CallConvention, visibility: Visibility, + inline: Inline, type_parameters: IndexMap, arguments: Arguments, bounds: TypeBounds, @@ -2280,11 +2294,18 @@ impl Method { ) -> MethodId { assert!(db.methods.len() < u32::MAX as usize); - let call_convention = if let MethodKind::Extern = kind { - CallConvention::C - } else { - CallConvention::Inko - }; + let mut call_convention = CallConvention::Inko; + let mut inline = Inline::Infer; + + if let MethodKind::Extern = kind { + call_convention = CallConvention::C; + + // External functions are never inlined because they're either + // defined externally (such that there's nothing to inline) _or_ + // they're meant to be called from C code, in which case the + // function _must_ in fact exist in generated code. + inline = Inline::Never; + } let id = db.methods.len(); let method = Method { @@ -2306,6 +2327,7 @@ impl Method { variadic: false, specializations: HashMap::new(), shapes: Vec::new(), + inline, }; db.methods.push(method); @@ -2642,7 +2664,7 @@ impl MethodId { } pub fn clone_for_specialization(self, db: &mut Database) -> MethodId { - let (module, location, name, vis, kind, source) = { + let (module, location, name, vis, kind, source, inline) = { let old = self.get(db); ( @@ -2652,12 +2674,14 @@ impl MethodId { old.visibility, old.kind, old.source, + old.inline, ) }; let new = Method::alloc(db, module, location, name, vis, kind); new.set_source(db, source); + new.set_inline(db, inline); new } @@ -2720,6 +2744,18 @@ impl MethodId { self.get(db).call_convention } + pub fn always_inline(self, db: &mut Database) { + self.get_mut(db).inline = Inline::Always; + } + + pub fn set_inline(self, db: &mut Database, inline: Inline) { + self.get_mut(db).inline = inline; + } + + pub fn inline(self, db: &Database) -> Inline { + self.get(db).inline + } + fn get(self, db: &Database) -> &Method { &db.methods[self.0 as usize] } @@ -2746,7 +2782,15 @@ impl Block for MethodId { } fn set_return_type(&self, db: &mut Database, typ: TypeRef) { - self.get_mut(db).return_type = typ; + let method = self.get_mut(db); + + // If a method never returns there's no point in inlining it, because it + // can only be called once upon which the program terminates. + if let TypeRef::Never = typ { + method.inline = Inline::Never; + } + + method.return_type = typ; } fn return_type(&self, db: &Database) -> TypeRef { @@ -5442,6 +5486,37 @@ mod tests { assert_eq!(db.methods[0].kind, MethodKind::Moving); } + #[test] + fn test_method_alloc_extern() { + let mut db = Database::new(); + let id = Method::alloc( + &mut db, + ModuleId(0), + Location::default(), + "foo".to_string(), + Visibility::Private, + MethodKind::Extern, + ); + + assert_eq!(id.inline(&db), Inline::Never); + } + + #[test] + fn test_method_set_never_return_type() { + let mut db = Database::new(); + let id = Method::alloc( + &mut db, + ModuleId(0), + Location::default(), + "foo".to_string(), + Visibility::Private, + MethodKind::Instance, + ); + + id.set_return_type(&mut db, TypeRef::Never); + assert_eq!(id.inline(&db), Inline::Never); + } + #[test] fn test_method_id_named_type() { let mut db = Database::new();