Skip to content

Commit

Permalink
Add support for inline types
Browse files Browse the repository at this point in the history
An inline type is an immutable value type that's allocated inline/on the
stack. Both regular and enum types can be defined as "inline".

Inline types don't have object headers, and combined with them being
stored inline means they don't incur extra indirection/overhead. For
example, consider this type:

    class inline A {
      let @A: Int
      let @b: Int
    }

If this were a regular type, its size would be 32 bytes: 16 bytes for
the header, and 16 bytes for the two fields. Because it's an inline type
though, it only needs 16 bytes.

Inline types are restricted to types that can be trivially copied, such
as Int, Float, and other inline types. String isn't allowed in inline
types at this point as this could result in an unexpected copy cost due
to String using atomic reference counting.

Inline types are immutable because supporting mutations introduces
significant compiler complexity, especially when dealing with closures
that capture generic type parameters, as support for mutations would
require rewriting part of the generated code as part of type
specialization.

This fixes #750.

Changelog: added
  • Loading branch information
yorickpeterse committed Nov 28, 2024
1 parent db72d59 commit 8ca46bf
Show file tree
Hide file tree
Showing 66 changed files with 4,243 additions and 1,680 deletions.
3 changes: 3 additions & 0 deletions ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ pub enum ClassKind {
#[derive(Debug, PartialEq, Eq)]
pub struct DefineClass {
pub public: bool,
pub inline: bool,
pub kind: ClassKind,
pub name: Constant,
pub type_parameters: Option<TypeParameters>,
Expand Down Expand Up @@ -637,13 +638,15 @@ impl Node for ReopenClass {
pub enum Requirement {
Trait(TypeName),
Mutable(Location),
Inline(Location),
}

impl Node for Requirement {
fn location(&self) -> &Location {
match self {
Requirement::Trait(n) => &n.location,
Requirement::Mutable(loc) => loc,
Requirement::Inline(loc) => loc,
}
}
}
Expand Down
81 changes: 81 additions & 0 deletions ast/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,12 @@ impl Parser {
start: Token,
) -> Result<TopLevelExpression, ParseError> {
let public = self.next_is_public();
let inline = if self.peek().kind == TokenKind::Inline {
self.next();
true
} else {
false
};
let kind = match self.peek().kind {
TokenKind::Async => {
self.next();
Expand Down Expand Up @@ -1064,6 +1070,7 @@ impl Parser {

Ok(TopLevelExpression::DefineClass(Box::new(DefineClass {
public,
inline,
kind,
name,
type_parameters,
Expand Down Expand Up @@ -1284,6 +1291,7 @@ impl Parser {
let token = self.require()?;
let req = match token.kind {
TokenKind::Mut => Requirement::Mutable(token.location),
TokenKind::Inline => Requirement::Inline(token.location),
_ => Requirement::Trait(
self.type_name_with_optional_namespace(token)?,
),
Expand Down Expand Up @@ -4604,6 +4612,7 @@ mod tests {
top(parse("class A {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand All @@ -4623,6 +4632,7 @@ mod tests {
top(parse("class pub A {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: true,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand All @@ -4645,6 +4655,7 @@ mod tests {
top(parse("class extern A {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand All @@ -4661,12 +4672,36 @@ mod tests {
);
}

#[test]
fn test_inline_class() {
assert_eq!(
top(parse("class inline A {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: true,
name: Constant {
source: None,
name: "A".to_string(),
location: cols(14, 14)
},
kind: ClassKind::Regular,
type_parameters: None,
body: ClassExpressions {
values: Vec::new(),
location: cols(16, 17)
},
location: cols(1, 17)
}))
);
}

#[test]
fn test_async_class() {
assert_eq!(
top(parse("class async A {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand All @@ -4689,6 +4724,7 @@ mod tests {
top(parse("class A { fn async foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4727,6 +4763,7 @@ mod tests {
top(parse("class A { fn async mut foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4768,6 +4805,7 @@ mod tests {
top(parse("class A[B: X, C] {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4820,6 +4858,7 @@ mod tests {
top(parse("class A[B: a.X] {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4867,6 +4906,7 @@ mod tests {
top(parse("class A { fn foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4905,6 +4945,7 @@ mod tests {
top(parse("class A { fn pub foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4946,6 +4987,7 @@ mod tests {
top(parse("class A { fn move foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -4987,6 +5029,7 @@ mod tests {
top(parse("class A { fn inline foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -5028,6 +5071,7 @@ mod tests {
top(parse("class A { fn mut foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -5069,6 +5113,7 @@ mod tests {
top(parse("class A { fn static foo {} }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -5110,6 +5155,7 @@ mod tests {
top(parse("class A { let @foo: A }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -5147,6 +5193,7 @@ mod tests {
top(parse("class A { let pub @foo: A }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
name: Constant {
source: None,
name: "A".to_string(),
Expand Down Expand Up @@ -5507,6 +5554,37 @@ mod tests {
}))
);

assert_eq!(
top(parse("impl A if T: inline {}")),
TopLevelExpression::ReopenClass(Box::new(ReopenClass {
class_name: Constant {
source: None,
name: "A".to_string(),
location: cols(6, 6)
},
body: ImplementationExpressions {
values: Vec::new(),
location: cols(21, 22)
},
bounds: Some(TypeBounds {
values: vec![TypeBound {
name: Constant {
source: None,
name: "T".to_string(),
location: cols(11, 11)
},
requirements: Requirements {
values: vec![Requirement::Inline(cols(14, 19))],
location: cols(14, 19)
},
location: cols(11, 19)
}],
location: cols(11, 19)
}),
location: cols(1, 19)
}))
);

assert_eq!(
top(parse("impl A if T: mut, {}")),
TopLevelExpression::ReopenClass(Box::new(ReopenClass {
Expand Down Expand Up @@ -6166,6 +6244,7 @@ mod tests {
top(parse("class builtin A {}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
kind: ClassKind::Builtin,
name: Constant {
source: None,
Expand Down Expand Up @@ -9600,6 +9679,7 @@ mod tests {
top(parse("class enum Option[T] { case Some(T) case None }")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
kind: ClassKind::Enum,
name: Constant {
source: None,
Expand Down Expand Up @@ -9699,6 +9779,7 @@ mod tests {
top(parse_with_comments("class A {\n# foo\n}")),
TopLevelExpression::DefineClass(Box::new(DefineClass {
public: false,
inline: false,
kind: ClassKind::Regular,
name: Constant {
source: None,
Expand Down
19 changes: 12 additions & 7 deletions compiler/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ use crate::pkg::version::Version;
use crate::state::State;
use crate::symbol_names::SymbolNames;
use crate::type_check::define_types::{
CheckTraitImplementations, CheckTraitRequirements, CheckTypeParameters,
DefineConstructors, DefineFields, DefineTraitRequirements,
DefineTypeParameterRequirements, DefineTypeParameters, DefineTypes,
ImplementTraits, InsertPrelude,
check_recursive_types, CheckTraitImplementations, CheckTraitRequirements,
CheckTypeParameters, DefineConstructors, DefineFields,
DefineTraitRequirements, DefineTypeParameterRequirements,
DefineTypeParameters, DefineTypes, ImplementTraits, InsertPrelude,
};
use crate::type_check::expressions::{
check_unused_imports, define_constants, Expressions,
use crate::type_check::expressions::{define_constants, Expressions};
use crate::type_check::imports::{
check_unused_imports, CollectExternImports, DefineImportedTypes,
};
use crate::type_check::imports::{CollectExternImports, DefineImportedTypes};
use crate::type_check::methods::{
CheckMainMethod, DefineMethods, DefineModuleMethodNames,
ImplementTraitMethods,
Expand Down Expand Up @@ -262,6 +262,10 @@ impl Compiler {
// MIR to LLVM, otherwise we may generate incorrect code.
self.specialize_mir(&mut mir);

// At this point we can get rid of various data structures stored in the
// type database. This must be done _after_ specialization.
self.state.db.compact();

// Splitting is done _after_ specialization, since specialization
// introduces new types and methods.
mir.split_modules(&mut self.state);
Expand Down Expand Up @@ -522,6 +526,7 @@ LLVM module timings:
&& CheckTypeParameters::run_all(state, modules)
&& DefineConstructors::run_all(state, modules)
&& DefineFields::run_all(state, modules)
&& check_recursive_types(state, modules)
&& DefineMethods::run_all(state, modules)
&& CheckMainMethod::run(state)
&& ImplementTraitMethods::run_all(state, modules)
Expand Down
Loading

0 comments on commit 8ca46bf

Please sign in to comment.