Skip to content

Commit

Permalink
Add support for inlining method calls
Browse files Browse the repository at this point in the history
The compiler is now able to inline calls to static and instance methods.
For each method we calculate a rough weight/cost, and calls are inlined
into their callers until the maximum weight is reached. Inlining is done
bottom-up using Tarjan's strongly connected components algorithm,
reducing the amount of duplicate inlining work. Inlining is also done in
a deterministic order as to ensure incremental compilation caches can be
reused as much as possible.

The current inlining threshold is on the conservative end as to not
increase compile times and compile-time memory usage too much. Over time
we may relax this based on user reports and any extra optimization
passes we might add.

If a method is annotated with the `inline` keyword, it's _always_
inlined into the caller regardless of it or the caller's weight. This is
meant to be used when you want to guarantee a method is inlined, such as
for the various operator methods of Int, Float, Bool, etc. Because of
this guarantee one should use it sparingly, as to not increase the
compile time and executable size too much.

This fixes #343.

Changelog: added
  • Loading branch information
yorickpeterse committed Oct 8, 2024
1 parent 9f68de6 commit cb7ce79
Show file tree
Hide file tree
Showing 62 changed files with 4,079 additions and 2,292 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,14 @@ jobs:
key: amd64-linux-gnu-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}
- name: Run compiler tests
run: cargo test
- name: Run stdlib tests
run: 'cd std && cargo run -- test'
- run: cd std
# We run tests with and without optimizations, such that we can catch any
# potential miscompilations introduced by optimizations. We only do this
# for this particular target as our optimizations aren't target specific.
- name: Run stdlib tests with optimizations
run: 'cargo run -- test'
- name: Run stdlib tests without optimizations
run: 'cargo run -- test --opt=none'

amd64-linux-musl:
runs-on: ubuntu-latest
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["ast", "inko", "compiler", "rt"]
members = ["ast", "inko", "compiler", "rt", "location"]
resolver = "2"

[workspace.package]
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ ${SOURCE_TAR}: ${TMP_DIR}
std/src \
rt \
types \
location \
| gzip > "${@}"

release/source: ${SOURCE_TAR}
Expand Down
1 change: 1 addition & 0 deletions ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ doctest = false
[dependencies]
unicode-segmentation = "^1.8"
getopts = "^0.2"
location = { path = "../location" }

[dev-dependencies]
similar-asserts = "^1.1"
88 changes: 53 additions & 35 deletions ast/src/lexer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Lexical analysis of Inko source code.
use crate::source_location::SourceLocation;
use location::Location;
use unicode_segmentation::UnicodeSegmentation;

const NULL: u8 = 0;
Expand Down Expand Up @@ -166,6 +166,7 @@ pub enum TokenKind {
While,
Whitespace,
Extern,
Inline,
}

impl TokenKind {
Expand Down Expand Up @@ -268,6 +269,7 @@ impl TokenKind {
TokenKind::Nil => "the 'nil' keyword",
TokenKind::Replace => "a '=:'",
TokenKind::Extern => "the 'extern' keyword",
TokenKind::Inline => "the 'inline' keyword",
}
}
}
Expand All @@ -276,23 +278,23 @@ impl TokenKind {
pub struct Token {
pub kind: TokenKind,
pub value: String,
pub location: SourceLocation,
pub location: Location,
}

impl Token {
fn new(kind: TokenKind, value: String, location: SourceLocation) -> Self {
fn new(kind: TokenKind, value: String, location: Location) -> Self {
Self { kind, value, location }
}

/// Returns a token signalling unexpected input. The token contains the
/// invalid character.
fn invalid(value: String, location: SourceLocation) -> Self {
fn invalid(value: String, location: Location) -> Self {
Self::new(TokenKind::Invalid, value, location)
}

/// Returns a token that signals the end of the input stream. We use null
/// tokens so we don't need to wrap/unwrap every token using an Option type.
fn null(location: SourceLocation) -> Self {
fn null(location: Location) -> Self {
Self::new(TokenKind::Null, String::new(), location)
}

Expand Down Expand Up @@ -335,6 +337,7 @@ impl Token {
| TokenKind::Case
| TokenKind::Enum
| TokenKind::Extern
| TokenKind::Inline
)
}

Expand Down Expand Up @@ -363,7 +366,7 @@ impl Token {
}

pub fn same_line_as(&self, token: &Token) -> bool {
self.location.lines.start() == token.location.lines.start()
self.location.line_start == token.location.line_start
}
}

Expand Down Expand Up @@ -422,10 +425,10 @@ pub struct Lexer {
states: Vec<State>,

/// The current line number.
line: usize,
line: u32,

/// The current (starting) column number.
column: usize,
column: u32,
}

impl Lexer {
Expand All @@ -443,8 +446,13 @@ impl Lexer {
}
}

pub fn start_location(&self) -> SourceLocation {
SourceLocation::new(self.line..=self.line, self.column..=self.column)
pub fn start_location(&self) -> Location {
Location {
line_start: self.line,
line_end: self.line,
column_start: self.column,
column_end: self.column,
}
}

pub fn next_token(&mut self) -> Token {
Expand All @@ -457,18 +465,16 @@ impl Lexer {
}
}

fn source_location(
&self,
start_line: usize,
start_column: usize,
) -> SourceLocation {
SourceLocation::new(
start_line..=self.line,
fn source_location(&self, start_line: u32, start_column: u32) -> Location {
Location {
line_start: start_line,
line_end: self.line,
// The end column points to whatever comes _after_ the last
// processed character. This means the end column is one column
// earlier.
start_column..=(self.column - 1),
)
column_start: start_column,
column_end: self.column - 1,
}
}

fn current_byte(&self) -> u8 {
Expand Down Expand Up @@ -500,7 +506,7 @@ impl Lexer {
}

fn advance_column(&mut self, value: &str) {
self.column += value.graphemes(true).count();
self.column += value.graphemes(true).count() as u32;
}

fn advance_char(&mut self) {
Expand Down Expand Up @@ -997,6 +1003,7 @@ impl Lexer {
"return" => TokenKind::Return,
"static" => TokenKind::Static,
"extern" => TokenKind::Extern,
"inline" => TokenKind::Inline,
_ => TokenKind::Identifier,
},
7 => match value.as_str() {
Expand Down Expand Up @@ -1087,14 +1094,14 @@ impl Lexer {
&mut self,
kind: TokenKind,
buffer: Vec<u8>,
line: usize,
column: usize,
line: u32,
column: u32,
new_line: bool,
) -> Token {
let value = String::from_utf8_lossy(&buffer).into_owned();

if !value.is_empty() {
self.column += value.graphemes(true).count();
self.column += value.graphemes(true).count() as u32;
}

let location = self.source_location(line, column);
Expand Down Expand Up @@ -1175,8 +1182,8 @@ impl Lexer {
&mut self,
kind: TokenKind,
start: usize,
line: usize,
column: usize,
line: u32,
column: u32,
) -> Token {
let value = self.slice_string(start, self.position);

Expand All @@ -1187,7 +1194,7 @@ impl Lexer {
Token::new(kind, value, location)
}

fn token(&mut self, kind: TokenKind, start: usize, line: usize) -> Token {
fn token(&mut self, kind: TokenKind, start: usize, line: u32) -> Token {
self.token_with_column(kind, start, line, self.column)
}

Expand Down Expand Up @@ -1223,13 +1230,22 @@ impl Lexer {
// When we encounter the end of the input, we want the location to point
// to the last column that came before it. This way any errors are
// reported within the bounds of the column range.
let lines = self.line..=self.line;
let location = if self.column == 1 {
SourceLocation::new(lines, 1..=1)
Location {
line_start: self.line,
line_end: self.line,
column_start: 1,
column_end: 1,
}
} else {
let column = self.column - 1;

SourceLocation::new(lines, column..=column)
Location {
line_start: self.line,
line_end: self.line,
column_start: column,
column_end: column,
}
};

Token::null(location)
Expand All @@ -1247,17 +1263,17 @@ mod tests {
}

fn location(
line_range: RangeInclusive<usize>,
column_range: RangeInclusive<usize>,
) -> SourceLocation {
SourceLocation::new(line_range, column_range)
line_range: RangeInclusive<u32>,
column_range: RangeInclusive<u32>,
) -> Location {
Location::new(&line_range, &column_range)
}

fn tok(
kind: TokenKind,
value: &str,
line_range: RangeInclusive<usize>,
column_range: RangeInclusive<usize>,
line_range: RangeInclusive<u32>,
column_range: RangeInclusive<u32>,
) -> Token {
Token::new(kind, value.to_string(), location(line_range, column_range))
}
Expand Down Expand Up @@ -1337,6 +1353,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]
Expand Down Expand Up @@ -1978,6 +1995,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);
Expand Down
1 change: 0 additions & 1 deletion ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
pub mod lexer;
pub mod nodes;
pub mod parser;
pub mod source_location;
Loading

0 comments on commit cb7ce79

Please sign in to comment.