diff --git a/ast/src/lexer.rs b/ast/src/lexer.rs index 1ea3e0730..bfc8a494e 100644 --- a/ast/src/lexer.rs +++ b/ast/src/lexer.rs @@ -170,6 +170,7 @@ pub enum TokenKind { UnsignedShrAssign, While, Whitespace, + Extern, } impl TokenKind { @@ -274,6 +275,7 @@ impl TokenKind { TokenKind::Recover => "the 'recover' keyword", TokenKind::Nil => "the 'nil' keyword", TokenKind::Replace => "a '=:'", + TokenKind::Extern => "the 'extern' keyword", } } } @@ -340,6 +342,7 @@ impl Token { | TokenKind::False | TokenKind::Case | TokenKind::Enum + | TokenKind::Extern ) } @@ -995,6 +998,7 @@ impl Lexer { "import" => TokenKind::Import, "return" => TokenKind::Return, "static" => TokenKind::Static, + "extern" => TokenKind::Extern, _ => TokenKind::Identifier, }, 7 => match value.as_str() { @@ -2073,6 +2077,7 @@ mod tests { assert_token!("import", Import, "import", 1..=1, 1..=6); 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!("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 303fd623f..7d75ed735 100644 --- a/ast/src/nodes.rs +++ b/ast/src/nodes.rs @@ -337,6 +337,24 @@ impl Node for Import { } } +#[derive(Debug, PartialEq, Eq)] +pub struct ExternImportPath { + pub path: String, + pub location: SourceLocation, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ExternImport { + pub path: ExternImportPath, + pub location: SourceLocation, +} + +impl Node for ExternImport { + fn location(&self) -> &SourceLocation { + &self.location + } +} + #[derive(Debug, PartialEq, Eq)] pub struct DefineConstant { pub public: bool, @@ -359,6 +377,7 @@ pub enum MethodKind { Moving, Mutable, AsyncMutable, + Extern, } #[derive(Debug, PartialEq, Eq)] @@ -419,6 +438,7 @@ pub enum ClassKind { Builtin, Enum, Regular, + Extern, } #[derive(Debug, PartialEq, Eq)] @@ -501,6 +521,7 @@ pub enum TopLevelExpression { ReopenClass(Box), ImplementTrait(Box), Import(Box), + ExternImport(Box), } impl Node for TopLevelExpression { @@ -513,6 +534,7 @@ impl Node for TopLevelExpression { TopLevelExpression::ReopenClass(ref typ) => typ.location(), TopLevelExpression::ImplementTrait(ref typ) => typ.location(), TopLevelExpression::Import(ref typ) => typ.location(), + TopLevelExpression::ExternImport(ref typ) => typ.location(), } } } diff --git a/ast/src/parser.rs b/ast/src/parser.rs index e09622b23..a3a0d36d5 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -119,6 +119,10 @@ impl Parser { &mut self, start: Token, ) -> Result { + if self.peek().kind == TokenKind::Extern { + return self.extern_import(start); + } + let path = self.import_path()?; let symbols = self.import_symbols()?; let end_loc = @@ -239,6 +243,48 @@ impl Parser { Ok(Some(ImportAlias { name: token.value, location: token.location })) } + fn extern_import( + &mut self, + start: Token, + ) -> Result { + // Skip the "extern". + self.next(); + + let path_start = self.require()?; + let path = self.extern_import_path(path_start)?; + let location = + SourceLocation::start_end(&start.location, &path.location); + + Ok(TopLevelExpression::ExternImport(Box::new(ExternImport { + path, + location, + }))) + } + + fn extern_import_path( + &mut self, + start: Token, + ) -> Result { + let close = match start.kind { + TokenKind::SingleStringOpen => TokenKind::SingleStringClose, + TokenKind::DoubleStringOpen => TokenKind::DoubleStringClose, + _ => { + error!( + start.location, + "expected a single or double quote, found '{}' instead", + start.kind.description() + ); + } + }; + + let text = self.expect(TokenKind::StringText)?; + let close = self.expect(close)?; + let location = + SourceLocation::start_end(&start.location, &close.location); + + Ok(ExternImportPath { path: text.value, location }) + } + fn define_constant( &mut self, start: Token, @@ -715,16 +761,38 @@ impl Parser { start: Token, ) -> Result { let public = self.next_is_public(); - let kind = MethodKind::Instance; + let kind = match self.peek().kind { + TokenKind::Extern => { + self.next(); + MethodKind::Extern + } + _ => MethodKind::Instance, + }; + let name_token = self.require()?; let (name, operator) = self.method_name(name_token)?; - let type_parameters = self.optional_type_parameter_definitions()?; + let type_parameters = if let MethodKind::Extern = kind { + None + } else { + self.optional_type_parameter_definitions()? + }; let arguments = self.optional_method_arguments()?; let return_type = self.optional_return_type()?; - let body_token = self.expect(TokenKind::CurlyOpen)?; - let body = self.expressions(body_token)?; - let location = - SourceLocation::start_end(&start.location, &body.location); + let body = if let MethodKind::Extern = kind { + None + } else { + let token = self.expect(TokenKind::CurlyOpen)?; + + Some(self.expressions(token)?) + }; + + let location = SourceLocation::start_end( + &start.location, + location!(body) + .or_else(|| location!(return_type)) + .or_else(|| location!(arguments)) + .unwrap_or_else(|| &name.location), + ); Ok(TopLevelExpression::DefineMethod(Box::new(DefineMethod { public, @@ -734,7 +802,7 @@ impl Parser { arguments, return_type, location, - body: Some(body), + body, kind, }))) } @@ -884,11 +952,26 @@ impl Parser { self.next(); ClassKind::Builtin } + TokenKind::Extern => { + self.next(); + ClassKind::Extern + } _ => ClassKind::Regular, }; + let name = Constant::from(self.expect(TokenKind::Constant)?); - let type_parameters = self.optional_type_parameter_definitions()?; - let body = self.class_expressions()?; + let type_parameters = if let ClassKind::Extern = kind { + None + } else { + self.optional_type_parameter_definitions()? + }; + + let body = if let ClassKind::Extern = kind { + self.extern_class_expressions()? + } else { + self.class_expressions()? + }; + let location = SourceLocation::start_end(&start.location, &body.location); @@ -944,6 +1027,38 @@ impl Parser { } } + fn extern_class_expressions( + &mut self, + ) -> Result { + let start = self.expect(TokenKind::CurlyOpen)?; + let mut values = Vec::new(); + + loop { + let token = self.require()?; + + if token.kind == TokenKind::CurlyClose { + let location = + SourceLocation::start_end(&start.location, &token.location); + + return Ok(ClassExpressions { values, location }); + } + + let node = match token.kind { + TokenKind::Let => ClassExpression::DefineField(Box::new( + self.define_field(token)?, + )), + _ => { + error!( + token.location, + "Expected a 'let', found '{}' instead", token.value + ); + } + }; + + values.push(node); + } + } + fn class_expression( &mut self, start: Token, @@ -1307,7 +1422,7 @@ impl Parser { } fn expression(&mut self, start: Token) -> Result { - self.type_cast(start) + self.boolean_and_or(start) } fn expression_without_trailing_block( @@ -1320,27 +1435,6 @@ impl Parser { }) } - fn type_cast(&mut self, start: Token) -> Result { - let mut node = self.boolean_and_or(start)?; - - while self.peek().kind == TokenKind::As { - self.next(); - - let cast_token = self.require()?; - let cast_to = self.type_reference(cast_token)?; - let location = - SourceLocation::start_end(node.location(), cast_to.location()); - - node = Expression::TypeCast(Box::new(TypeCast { - value: node, - cast_to, - location, - })); - } - - Ok(node) - } - fn boolean_and_or( &mut self, start: Token, @@ -1375,18 +1469,37 @@ impl Parser { fn binary(&mut self, start: Token) -> Result { let mut node = self.postfix(start)?; - while let Some(op) = self.binary_operator() { - let rhs_token = self.require()?; - let rhs = self.postfix(rhs_token)?; - let location = - SourceLocation::start_end(node.location(), rhs.location()); + loop { + if let Some(op) = self.binary_operator() { + let rhs_token = self.require()?; + let rhs = self.postfix(rhs_token)?; + let location = + SourceLocation::start_end(node.location(), rhs.location()); - node = Expression::Binary(Box::new(Binary { - operator: op, - left: node, - right: rhs, - location, - })); + node = Expression::Binary(Box::new(Binary { + operator: op, + left: node, + right: rhs, + location, + })); + } else if self.peek().kind == TokenKind::As { + self.next(); + + let cast_token = self.require()?; + let cast_to = self.type_reference(cast_token)?; + let location = SourceLocation::start_end( + node.location(), + cast_to.location(), + ); + + node = Expression::TypeCast(Box::new(TypeCast { + value: node, + cast_to, + location, + })); + } else { + break; + } } Ok(node) @@ -2917,6 +3030,7 @@ mod tests { Parser::new(input.into(), "test.inko".into()) } + #[track_caller] fn parse(input: &str) -> Module { parser(input) .parse() @@ -3083,6 +3197,34 @@ mod tests { ); } + #[test] + fn test_extern_imports() { + assert_eq!( + top(parse("import extern 'foo'")), + TopLevelExpression::ExternImport(Box::new(ExternImport { + path: ExternImportPath { + path: "foo".to_string(), + location: cols(15, 19) + }, + location: cols(1, 19) + })) + ); + + assert_eq!( + top(parse("import extern \"foo\"")), + TopLevelExpression::ExternImport(Box::new(ExternImport { + path: ExternImportPath { + path: "foo".to_string(), + location: cols(15, 19) + }, + location: cols(1, 19) + })) + ); + + assert_error!("import extern ''", cols(16, 16)); + assert_error!("import extern \"\"", cols(16, 16)); + } + #[test] fn test_imports_with_symbols() { assert_eq!( @@ -4106,6 +4248,27 @@ mod tests { ); } + #[test] + fn test_extern_method() { + assert_eq!( + top(parse("fn extern foo")), + TopLevelExpression::DefineMethod(Box::new(DefineMethod { + public: false, + operator: false, + kind: MethodKind::Extern, + name: Identifier { + name: "foo".to_string(), + location: cols(11, 13) + }, + type_parameters: None, + arguments: None, + return_type: None, + body: None, + location: cols(1, 13), + })) + ); + } + #[test] fn test_invalid_methods() { assert_error!("fn foo [ {}", cols(10, 10)); @@ -4115,6 +4278,7 @@ mod tests { assert_error!("fn foo -> {}", cols(11, 11)); assert_error!("fn foo {", cols(8, 8)); assert_error!("fn foo", cols(6, 6)); + assert_error!("fn extern foo[T](arg: T)", cols(14, 14)); } #[test] @@ -4158,6 +4322,28 @@ mod tests { ); } + #[test] + fn test_extern_class() { + assert_eq!( + top(parse("class extern A {}")), + TopLevelExpression::DefineClass(Box::new(DefineClass { + public: false, + name: Constant { + source: None, + name: "A".to_string(), + location: cols(14, 14) + }, + kind: ClassKind::Extern, + type_parameters: None, + body: ClassExpressions { + values: Vec::new(), + location: cols(16, 17) + }, + location: cols(1, 17) + })) + ); + } + #[test] fn test_class_literal() { assert_eq!( @@ -4681,6 +4867,8 @@ mod tests { assert_error!("class A { 10 }", cols(11, 12)); assert_error!("class {}", cols(7, 7)); assert_error!("class A {", cols(9, 9)); + assert_error!("class extern A[T] {", cols(15, 15)); + assert_error!("class extern A { fn foo { } }", cols(18, 19)); } #[test] diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index 7128ad322..9790e179a 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -15,7 +15,7 @@ use crate::type_check::define_types::{ InsertPrelude, }; use crate::type_check::expressions::{DefineConstants, Expressions}; -use crate::type_check::imports::DefineImportedTypes; +use crate::type_check::imports::{CollectExternImports, DefineImportedTypes}; use crate::type_check::methods::{ CheckMainMethod, DefineMethods, DefineModuleMethodNames, ImplementTraitMethods, @@ -165,6 +165,7 @@ impl Compiler { let state = &mut self.state; DefineTypes::run_all(state, modules) + && CollectExternImports::run_all(state, modules) && DefineModuleMethodNames::run_all(state, modules) && DefineImportedTypes::run_all(state, modules) && InsertPrelude::run_all(state, modules) @@ -246,8 +247,7 @@ impl Compiler { llvm::passes::Compile::run_all(&self.state, directories, mir) .map_err(CompileError::Internal)?; - link(&self.state.config, &exe, &objects) - .map_err(CompileError::Internal)?; + link(&self.state, &exe, &objects).map_err(CompileError::Internal)?; Ok(exe) } diff --git a/compiler/src/config.rs b/compiler/src/config.rs index bb6620b2b..1ed58e00e 100644 --- a/compiler/src/config.rs +++ b/compiler/src/config.rs @@ -156,6 +156,9 @@ pub struct Config { /// If MIR should be printed to DOT files. pub dot: bool, + + /// If C libraries should be linked statically or not. + pub static_linking: bool, } impl Config { @@ -177,6 +180,7 @@ impl Config { target: Target::native(), opt: Opt::Balanced, dot: false, + static_linking: false, } } diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index 26f079fa8..34c6c5731 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -111,7 +111,7 @@ impl Diagnostic { self.id } - pub(crate) fn message(&self) -> &String { + pub(crate) fn message(&self) -> &str { &self.message } @@ -209,7 +209,7 @@ impl Diagnostics { pub(crate) fn duplicate_symbol( &mut self, - name: &String, + name: &str, file: PathBuf, location: SourceLocation, ) { @@ -221,6 +221,47 @@ impl Diagnostics { ); } + pub(crate) fn duplicate_field( + &mut self, + name: &str, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::DuplicateSymbol, + format!("The field '{}' is already defined", name), + file, + location, + ); + } + + pub(crate) fn fields_not_allowed( + &mut self, + name: &str, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::DuplicateSymbol, + format!("Fields can't be defined for '{}'", name), + file, + location, + ); + } + + pub(crate) fn public_field_private_class( + &mut self, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::DuplicateSymbol, + "Public fields can't be defined for private classes", + file, + location, + ); + } + pub(crate) fn duplicate_type_parameter( &mut self, name: &str, @@ -237,7 +278,7 @@ impl Diagnostics { pub(crate) fn not_a_class( &mut self, - name: &String, + name: &str, file: PathBuf, location: SourceLocation, ) { @@ -251,7 +292,7 @@ impl Diagnostics { pub(crate) fn duplicate_method( &mut self, - method_name: &String, + method_name: &str, type_name: String, file: PathBuf, location: SourceLocation, @@ -832,6 +873,38 @@ impl Diagnostics { ); } + pub(crate) fn incorrect_number_of_type_arguments( + &mut self, + required: usize, + given: usize, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidType, + format!( + "Incorrect number of type arguments: expected {}, found {}", + required, given + ), + file, + location, + ); + } + + pub(crate) fn invalid_c_type( + &mut self, + name: &str, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidType, + format!("'{}' isn't a valid C type", name), + file, + location, + ); + } + pub(crate) fn iter(&self) -> impl Iterator { self.values.iter() } diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 07fca757d..a0994c611 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -183,6 +183,12 @@ pub(crate) struct Import { pub(crate) location: SourceLocation, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct ExternImport { + pub(crate) source: String, + pub(crate) location: SourceLocation, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct DefineConstant { pub(crate) public: bool, @@ -230,6 +236,16 @@ pub(crate) struct DefineModuleMethod { pub(crate) method_id: Option, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct DefineExternFunction { + pub(crate) public: bool, + pub(crate) name: Identifier, + pub(crate) arguments: Vec, + pub(crate) return_type: Option, + pub(crate) location: SourceLocation, + pub(crate) method_id: Option, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct DefineRequiredMethod { pub(crate) public: bool, @@ -304,6 +320,15 @@ pub(crate) struct DefineClass { pub(crate) location: SourceLocation, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct DefineExternClass { + pub(crate) public: bool, + pub(crate) class_id: Option, + pub(crate) name: Constant, + pub(crate) fields: Vec, + pub(crate) location: SourceLocation, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct DefineVariant { pub(crate) method_id: Option, @@ -351,12 +376,15 @@ pub(crate) struct DefineTrait { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum TopLevelExpression { Class(Box), + ExternClass(Box), Constant(Box), ModuleMethod(Box), + ExternFunction(Box), Trait(Box), Implement(Box), Import(Box), Reopen(Box), + ExternImport(Box), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -572,6 +600,14 @@ pub(crate) enum Argument { Named(Box), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct ExternTypeName { + pub(crate) resolved_type: types::TypeRef, + pub(crate) name: Constant, + pub(crate) arguments: Vec, + pub(crate) location: SourceLocation, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct TypeName { pub(crate) source: Option, @@ -1035,6 +1071,9 @@ impl<'a> LowerToHir<'a> { self.implement_trait(*node) } ast::TopLevelExpression::Import(node) => self.import(*node), + ast::TopLevelExpression::ExternImport(node) => { + self.extern_import(*node) + } }) .collect() } @@ -1063,17 +1102,28 @@ impl<'a> LowerToHir<'a> { ) -> TopLevelExpression { self.operator_method_not_allowed(node.operator, &node.location); - TopLevelExpression::ModuleMethod(Box::new(DefineModuleMethod { - public: node.public, - name: self.identifier(node.name), - type_parameters: self - .optional_type_parameters(node.type_parameters), - arguments: self.optional_method_arguments(node.arguments), - return_type: node.return_type.map(|n| self.type_reference(n)), - body: self.optional_expressions(node.body), - method_id: None, - location: node.location, - })) + if let ast::MethodKind::Extern = node.kind { + TopLevelExpression::ExternFunction(Box::new(DefineExternFunction { + public: node.public, + name: self.identifier(node.name), + arguments: self.optional_method_arguments(node.arguments), + return_type: node.return_type.map(|n| self.type_reference(n)), + method_id: None, + location: node.location, + })) + } else { + TopLevelExpression::ModuleMethod(Box::new(DefineModuleMethod { + public: node.public, + name: self.identifier(node.name), + type_parameters: self + .optional_type_parameters(node.type_parameters), + arguments: self.optional_method_arguments(node.arguments), + return_type: node.return_type.map(|n| self.type_reference(n)), + body: self.optional_expressions(node.body), + method_id: None, + location: node.location, + })) + } } fn optional_method_arguments( @@ -1096,6 +1146,10 @@ impl<'a> LowerToHir<'a> { } fn define_class(&mut self, node: ast::DefineClass) -> TopLevelExpression { + if let ast::ClassKind::Extern = node.kind { + return self.define_extern_class(node); + } + TopLevelExpression::Class(Box::new(DefineClass { public: node.public, class_id: None, @@ -1113,6 +1167,30 @@ impl<'a> LowerToHir<'a> { })) } + fn define_extern_class( + &mut self, + node: ast::DefineClass, + ) -> TopLevelExpression { + TopLevelExpression::ExternClass(Box::new(DefineExternClass { + public: node.public, + class_id: None, + name: self.constant(node.name), + fields: node + .body + .values + .into_iter() + .map(|expr| { + if let ast::ClassExpression::DefineField(n) = expr { + self.define_field(*n) + } else { + unreachable!() + } + }) + .collect(), + location: node.location, + })) + } + fn class_expressions( &mut self, node: ast::ClassExpressions, @@ -1124,7 +1202,7 @@ impl<'a> LowerToHir<'a> { self.define_method_in_class(*node) } ast::ClassExpression::DefineField(node) => { - self.define_field(*node) + ClassExpression::Field(Box::new(self.define_field(*node))) } ast::ClassExpression::DefineVariant(node) => { self.define_case(*node) @@ -1133,14 +1211,14 @@ impl<'a> LowerToHir<'a> { .collect() } - fn define_field(&self, node: ast::DefineField) -> ClassExpression { - ClassExpression::Field(Box::new(DefineField { + fn define_field(&self, node: ast::DefineField) -> DefineField { + DefineField { public: node.public, field_id: None, name: self.identifier(node.name), value_type: self.type_reference(node.value_type), location: node.location, - })) + } } fn define_case(&mut self, node: ast::DefineVariant) -> ClassExpression { @@ -1398,6 +1476,13 @@ impl<'a> LowerToHir<'a> { })) } + fn extern_import(&self, node: ast::ExternImport) -> TopLevelExpression { + TopLevelExpression::ExternImport(Box::new(ExternImport { + source: node.path.path, + location: node.location, + })) + } + fn import_module_path(&self, node: ast::ImportPath) -> Vec { node.steps.into_iter().map(|n| self.identifier(n)).collect() } @@ -3219,6 +3304,29 @@ mod tests { ); } + #[test] + fn test_lower_function() { + let (hir, diags) = lower_top_expr("fn extern foo"); + + assert_eq!(diags, 0); + assert_eq!( + hir, + TopLevelExpression::ExternFunction(Box::new( + DefineExternFunction { + public: false, + name: Identifier { + name: "foo".to_string(), + location: cols(11, 13) + }, + arguments: Vec::new(), + return_type: None, + method_id: None, + location: cols(1, 13), + } + )), + ); + } + #[test] fn test_lower_class() { let hir = lower_top_expr("class A[B: C] { let @a: B }").0; @@ -3273,6 +3381,43 @@ mod tests { ); } + #[test] + fn test_lower_extern_class() { + let hir = lower_top_expr("class extern A { let @a: B }").0; + + assert_eq!( + hir, + TopLevelExpression::ExternClass(Box::new(DefineExternClass { + public: false, + class_id: None, + name: Constant { + name: "A".to_string(), + location: cols(14, 14) + }, + fields: vec![DefineField { + public: false, + field_id: None, + name: Identifier { + name: "a".to_string(), + location: cols(22, 23) + }, + value_type: Type::Named(Box::new(TypeName { + source: None, + resolved_type: types::TypeRef::Unknown, + name: Constant { + name: "B".to_string(), + location: cols(26, 26) + }, + arguments: Vec::new(), + location: cols(26, 26) + })), + location: cols(18, 26), + }], + location: cols(1, 28) + })), + ); + } + #[test] fn test_lower_public_class() { let hir = lower_top_expr("class pub A {}").0; @@ -4288,6 +4433,19 @@ mod tests { ); } + #[test] + fn test_lower_extern_import() { + let hir = lower_top_expr("import extern 'a'").0; + + assert_eq!( + hir, + TopLevelExpression::ExternImport(Box::new(ExternImport { + source: "a".to_string(), + location: cols(1, 17) + })) + ); + } + #[test] fn test_lower_import_symbol() { let hir = lower_top_expr("import a::(b)").0; diff --git a/compiler/src/linker.rs b/compiler/src/linker.rs index 7490accc2..55c73a4c8 100644 --- a/compiler/src/linker.rs +++ b/compiler/src/linker.rs @@ -1,4 +1,5 @@ use crate::config::Config; +use crate::state::State; use crate::target::OperatingSystem; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -36,7 +37,7 @@ fn lld_is_available() -> bool { } pub(crate) fn link( - config: &Config, + state: &State, output: &Path, paths: &[PathBuf], ) -> Result<(), String> { @@ -56,21 +57,60 @@ pub(crate) fn link( cmd.arg(path); } - match config.target.os { + match state.config.target.os { OperatingSystem::Linux | OperatingSystem::Freebsd | OperatingSystem::Openbsd => { // macOS includes libm in the standard C library, so there's no need // to explicitly include it. + // + // We don't support static linking as libm is part of glibc, libc + // doesn't really support (proper) static linking, and you can't + // statically link libm _without_ statically linking libc. See + // https://bugzilla.redhat.com/show_bug.cgi?id=1433347 for some + // extra details. cmd.arg("-lm"); } _ => {} } + let mut static_linking = state.config.static_linking; + + if let OperatingSystem::Mac = state.config.target.os { + // On macOS there's no equivalent of -l:libX.a as there is for GNU + // platforms. We also don't have the logic (nor want to implement this) + // to find where the .a files are for each library linked against. + println!( + "Static linking isn't supported on macOS, \ + falling back to dynamic linking" + ); + + static_linking = false; + } + + if static_linking { + cmd.arg("-Wl,-Bstatic"); + } + + for lib in &state.libraries { + // These libraries are already included if needed, and we can't + // statically link against them (if static linking is desired), so we + // skip them here. + if lib == "m" || lib == "c" { + continue; + } + + cmd.arg(&(format!("-l{}", lib))); + } + + if static_linking { + cmd.arg("-Wl,-Bdynamic"); + } + cmd.arg("-o"); cmd.arg(output); - if let OperatingSystem::Linux = config.target.os { + if let OperatingSystem::Linux = state.config.target.os { // This removes the need for installing libgcc in deployment // environments. cmd.arg("-static-libgcc"); @@ -82,8 +122,8 @@ pub(crate) fn link( } } - let rt_path = runtime_library(config).ok_or_else(|| { - format!("No runtime is available for target '{}'", config.target) + let rt_path = runtime_library(&state.config).ok_or_else(|| { + format!("No runtime is available for target '{}'", state.config.target) })?; cmd.arg(&rt_path); diff --git a/compiler/src/llvm/builder.rs b/compiler/src/llvm/builder.rs index b86ae833b..533844afe 100644 --- a/compiler/src/llvm/builder.rs +++ b/compiler/src/llvm/builder.rs @@ -441,16 +441,45 @@ impl<'ctx> Builder<'ctx> { pub(crate) fn int_to_float( &self, value: IntValue<'ctx>, + size: u32, ) -> FloatValue<'ctx> { - let typ = self.context.f64_type(); + let typ = if size == 32 { + self.context.f32_type() + } else { + self.context.f64_type() + }; self.inner .build_cast(InstructionOpcode::SIToFP, value, typ, "") .into_float_value() } - pub(crate) fn int_to_int(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { - self.inner.build_int_cast(value, self.context.i64_type(), "") + pub(crate) fn int_to_int( + &self, + value: IntValue<'ctx>, + size: u32, + ) -> IntValue<'ctx> { + let target = match size { + 8 => self.context.i8_type(), + 16 => self.context.i16_type(), + 32 => self.context.i32_type(), + _ => self.context.i64_type(), + }; + + self.inner.build_int_cast(value, target, "") + } + + pub(crate) fn float_to_float( + &self, + value: FloatValue<'ctx>, + size: u32, + ) -> FloatValue<'ctx> { + let target = match size { + 32 => self.context.f32_type(), + _ => self.context.f64_type(), + }; + + self.inner.build_float_cast(value, target, "") } pub(crate) fn int_to_pointer( diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index 886016647..604cb769a 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -1,13 +1,15 @@ +use crate::llvm::layouts::Layouts; use inkwell::basic_block::BasicBlock; use inkwell::builder::Builder; use inkwell::module::Module; use inkwell::types::{ - ArrayType, BasicTypeEnum, FloatType, IntType, PointerType, StructType, - VoidType, + ArrayType, BasicType, BasicTypeEnum, FloatType, IntType, PointerType, + StructType, VoidType, }; use inkwell::values::FunctionValue; use inkwell::{context, AddressSpace}; use std::mem::size_of; +use types::{Database, ForeignType, TypeId, TypeRef}; /// A wrapper around an LLVM Context that provides some additional methods. pub(crate) struct Context { @@ -43,6 +45,10 @@ impl Context { self.inner.i64_type() } + pub(crate) fn f32_type(&self) -> FloatType { + self.inner.f32_type() + } + pub(crate) fn f64_type(&self) -> FloatType { self.inner.f64_type() } @@ -127,6 +133,59 @@ impl Context { pub(crate) fn create_module(&self, name: &str) -> Module { self.inner.create_module(name) } + + pub(crate) fn foreign_type<'a>( + &'a self, + db: &Database, + layouts: &Layouts<'a>, + type_ref: TypeRef, + ) -> Option> { + let space = AddressSpace::default(); + + match type_ref { + TypeRef::Owned(id) => self + .foreign_base_type(db, layouts, id) + .map(|v| v.as_basic_type_enum()), + TypeRef::Pointer(id) => self + .foreign_base_type(db, layouts, id) + .map(|v| v.ptr_type(space).as_basic_type_enum()), + _ => None, + } + } + + fn foreign_base_type<'a>( + &'a self, + db: &Database, + layouts: &Layouts<'a>, + id: TypeId, + ) -> Option> { + match id { + TypeId::Foreign(ForeignType::Int(size)) => { + let typ = match size { + 8 => self.i8_type(), + 16 => self.i16_type(), + 32 => self.i32_type(), + _ => self.i64_type(), + }; + + Some(typ.as_basic_type_enum()) + } + TypeId::Foreign(ForeignType::Float(size)) => { + let typ = match size { + 32 => self.f32_type(), + _ => self.f64_type(), + }; + + Some(typ.as_basic_type_enum()) + } + TypeId::ClassInstance(ins) + if ins.instance_of().kind(db).is_extern() => + { + Some(layouts.instances[&ins.instance_of()].as_basic_type_enum()) + } + _ => None, + } + } } #[cfg(test)] diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs index cdbcb9c3e..2f136894e 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -5,15 +5,15 @@ use crate::mir::Mir; use crate::state::State; use crate::target::OperatingSystem; use inkwell::types::{ - BasicMetadataTypeEnum, BasicTypeEnum, FunctionType, StructType, + BasicMetadataTypeEnum, BasicType, FunctionType, StructType, }; use inkwell::AddressSpace; use std::cmp::max; use std::collections::HashMap; use types::{ - ClassId, MethodId, MethodSource, ARRAY_ID, BOOLEAN_ID, BYTE_ARRAY_ID, - CALL_METHOD, CHANNEL_ID, DROPPER_METHOD, FLOAT_ID, INT_ID, NIL_ID, - STRING_ID, + Block, ClassId, MethodId, MethodSource, ARRAY_ID, BOOLEAN_ID, + BYTE_ARRAY_ID, CALL_METHOD, CHANNEL_ID, DROPPER_METHOD, FLOAT_ID, INT_ID, + NIL_ID, STRING_ID, }; /// The size of an object header. @@ -152,6 +152,7 @@ impl<'ctx> Layouts<'ctx> { context.pointer_type().into(), // Arguments pointer ]); + // TODO: remove in favour of using the FFI let result_layout = context.struct_type(&[ context.pointer_type().into(), // Tag context.pointer_type().into(), // Value @@ -217,6 +218,11 @@ impl<'ctx> Layouts<'ctx> { } } + let mut method_table_sizes = Vec::with_capacity(mir.classes.len()); + + // We generate the bare structs first, that way method signatures can + // refer to them, regardless of the order in which methods/classes are + // defined. for (id, mir_class) in &mir.classes { // We size classes larger than actually needed in an attempt to // reduce collisions when performing dynamic dispatch. @@ -224,6 +230,9 @@ impl<'ctx> Layouts<'ctx> { round_methods(mir_class.methods.len()) * METHOD_TABLE_FACTOR, METHOD_TABLE_MIN_SIZE, ); + + method_table_sizes.push(methods_len); + let name = format!("{}::{}", id.module(db).name(db).as_str(), id.name(db)); let class = context.class_type( @@ -276,6 +285,29 @@ impl<'ctx> Layouts<'ctx> { } }; + class_layouts.insert(*id, class); + instance_layouts.insert(*id, instance); + } + + let mut layouts = Self { + empty_class: context.class_type(0, "", method), + method, + classes: class_layouts, + instances: instance_layouts, + state: state_layout, + header, + result: result_layout, + context: context_layout, + method_counts: method_counts_layout, + methods, + message: message_layout, + }; + + // Now that all the LLVM structs are defined, we can process all + // methods. + for (mir_class, methods_len) in + mir.classes.values().zip(method_table_sizes.into_iter()) + { let mut buckets = vec![false; methods_len]; let max_bucket = methods_len.saturating_sub(1); @@ -330,8 +362,11 @@ impl<'ctx> Layouts<'ctx> { // the trait, not the implementation defined for the // class. This is because when we generate the dynamic // dispatch code, we only know about the trait method. - methods.get_mut(&orig).unwrap().collision = true; - methods + layouts.methods.get_mut(&orig).unwrap().collision = + true; + + layouts + .methods .get_mut(&orig) .unwrap() .colliding @@ -350,18 +385,34 @@ impl<'ctx> Layouts<'ctx> { context.pointer_type().into(), // Process ]; + // For instance methods, the receiver is passed as an + // explicit argument before any user-defined arguments. if method.is_instance_method(db) { args.push(context.pointer_type().into()); } - for _ in 0..method.number_of_arguments(db) { - args.push(context.pointer_type().into()); + for arg in method.arguments(db) { + let typ = context + .foreign_type(db, &layouts, arg.value_type) + .unwrap_or_else(|| { + context.pointer_type().as_basic_type_enum() + }); + + args.push(typ.into()); } - context.pointer_type().fn_type(&args, false) + // Unlike extern methods (which we handle below), Inko + // methods always return a value, so we don't need to fall + // back to returning "void" here. + context + .foreign_type(db, &layouts, method.return_type(db)) + .unwrap_or_else(|| { + context.pointer_type().as_basic_type_enum() + }) + .fn_type(&args, false) }; - methods.insert( + layouts.methods.insert( method, MethodInfo { index: index as u16, @@ -372,9 +423,46 @@ impl<'ctx> Layouts<'ctx> { }, ); } + } - class_layouts.insert(*id, class); - instance_layouts.insert(*id, instance); + for mod_id in mir.modules.keys() { + for &method in mod_id.extern_methods(db) { + let args: Vec = method + .arguments(db) + .into_iter() + .map(|arg| { + context + .foreign_type(db, &layouts, arg.value_type) + .unwrap_or_else(|| { + context.pointer_type().as_basic_type_enum() + }) + .into() + }) + .collect(); + + let ret = method.return_type(db); + let typ = context + .foreign_type(db, &layouts, ret) + .map(|t| t.fn_type(&args, false)) + .unwrap_or_else(|| { + if method.returns_value(db) { + context.pointer_type().fn_type(&args, false) + } else { + context.void_type().fn_type(&args, false) + } + }); + + layouts.methods.insert( + method, + MethodInfo { + index: 0, + hash: 0, + signature: typ, + collision: false, + colliding: Vec::new(), + }, + ); + } } let process_size = @@ -392,42 +480,51 @@ impl<'ctx> Layouts<'ctx> { continue; } - let layout = instance_layouts[id]; - let mut fields: Vec = vec![header.into()]; + let layout = layouts.instances[id]; + let kind = id.kind(db); + let mut fields = Vec::new(); - // For processes we need to take into account the space between the - // header and the first field. We don't actually care about that - // state in the generated code, so we just insert a single member - // that covers it. - if id.kind(db).is_async() { - fields.push( - context - .i8_type() - .array_type(process_size - HEADER_SIZE) - .into(), - ); - } + if kind.is_extern() { + for field in id.fields(db) { + let typ = context + .foreign_type(db, &layouts, field.value_type(db)) + .unwrap_or_else(|| { + context.pointer_type().as_basic_type_enum() + }); + + fields.push(typ); + } + } else { + fields.push(header.into()); + + // For processes we need to take into account the space between + // the header and the first field. We don't actually care about + // that state in the generated code, so we just insert a single + // member that covers it. + if kind.is_async() { + fields.push( + context + .i8_type() + .array_type(process_size - HEADER_SIZE) + .into(), + ); + } - for _ in 0..id.number_of_fields(db) { - fields.push(context.pointer_type().into()); + for field in id.fields(db) { + let typ = context + .foreign_type(db, &layouts, field.value_type(db)) + .unwrap_or_else(|| { + context.pointer_type().as_basic_type_enum() + }); + + fields.push(typ); + } } layout.set_body(&fields, false); } - Self { - empty_class: context.class_type(0, "", method), - method, - classes: class_layouts, - instances: instance_layouts, - state: state_layout, - header, - result: result_layout, - context: context_layout, - method_counts: method_counts_layout, - methods, - message: message_layout, - } + layouts } pub(crate) fn methods(&self, class: ClassId) -> u32 { diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index c57009973..d2bea7f23 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -17,7 +17,8 @@ use crate::llvm::layouts::Layouts; use crate::llvm::module::Module; use crate::llvm::runtime_function::RuntimeFunction; use crate::mir::{ - CloneKind, Constant, Instruction, LocationId, Method, Mir, RegisterId, + CastType, CloneKind, Constant, Instruction, LocationId, Method, Mir, + RegisterId, }; use crate::state::State; use crate::symbol_names::SymbolNames; @@ -41,7 +42,7 @@ use std::path::Path; use std::path::PathBuf; use std::rc::Rc; use types::module_name::ModuleName; -use types::{BuiltinFunction, ClassId, Database}; +use types::{BuiltinFunction, ClassId, Database, TypeRef}; /// A compiler pass that compiles Inko MIR into object files using LLVM. pub(crate) struct Compile<'a, 'b, 'ctx> { @@ -215,11 +216,6 @@ impl<'a, 'b, 'ctx> Compile<'a, 'b, 'ctx> { for &class_id in &self.mir.modules[self.module_index].classes { let raw_name = class_id.name(self.db); let name_ptr = builder.string_literal(raw_name).0.into(); - let fields_len = self - .context - .i8_type() - .const_int(class_id.number_of_fields(self.db) as _, false) - .into(); let methods_len = self .context .i16_type() @@ -256,8 +252,10 @@ impl<'a, 'b, 'ctx> Compile<'a, 'b, 'ctx> { .load_field(self.layouts.state, state, class_id.0 + 3) .into_pointer_value() } else { + let size = self.layouts.instances[&class_id].size_of().unwrap(); + builder - .call(class_new, &[name_ptr, fields_len, methods_len]) + .call(class_new, &[name_ptr, size.into(), methods_len]) .into_pointer_value() }; @@ -1026,24 +1024,6 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatToInt => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let val = self.read_float(val_var).into(); - let func = self.module.intrinsic( - "llvm.fptosi.sat", - &[ - self.builder.context.i64_type().into(), - self.builder.context.f64_type().into(), - ], - ); - - let raw = - self.builder.call(func, &[val]).into_int_value(); - let res = self.new_int(state_var, raw); - - self.builder.store(reg_var, res); - } BuiltinFunction::FloatToString => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; @@ -1059,90 +1039,6 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::ArrayCapacity => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let val = self.builder.load_untyped_pointer(val_var); - let array = self.builder.untagged(val).into(); - let func_name = RuntimeFunction::ArrayCapacity; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArrayClear => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let val = self.builder.load_untyped_pointer(val_var); - let array = self.builder.untagged(val).into(); - let func_name = RuntimeFunction::ArrayClear; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArrayDrop => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let val = self.builder.load_untyped_pointer(val_var); - let array = self.builder.untagged(val).into(); - let func_name = RuntimeFunction::ArrayDrop; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArrayGet => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let idx_var = self.variables[&ins.arguments[1]]; - let val = self.builder.load_untyped_pointer(val_var); - let array = self.builder.untagged(val).into(); - let index = self.read_int(idx_var).into(); - let func_name = RuntimeFunction::ArrayGet; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[array, index]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArrayLength => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let val = self.builder.load_untyped_pointer(val_var); - let array = self.builder.untagged(val).into(); - let func_name = RuntimeFunction::ArrayLength; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArrayPop => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let val = self.builder.load_untyped_pointer(val_var); - let array = self.builder.untagged(val).into(); - let func_name = RuntimeFunction::ArrayPop; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[array]); - - self.builder.store(reg_var, res); - } BuiltinFunction::ArrayPush => { let reg_var = self.variables[&ins.register]; let array_var = self.variables[&ins.arguments[0]]; @@ -1163,425 +1059,6 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::ArrayRemove => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let idx_var = self.variables[&ins.arguments[1]]; - let val = self.builder.load_untyped_pointer(array_var); - let array = self.builder.untagged(val).into(); - let idx = self.read_int(idx_var).into(); - let func_name = RuntimeFunction::ArrayRemove; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[array, idx]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArrayReserve => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let amount_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let val = self.builder.load_untyped_pointer(array_var); - let array = self.builder.untagged(val).into(); - let amount = self.read_int(amount_var).into(); - let func_name = RuntimeFunction::ArrayReserve; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, array, amount]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ArraySet => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let index_var = self.variables[&ins.arguments[1]]; - let value_var = self.variables[&ins.arguments[2]]; - let tagged = - self.builder.load_untyped_pointer(array_var); - let array = self.builder.untagged(tagged).into(); - let index = self.read_int(index_var).into(); - let value = - self.builder.load_untyped_pointer(value_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::ArraySet); - let res = - self.builder.call(func, &[array, index, value]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayNew => { - let reg_var = self.variables[&ins.register]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayNew); - let res = self.builder.call(func, &[state]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayAppend => { - let reg_var = self.variables[&ins.register]; - let target_var = self.variables[&ins.arguments[0]]; - let source_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let target = self - .builder - .untagged( - self.builder.load_untyped_pointer(target_var), - ) - .into(); - let source = self - .builder - .untagged( - self.builder.load_untyped_pointer(source_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayAppend); - let res = - self.builder.call(func, &[state, target, source]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayClear => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayClear); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayClone => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayClone); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayCopyFrom => { - let reg_var = self.variables[&ins.register]; - let target_var = self.variables[&ins.arguments[0]]; - let source_var = self.variables[&ins.arguments[1]]; - let start_var = self.variables[&ins.arguments[2]]; - let length_var = self.variables[&ins.arguments[3]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let target = self - .builder - .untagged( - self.builder.load_untyped_pointer(target_var), - ) - .into(); - let source = self - .builder - .untagged( - self.builder.load_untyped_pointer(source_var), - ) - .into(); - let start = self.read_int(start_var).into(); - let length = self.read_int(length_var).into(); - let func = self.module.runtime_function( - RuntimeFunction::ByteArrayCopyFrom, - ); - let res = self.builder.call( - func, - &[state, target, source, start, length], - ); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayDrainToString => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self.module.runtime_function( - RuntimeFunction::ByteArrayDrainToString, - ); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayToString => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self.module.runtime_function( - RuntimeFunction::ByteArrayToString, - ); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayDrop => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayDrop); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayEq => { - let reg_var = self.variables[&ins.register]; - let lhs_var = self.variables[&ins.arguments[0]]; - let rhs_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let lhs = self - .builder - .untagged( - self.builder.load_untyped_pointer(lhs_var), - ) - .into(); - let rhs = self - .builder - .untagged( - self.builder.load_untyped_pointer(rhs_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayEq); - let res = self.builder.call(func, &[state, lhs, rhs]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayGet => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let index_var = self.variables[&ins.arguments[1]]; - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let index = self.read_int(index_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayGet); - let res = self.builder.call(func, &[array, index]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayLength => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayLength); - let res = self.builder.call(func, &[state, array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayPop => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayPop); - let res = self.builder.call(func, &[array]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayPush => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let value_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let value = self.read_int(value_var).into(); - let func_name = RuntimeFunction::ByteArrayPush; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, array, value]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayRemove => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let index_var = self.variables[&ins.arguments[1]]; - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let index = self.read_int(index_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayRemove); - let res = self.builder.call(func, &[array, index]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArrayResize => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let size_var = self.variables[&ins.arguments[1]]; - let fill_var = self.variables[&ins.arguments[2]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let size = self.read_int(size_var).into(); - let fill = self.read_int(fill_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArrayResize); - let res = self - .builder - .call(func, &[state, array, size, fill]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArraySet => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let index_var = self.variables[&ins.arguments[1]]; - let value_var = self.variables[&ins.arguments[2]]; - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let index = self.read_int(index_var).into(); - let value = self.read_int(value_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArraySet); - let res = - self.builder.call(func, &[array, index, value]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ByteArraySlice => { - let reg_var = self.variables[&ins.register]; - let array_var = self.variables[&ins.arguments[0]]; - let start_var = self.variables[&ins.arguments[1]]; - let length_var = self.variables[&ins.arguments[2]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let array = self - .builder - .untagged( - self.builder.load_untyped_pointer(array_var), - ) - .into(); - let start = self.read_int(start_var).into(); - let length = self.read_int(length_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::ByteArraySlice); - let res = self - .builder - .call(func, &[state, array, start, length]); - - self.builder.store(reg_var, res); - } BuiltinFunction::ChildProcessSpawn => { let reg_var = self.variables[&ins.register]; let program_var = self.variables[&ins.arguments[0]]; @@ -2184,175 +1661,56 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { let proc = self.builder.load_untyped_pointer(proc_var).into(); let path = - self.builder.load_untyped_pointer(path_var).into(); - let func_name = RuntimeFunction::FileSize; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, proc, path]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::FileWriteBytes => { - let reg_var = self.variables[&ins.register]; - let file_var = self.variables[&ins.arguments[0]]; - let buf_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let file = - self.builder.load_untyped_pointer(file_var).into(); - let buf = self - .builder - .untagged( - self.builder.load_untyped_pointer(buf_var), - ) - .into(); - let func_name = RuntimeFunction::FileWriteBytes; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, proc, file, buf]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::FileWriteString => { - let reg_var = self.variables[&ins.register]; - let file_var = self.variables[&ins.arguments[0]]; - let buf_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let file = - self.builder.load_untyped_pointer(file_var).into(); - let buf = - self.builder.load_untyped_pointer(buf_var).into(); - let func_name = RuntimeFunction::FileWriteString; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, proc, file, buf]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ChannelReceive => { - let reg_var = self.variables[&ins.register]; - let chan_var = self.variables[&ins.arguments[0]]; - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let chan = - self.builder.load_untyped_pointer(chan_var).into(); - let func_name = RuntimeFunction::ChannelReceive; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[proc, chan]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ChannelReceiveUntil => { - let reg_var = self.variables[&ins.register]; - let chan_var = self.variables[&ins.arguments[0]]; - let time_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let chan = - self.builder.load_untyped_pointer(chan_var).into(); - let time = self.read_int(time_var).into(); - let func_name = RuntimeFunction::ChannelReceiveUntil; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, proc, chan, time]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ChannelDrop => { - let reg_var = self.variables[&ins.register]; - let chan_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let chan = - self.builder.load_untyped_pointer(chan_var).into(); - let func_name = RuntimeFunction::ChannelDrop; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, chan]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ChannelWait => { - let reg_var = self.variables[&ins.register]; - let chans_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var); - let proc = self.builder.load_untyped_pointer(proc_var); - - // The standard library uses a reference in the wait() - // method, so we need to clear the reference bit before - // using the pointer. - let chans = self.builder.untagged( - self.builder.load_untyped_pointer(chans_var), - ); - let func_name = RuntimeFunction::ChannelWait; - let func = self.module.runtime_function(func_name); - let res = self.builder.call( - func, - &[state.into(), proc.into(), chans.into()], - ); - - self.builder.store(reg_var, res); - } - BuiltinFunction::ChannelNew => { - let reg_var = self.variables[&ins.register]; - let cap_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let cap = self.read_int(cap_var).into(); - let func_name = RuntimeFunction::ChannelNew; + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::FileSize; let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, cap]); + let res = self.builder.call(func, &[state, proc, path]); self.builder.store(reg_var, res); } - BuiltinFunction::ChannelSend => { + BuiltinFunction::FileWriteBytes => { let reg_var = self.variables[&ins.register]; - let chan_var = self.variables[&ins.arguments[0]]; - let msg_var = self.variables[&ins.arguments[1]]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); let proc = self.builder.load_untyped_pointer(proc_var).into(); - let chan = - self.builder.load_untyped_pointer(chan_var).into(); - let msg = - self.builder.load_untyped_pointer(msg_var).into(); - let func_name = RuntimeFunction::ChannelSend; + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func_name = RuntimeFunction::FileWriteBytes; let func = self.module.runtime_function(func_name); let res = - self.builder.call(func, &[state, proc, chan, msg]); + self.builder.call(func, &[state, proc, file, buf]); self.builder.store(reg_var, res); } - BuiltinFunction::ChannelTryReceive => { + BuiltinFunction::FileWriteString => { let reg_var = self.variables[&ins.register]; - let chan_var = self.variables[&ins.arguments[0]]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); let proc = self.builder.load_untyped_pointer(proc_var).into(); - let chan = - self.builder.load_untyped_pointer(chan_var).into(); - let func_name = RuntimeFunction::ChannelTryReceive; + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let func_name = RuntimeFunction::FileWriteString; let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[proc, chan]); + let res = + self.builder.call(func, &[state, proc, file, buf]); self.builder.store(reg_var, res); } @@ -2434,15 +1792,6 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntToFloat => { - let reg_var = self.variables[&ins.register]; - let val_var = self.variables[&ins.arguments[0]]; - let val = self.read_int(val_var); - let raw = self.builder.int_to_float(val); - let res = self.new_float(state_var, raw); - - self.builder.store(reg_var, res); - } BuiltinFunction::IntToString => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; @@ -3473,396 +2822,164 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(reg_var, res); } BuiltinFunction::StderrWriteBytes => { - let reg_var = self.variables[&ins.register]; - let input_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let input = self - .builder - .untagged( - self.builder.load_untyped_pointer(input_var), - ) - .into(); - let func = self.module.runtime_function( - RuntimeFunction::StderrWriteBytes, - ); - - let ret = - self.builder.call(func, &[state, proc, input]); - - self.builder.store(reg_var, ret); - } - BuiltinFunction::StderrWriteString => { - let reg_var = self.variables[&ins.register]; - let input_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let input = - self.builder.load_untyped_pointer(input_var).into(); - let func = self.module.runtime_function( - RuntimeFunction::StderrWriteString, - ); - - let ret = - self.builder.call(func, &[state, proc, input]); - - self.builder.store(reg_var, ret); - } - BuiltinFunction::StdinRead => { - let reg_var = self.variables[&ins.register]; - let buf_var = self.variables[&ins.arguments[0]]; - let size_var = self.variables[&ins.arguments[1]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let buf = self - .builder - .untagged( - self.builder.load_untyped_pointer(buf_var), - ) - .into(); - let size = self.read_int(size_var).into(); - let func_name = RuntimeFunction::StdinRead; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, proc, buf, size]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StdoutFlush => { - let reg_var = self.variables[&ins.register]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let func_name = RuntimeFunction::StdoutFlush; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, proc]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StdoutWriteBytes => { - let reg_var = self.variables[&ins.register]; - let buf_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let buf = self - .builder - .untagged( - self.builder.load_untyped_pointer(buf_var), - ) - .into(); - let func_name = RuntimeFunction::StdoutWriteBytes; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, proc, buf]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StdoutWriteString => { - let reg_var = self.variables[&ins.register]; - let input_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let proc = - self.builder.load_untyped_pointer(proc_var).into(); - let input = - self.builder.load_untyped_pointer(input_var).into(); - let func_name = RuntimeFunction::StdoutWriteString; - let func = self.module.runtime_function(func_name); - let res = - self.builder.call(func, &[state, proc, input]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringByte => { - let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; - let index_var = self.variables[&ins.arguments[1]]; - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let index = self.read_int(index_var).into(); - let func_name = RuntimeFunction::StringByte; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[string, index]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringCharacters => { - let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let func_name = RuntimeFunction::StringCharacters; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[string]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringCharactersDrop => { - let reg_var = self.variables[&ins.register]; - let iter_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let iter = - self.builder.load_untyped_pointer(iter_var).into(); - let func_name = RuntimeFunction::StringCharactersDrop; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, iter]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringCharactersNext => { - let reg_var = self.variables[&ins.register]; - let iter_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let iter = - self.builder.load_untyped_pointer(iter_var).into(); - let func_name = RuntimeFunction::StringCharactersNext; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, iter]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringConcat => { - let reg_var = self.variables[&ins.register]; - let len = - self.builder.i64_literal(ins.arguments.len() as _); - let temp_type = self - .builder - .context - .pointer_type() - .array_type(ins.arguments.len() as _); - let temp_var = self.builder.new_stack_slot(temp_type); - - for (idx, reg) in ins.arguments.iter().enumerate() { - let val = self - .builder - .load_untyped_pointer(self.variables[reg]); - - self.builder.store_array_field( - temp_type, temp_var, idx as _, val, - ); - } - - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let func_name = RuntimeFunction::StringConcat; - let func = self.module.runtime_function(func_name); - let res = self - .builder - .call(func, &[state, temp_var.into(), len.into()]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringConcatArray => { - let reg_var = self.variables[&ins.register]; - let ary_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let ary = - self.builder.load_untyped_pointer(ary_var).into(); - let func_name = RuntimeFunction::StringConcatArray; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, ary]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringDrop => { - let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; - let state = self - .builder - .load_pointer(self.layouts.state, state_var) - .into(); - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let func_name = RuntimeFunction::StringDrop; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, string]); - - self.builder.store(reg_var, res); - } - BuiltinFunction::StringEq => { - let reg_var = self.variables[&ins.register]; - let lhs_var = self.variables[&ins.arguments[0]]; - let rhs_var = self.variables[&ins.arguments[1]]; + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); - let lhs = - self.builder.load_untyped_pointer(lhs_var).into(); - let rhs = - self.builder.load_untyped_pointer(rhs_var).into(); - let func = self - .module - .runtime_function(RuntimeFunction::StringEquals); - let ret = self.builder.call(func, &[state, lhs, rhs]); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = self + .builder + .untagged( + self.builder.load_untyped_pointer(input_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::StderrWriteBytes, + ); + + let ret = + self.builder.call(func, &[state, proc, input]); self.builder.store(reg_var, ret); } - BuiltinFunction::StringSize => { + BuiltinFunction::StderrWriteString => { let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; + let input_var = self.variables[&ins.arguments[0]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let func_name = RuntimeFunction::StringSize; - let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, string]); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::StderrWriteString, + ); - self.builder.store(reg_var, res); + let ret = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, ret); } - BuiltinFunction::StringSliceBytes => { + BuiltinFunction::StdinRead => { let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; - let start_var = self.variables[&ins.arguments[1]]; - let len_var = self.variables[&ins.arguments[2]]; + let buf_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); - let string = self + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let buf = self .builder - .load_untyped_pointer(string_var) + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) .into(); - let start = self.read_int(start_var).into(); - let len = self.read_int(len_var).into(); - let func_name = RuntimeFunction::StringSliceBytes; + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::StdinRead; let func = self.module.runtime_function(func_name); - let res = self - .builder - .call(func, &[state, string, start, len]); + let res = + self.builder.call(func, &[state, proc, buf, size]); self.builder.store(reg_var, res); } - BuiltinFunction::StringToByteArray => { + BuiltinFunction::StdoutFlush => { let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let func_name = RuntimeFunction::StringToByteArray; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::StdoutFlush; let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, string]); + let res = self.builder.call(func, &[state, proc]); self.builder.store(reg_var, res); } - BuiltinFunction::StringToFloat => { + BuiltinFunction::StdoutWriteBytes => { let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; - let start_var = self.variables[&ins.arguments[1]]; - let end_var = self.variables[&ins.arguments[2]]; + let buf_var = self.variables[&ins.arguments[0]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); - let string = self + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let buf = self .builder - .load_untyped_pointer(string_var) + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) .into(); - let start = self.read_int(start_var).into(); - let end = self.read_int(end_var).into(); - let func_name = RuntimeFunction::StringToFloat; + let func_name = RuntimeFunction::StdoutWriteBytes; let func = self.module.runtime_function(func_name); - let res = self - .builder - .call(func, &[state, string, start, end]); + let res = self.builder.call(func, &[state, proc, buf]); self.builder.store(reg_var, res); } - BuiltinFunction::StringToInt => { + BuiltinFunction::StdoutWriteString => { let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; - let radix_var = self.variables[&ins.arguments[1]]; - let start_var = self.variables[&ins.arguments[2]]; - let end_var = self.variables[&ins.arguments[3]]; + let input_var = self.variables[&ins.arguments[0]]; let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); let proc = self.builder.load_untyped_pointer(proc_var).into(); - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let radix = self.read_int(radix_var).into(); - let start = self.read_int(start_var).into(); - let end = self.read_int(end_var).into(); - let func_name = RuntimeFunction::StringToInt; + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func_name = RuntimeFunction::StdoutWriteString; let func = self.module.runtime_function(func_name); - let res = self.builder.call( - func, - &[state, proc, string, radix, start, end], - ); + let res = + self.builder.call(func, &[state, proc, input]); self.builder.store(reg_var, res); } - BuiltinFunction::StringToLower => { + BuiltinFunction::StringConcat => { let reg_var = self.variables[&ins.register]; - let string_var = self.variables[&ins.arguments[0]]; + let len = + self.builder.i64_literal(ins.arguments.len() as _); + let temp_type = self + .builder + .context + .pointer_type() + .array_type(ins.arguments.len() as _); + let temp_var = self.builder.new_stack_slot(temp_type); + + for (idx, reg) in ins.arguments.iter().enumerate() { + let val = self + .builder + .load_untyped_pointer(self.variables[reg]); + + self.builder.store_array_field( + temp_type, temp_var, idx as _, val, + ); + } + let state = self .builder .load_pointer(self.layouts.state, state_var) .into(); - let string = self - .builder - .load_untyped_pointer(string_var) - .into(); - let func_name = RuntimeFunction::StringToLower; + let func_name = RuntimeFunction::StringConcat; let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, string]); + let res = self + .builder + .call(func, &[state, temp_var.into(), len.into()]); self.builder.store(reg_var, res); } - BuiltinFunction::StringToUpper => { + BuiltinFunction::StringToFloat => { let reg_var = self.variables[&ins.register]; let string_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let end_var = self.variables[&ins.arguments[2]]; let state = self .builder .load_pointer(self.layouts.state, state_var) @@ -3871,9 +2988,13 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { .builder .load_untyped_pointer(string_var) .into(); - let func_name = RuntimeFunction::StringToUpper; + let start = self.read_int(start_var).into(); + let end = self.read_int(end_var).into(); + let func_name = RuntimeFunction::StringToFloat; let func = self.module.runtime_function(func_name); - let res = self.builder.call(func, &[state, string]); + let res = self + .builder + .call(func, &[state, string, start, end]); self.builder.store(reg_var, res); } @@ -3931,6 +3052,20 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(reg_var, res); } + BuiltinFunction::State => { + let reg_var = self.variables[&ins.register]; + let typ = self.layouts.state; + let state = self.builder.load_pointer(typ, state_var); + + self.builder.store(reg_var, state); + } + BuiltinFunction::Process => { + let reg_var = self.variables[&ins.register]; + let typ = self.layouts.state; + let state = self.builder.load_pointer(typ, proc_var); + + self.builder.store(reg_var, state); + } BuiltinFunction::Moved => unreachable!(), } } @@ -4118,6 +3253,48 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.store(target, self.builder.load(typ, source)); } + Instruction::CallExtern(ins) => { + self.set_debug_location(ins.location); + + let func_name = ins.method.name(self.db); + let func = self.module.add_method(func_name, ins.method); + let args: Vec = ins + .arguments + .iter() + .map(|®| { + let reg_typ = self.register_type(reg); + let arg_var = self.variables[®]; + + // References and tagged integers are passed as their + // raw versions (i.e. without a tag bit). This makes it + // a little easier to pass such data to the runtime + // functions. + if reg_typ.is_ref_or_mut(self.db) { + let arg = self + .builder + .load(self.variable_types[®], arg_var); + + self.builder + .untagged(arg.into_pointer_value()) + .into() + } else if reg_typ.is_int(self.db) { + self.read_int(arg_var).into() + } else { + self.builder + .load(self.variable_types[®], arg_var) + .into() + } + }) + .collect(); + + if ins.method.returns_value(self.db) { + let var = self.variables[&ins.register]; + + self.builder.store(var, self.builder.call(func, &args)); + } else { + self.builder.call_void(func, &args); + } + } Instruction::CallStatic(ins) => { self.set_debug_location(ins.location); @@ -4196,6 +3373,7 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { .into_int_value(), self.builder.u16_literal(1), ), + 64, ); let hash = self.builder.u64_literal(info.hash); @@ -4470,11 +3648,22 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { { let reg_var = self.variables[&ins.register]; let rec_var = self.variables[&ins.receiver]; + let rec_typ = self.variable_types[&ins.receiver]; let layout = self.layouts.instances[&ins.class]; let index = ins.field.index(self.db) as u32; - let rec = - self.builder.load(layout, rec_var).into_struct_value(); - let field = self.builder.extract_field(rec, index); + let field = if rec_typ.is_pointer_type() { + let rec = self + .builder + .load(rec_typ, rec_var) + .into_pointer_value(); + + self.builder.load_field(layout, rec, index) + } else { + let rec = + self.builder.load(rec_typ, rec_var).into_struct_value(); + + self.builder.extract_field(rec, index) + }; self.builder.store(reg_var, field); } @@ -4482,12 +3671,23 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { if ins.class.kind(self.db).is_extern() => { let rec_var = self.variables[&ins.receiver]; + let rec_typ = self.variable_types[&ins.receiver]; let val_var = self.variables[&ins.value]; let layout = self.layouts.instances[&ins.class]; let index = ins.field.index(self.db) as u32; - let val = self.builder.load_untyped_pointer(val_var); + let val_typ = self.variable_types[&ins.value]; + let val = self.builder.load(val_typ, val_var); + + if rec_typ.is_pointer_type() { + let rec = self + .builder + .load(rec_typ, rec_var) + .into_pointer_value(); - self.builder.store_field(layout, rec_var, index, val); + self.builder.store_field(layout, rec, index, val); + } else { + self.builder.store_field(layout, rec_var, index, val); + } } Instruction::GetField(ins) => { let reg_var = self.variables[&ins.register]; @@ -4760,6 +3960,170 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.call_void(func, &[proc, terminate]); self.builder.unreachable(); } + Instruction::Cast(ins) => { + let reg_var = self.variables[&ins.register]; + let src_var = self.variables[&ins.source]; + let src_typ = self.variable_types[&ins.source]; + let res = match (ins.from, ins.to) { + (CastType::Int(_), CastType::Int(size)) => { + let src = self.builder.load(src_typ, src_var); + + self.builder + .int_to_int(src.into_int_value(), size) + .as_basic_value_enum() + } + (CastType::Int(_), CastType::Float(size)) => { + let src = self.builder.load(src_typ, src_var); + + self.builder + .int_to_float(src.into_int_value(), size) + .as_basic_value_enum() + } + (CastType::Int(_), CastType::Pointer) => { + let src = self + .builder + .load(src_typ, src_var) + .into_int_value(); + + self.builder.int_to_pointer(src).as_basic_value_enum() + } + (CastType::Float(_), CastType::Int(size)) => { + let src = self.builder.load(src_typ, src_var); + + self.float_to_int(src.into_float_value(), size) + .as_basic_value_enum() + } + (CastType::Float(_), CastType::Float(size)) => { + let src = self.builder.load(src_typ, src_var); + + self.builder + .float_to_float(src.into_float_value(), size) + .as_basic_value_enum() + } + (CastType::Int(_), CastType::InkoInt) => { + let src = self.builder.load(src_typ, src_var); + let raw = + self.builder.int_to_int(src.into_int_value(), 64); + + self.new_int(state_var, raw).as_basic_value_enum() + } + (CastType::Int(_), CastType::InkoFloat) => { + let src = self.builder.load(src_typ, src_var); + let raw = + self.builder.int_to_float(src.into_int_value(), 64); + + self.new_float(state_var, raw).as_basic_value_enum() + } + (CastType::Float(_), CastType::InkoInt) => { + let src = self.builder.load(src_typ, src_var); + let raw = self.float_to_int(src.into_float_value(), 64); + + self.new_int(state_var, raw).as_basic_value_enum() + } + (CastType::Float(_), CastType::InkoFloat) => { + let src = self.builder.load(src_typ, src_var); + let raw = self + .builder + .float_to_float(src.into_float_value(), 64); + + self.new_float(state_var, raw).as_basic_value_enum() + } + (CastType::InkoInt, CastType::Int(size)) => { + let raw = self.read_int(src_var); + + self.builder.int_to_int(raw, size).as_basic_value_enum() + } + (CastType::InkoInt, CastType::Float(size)) => { + let raw = self.read_int(src_var); + + self.builder + .int_to_float(raw, size) + .as_basic_value_enum() + } + (CastType::InkoInt, CastType::Pointer) => { + let raw = self.read_int(src_var); + + self.builder.int_to_pointer(raw).as_basic_value_enum() + } + (CastType::InkoFloat, CastType::Int(size)) => { + let raw = self.read_float(src_var); + + self.float_to_int(raw, size).as_basic_value_enum() + } + (CastType::InkoFloat, CastType::Float(size)) => { + let raw = self.read_float(src_var); + + self.builder + .float_to_float(raw, size) + .as_basic_value_enum() + } + (CastType::InkoInt, CastType::InkoFloat) => { + let val = self.read_int(src_var); + let raw = self.builder.int_to_float(val, 64); + + self.new_float(state_var, raw).as_basic_value_enum() + } + (CastType::InkoFloat, CastType::InkoInt) => { + let val = self.read_float(src_var); + let raw = self.float_to_int(val, 64); + + self.new_int(state_var, raw).as_basic_value_enum() + } + (CastType::Pointer, CastType::Int(size)) => { + let src = self.builder.load(src_typ, src_var); + let raw = self + .builder + .pointer_to_int(src.into_pointer_value()); + + self.builder.int_to_int(raw, size).as_basic_value_enum() + } + (CastType::Pointer, CastType::InkoInt) => { + let src = self.builder.load(src_typ, src_var); + let raw = self + .builder + .pointer_to_int(src.into_pointer_value()); + + self.new_int(state_var, raw).as_basic_value_enum() + } + (CastType::Pointer, CastType::Pointer) => { + // Pointers are untyped at the LLVM level and instead + // load/stores/etc use the types, so there's nothing + // special we need to do in this case. + self.builder.load(src_typ, src_var) + } + _ => unreachable!(), + }; + + self.builder.store(reg_var, res); + } + Instruction::ReadPointer(ins) => { + let reg_var = self.variables[&ins.register]; + let reg_typ = self.variable_types[&ins.register]; + let ptr_var = self.variables[&ins.pointer]; + let ptr_typ = self.variable_types[&ins.pointer]; + let ptr = + self.builder.load(ptr_typ, ptr_var).into_pointer_value(); + let val = self.builder.load(reg_typ, ptr); + + self.builder.store(reg_var, val); + } + Instruction::WritePointer(ins) => { + let ptr_var = self.variables[&ins.pointer]; + let ptr_typ = self.variable_types[&ins.pointer]; + let val_var = self.variables[&ins.value]; + let val_typ = self.variable_types[&ins.value]; + let val = self.builder.load(val_typ, val_var); + let ptr = + self.builder.load(ptr_typ, ptr_var).into_pointer_value(); + + self.builder.store(ptr, val); + } + Instruction::Pointer(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.value]; + + self.builder.store(reg_var, val_var); + } Instruction::Reference(_) => unreachable!(), Instruction::Drop(_) => unreachable!(), } @@ -5154,25 +4518,27 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { for index in 0..self.method.registers.len() { let id = RegisterId(index as _); - let typ = self.method.registers.value_type(id); - let alloca_typ = if let Some(id) = typ.class_id(self.db) { - let layout = self.layouts.instances[&id]; - - if id.kind(self.db).is_extern() { - layout.as_basic_type_enum() - } else { - layout.ptr_type(space).as_basic_type_enum() - } - } else { - self.builder.context.pointer_type().as_basic_type_enum() - }; - - self.variables.insert(id, self.builder.alloca(alloca_typ)); - self.variable_types.insert(id, alloca_typ); + let raw = self.method.registers.value_type(id); + let typ = self + .builder + .context + .foreign_type(self.db, self.layouts, raw) + .unwrap_or_else(|| { + if let Some(id) = raw.class_id(self.db) { + self.layouts.instances[&id] + .ptr_type(space) + .as_basic_type_enum() + } else { + self.builder.context.pointer_type().as_basic_type_enum() + } + }); + + self.variables.insert(id, self.builder.alloca(typ)); + self.variable_types.insert(id, typ); } } - fn register_type(&self, register: RegisterId) -> types::TypeRef { + fn register_type(&self, register: RegisterId) -> TypeRef { self.method.registers.value_type(register) } @@ -5223,6 +4589,26 @@ impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { self.builder.set_debug_location(loc); } + + fn float_to_int( + &mut self, + source: FloatValue<'ctx>, + size: u32, + ) -> IntValue<'ctx> { + let target = match size { + 8 => self.builder.context.i8_type(), + 16 => self.builder.context.i16_type(), + 32 => self.builder.context.i32_type(), + _ => self.builder.context.i64_type(), + }; + + let func = self.module.intrinsic( + "llvm.fptosi.sat", + &[target.into(), source.get_type().into()], + ); + + self.builder.call(func, &[source.into()]).into_int_value() + } } /// A pass for generating the entry module and method (i.e. `main()`). diff --git a/compiler/src/llvm/runtime_function.rs b/compiler/src/llvm/runtime_function.rs index f97e1072d..d3529eef5 100644 --- a/compiler/src/llvm/runtime_function.rs +++ b/compiler/src/llvm/runtime_function.rs @@ -4,42 +4,9 @@ use inkwell::AddressSpace; #[derive(Copy, Clone)] pub(crate) enum RuntimeFunction { - ArrayCapacity, - ArrayClear, - ArrayDrop, - ArrayGet, - ArrayLength, ArrayNew, ArrayNewPermanent, - ArrayPop, ArrayPush, - ArrayRemove, - ArrayReserve, - ArraySet, - ByteArrayAppend, - ByteArrayClear, - ByteArrayClone, - ByteArrayCopyFrom, - ByteArrayDrainToString, - ByteArrayDrop, - ByteArrayEq, - ByteArrayGet, - ByteArrayLength, - ByteArrayNew, - ByteArrayPop, - ByteArrayPush, - ByteArrayRemove, - ByteArrayResize, - ByteArraySet, - ByteArraySlice, - ByteArrayToString, - ChannelDrop, - ChannelNew, - ChannelReceive, - ChannelReceiveUntil, - ChannelSend, - ChannelTryReceive, - ChannelWait, CheckRefs, ChildProcessDrop, ChildProcessSpawn, @@ -164,22 +131,9 @@ pub(crate) enum RuntimeFunction { StdoutFlush, StdoutWriteBytes, StdoutWriteString, - StringByte, - StringCharacters, - StringCharactersDrop, - StringCharactersNext, StringConcat, - StringConcatArray, - StringDrop, - StringEquals, StringNewPermanent, - StringSize, - StringSliceBytes, - StringToByteArray, StringToFloat, - StringToInt, - StringToLower, - StringToUpper, TimeMonotonic, TimeSystem, TimeSystemOffset, @@ -188,46 +142,9 @@ pub(crate) enum RuntimeFunction { impl RuntimeFunction { pub(crate) fn name(self) -> &'static str { match self { - RuntimeFunction::ArrayCapacity => "inko_array_capacity", - RuntimeFunction::ArrayClear => "inko_array_clear", - RuntimeFunction::ArrayDrop => "inko_array_drop", - RuntimeFunction::ArrayGet => "inko_array_get", - RuntimeFunction::ArrayLength => "inko_array_length", RuntimeFunction::ArrayNew => "inko_array_new", RuntimeFunction::ArrayNewPermanent => "inko_array_new_permanent", - RuntimeFunction::ArrayPop => "inko_array_pop", RuntimeFunction::ArrayPush => "inko_array_push", - RuntimeFunction::ArrayRemove => "inko_array_remove", - RuntimeFunction::ArrayReserve => "inko_array_reserve", - RuntimeFunction::ArraySet => "inko_array_set", - RuntimeFunction::ByteArrayAppend => "inko_byte_array_append", - RuntimeFunction::ByteArrayClear => "inko_byte_array_clear", - RuntimeFunction::ByteArrayClone => "inko_byte_array_clone", - RuntimeFunction::ByteArrayCopyFrom => "inko_byte_array_copy_from", - RuntimeFunction::ByteArrayDrainToString => { - "inko_byte_array_drain_to_string" - } - RuntimeFunction::ByteArrayDrop => "inko_byte_array_drop", - RuntimeFunction::ByteArrayEq => "inko_byte_array_eq", - RuntimeFunction::ByteArrayGet => "inko_byte_array_get", - RuntimeFunction::ByteArrayLength => "inko_byte_array_length", - RuntimeFunction::ByteArrayNew => "inko_byte_array_new", - RuntimeFunction::ByteArrayPop => "inko_byte_array_pop", - RuntimeFunction::ByteArrayPush => "inko_byte_array_push", - RuntimeFunction::ByteArrayRemove => "inko_byte_array_remove", - RuntimeFunction::ByteArrayResize => "inko_byte_array_resize", - RuntimeFunction::ByteArraySet => "inko_byte_array_set", - RuntimeFunction::ByteArraySlice => "inko_byte_array_slice", - RuntimeFunction::ByteArrayToString => "inko_byte_array_to_string", - RuntimeFunction::ChannelDrop => "inko_channel_drop", - RuntimeFunction::ChannelNew => "inko_channel_new", - RuntimeFunction::ChannelReceive => "inko_channel_receive", - RuntimeFunction::ChannelReceiveUntil => { - "inko_channel_receive_until" - } - RuntimeFunction::ChannelSend => "inko_channel_send", - RuntimeFunction::ChannelTryReceive => "inko_channel_try_receive", - RuntimeFunction::ChannelWait => "inko_channel_wait", RuntimeFunction::CheckRefs => "inko_check_refs", RuntimeFunction::ChildProcessDrop => "inko_child_process_drop", RuntimeFunction::ChildProcessSpawn => "inko_child_process_spawn", @@ -401,26 +318,9 @@ impl RuntimeFunction { RuntimeFunction::StdoutFlush => "inko_stdout_flush", RuntimeFunction::StdoutWriteBytes => "inko_stdout_write_bytes", RuntimeFunction::StdoutWriteString => "inko_stdout_write_string", - RuntimeFunction::StringByte => "inko_string_byte", - RuntimeFunction::StringCharacters => "inko_string_characters", - RuntimeFunction::StringCharactersDrop => { - "inko_string_characters_drop" - } - RuntimeFunction::StringCharactersNext => { - "inko_string_characters_next" - } RuntimeFunction::StringConcat => "inko_string_concat", - RuntimeFunction::StringConcatArray => "inko_string_concat_array", - RuntimeFunction::StringDrop => "inko_string_drop", - RuntimeFunction::StringEquals => "inko_string_equals", RuntimeFunction::StringNewPermanent => "inko_string_new_permanent", - RuntimeFunction::StringSize => "inko_string_size", - RuntimeFunction::StringSliceBytes => "inko_string_slice_bytes", - RuntimeFunction::StringToByteArray => "inko_string_to_byte_array", RuntimeFunction::StringToFloat => "inko_string_to_float", - RuntimeFunction::StringToInt => "inko_string_to_int", - RuntimeFunction::StringToLower => "inko_string_to_lower", - RuntimeFunction::StringToUpper => "inko_string_to_upper", RuntimeFunction::TimeMonotonic => "inko_time_monotonic", RuntimeFunction::TimeSystem => "inko_time_system", RuntimeFunction::TimeSystemOffset => "inko_time_system_offset", @@ -524,14 +424,6 @@ impl RuntimeFunction { ret.fn_type(&[class], false) } - RuntimeFunction::StringEquals => { - let state = module.layouts.state.ptr_type(space).into(); - let lhs = context.pointer_type().into(); - let rhs = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, lhs, rhs], false) - } RuntimeFunction::ProcessPanic => { let proc = context.pointer_type().into(); let val = context.pointer_type().into(); @@ -577,198 +469,6 @@ impl RuntimeFunction { ret.fn_type(&[state, val], false) } - RuntimeFunction::ArrayCapacity => { - let state = module.layouts.state.ptr_type(space).into(); - let val = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, val], false) - } - RuntimeFunction::ArrayClear => { - let state = module.layouts.state.ptr_type(space).into(); - let val = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, val], false) - } - RuntimeFunction::ArrayDrop => { - let state = module.layouts.state.ptr_type(space).into(); - let val = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, val], false) - } - RuntimeFunction::ArrayGet => { - let array = context.pointer_type().into(); - let index = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array, index], false) - } - RuntimeFunction::ArrayLength => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ArrayPop => { - let array = context.pointer_type().into(); - let ret = module.layouts.result; - - ret.fn_type(&[array], false) - } - RuntimeFunction::ArrayRemove => { - let array = context.pointer_type().into(); - let index = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array, index], false) - } - RuntimeFunction::ArrayReserve => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let amount = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array, amount], false) - } - RuntimeFunction::ArraySet => { - let array = context.pointer_type().into(); - let index = context.i64_type().into(); - let value = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array, index, value], false) - } - RuntimeFunction::ByteArrayNew => { - let state = module.layouts.state.ptr_type(space).into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state], false) - } - RuntimeFunction::ByteArrayPush => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let value = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array, value], false) - } - RuntimeFunction::ByteArrayPop => { - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array], false) - } - RuntimeFunction::ByteArraySet => { - let array = context.pointer_type().into(); - let index = context.i64_type().into(); - let value = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array, index, value], false) - } - RuntimeFunction::ByteArrayGet => { - let array = context.pointer_type().into(); - let index = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array, index], false) - } - RuntimeFunction::ByteArrayRemove => { - let array = context.pointer_type().into(); - let index = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[array, index], false) - } - RuntimeFunction::ByteArrayLength => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ByteArrayEq => { - let state = module.layouts.state.ptr_type(space).into(); - let lhs = context.pointer_type().into(); - let rhs = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, lhs, rhs], false) - } - RuntimeFunction::ByteArrayClear => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ByteArrayClone => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ByteArrayDrop => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ByteArrayToString => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ByteArrayDrainToString => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array], false) - } - RuntimeFunction::ByteArraySlice => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let start = context.i64_type().into(); - let length = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array, start, length], false) - } - RuntimeFunction::ByteArrayAppend => { - let state = module.layouts.state.ptr_type(space).into(); - let target = context.pointer_type().into(); - let source = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, target, source], false) - } - RuntimeFunction::ByteArrayCopyFrom => { - let state = module.layouts.state.ptr_type(space).into(); - let target = context.pointer_type().into(); - let source = context.pointer_type().into(); - let start = context.i64_type().into(); - let length = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, target, source, start, length], false) - } - RuntimeFunction::ByteArrayResize => { - let state = module.layouts.state.ptr_type(space).into(); - let array = context.pointer_type().into(); - let size = context.i64_type().into(); - let filler = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, array, size, filler], false) - } RuntimeFunction::ChildProcessSpawn => { let proc = context.pointer_type().into(); let program = context.pointer_type().into(); @@ -913,19 +613,19 @@ impl RuntimeFunction { } RuntimeFunction::ClassObject => { let name = context.pointer_type().into(); - let fields = context.i8_type().into(); + let size = context.i32_type().into(); let methods = context.i16_type().into(); let ret = context.pointer_type(); - ret.fn_type(&[name, fields, methods], false) + ret.fn_type(&[name, size, methods], false) } RuntimeFunction::ClassProcess => { let name = context.pointer_type().into(); - let fields = context.i8_type().into(); + let size = context.i32_type().into(); let methods = context.i16_type().into(); let ret = context.pointer_type(); - ret.fn_type(&[name, fields, methods], false) + ret.fn_type(&[name, size, methods], false) } RuntimeFunction::MessageNew => { let method = context.pointer_type().into(); @@ -963,60 +663,6 @@ impl RuntimeFunction { ret.fn_type(&[state, value], false) } - RuntimeFunction::ChannelDrop => { - let state = module.layouts.state.ptr_type(space).into(); - let chan = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, chan], false) - } - RuntimeFunction::ChannelNew => { - let state = module.layouts.state.ptr_type(space).into(); - let capacity = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, capacity], false) - } - RuntimeFunction::ChannelReceive => { - let proc = context.pointer_type().into(); - let chan = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[proc, chan], false) - } - RuntimeFunction::ChannelReceiveUntil => { - let state = module.layouts.state.ptr_type(space).into(); - let proc = context.pointer_type().into(); - let channel = context.pointer_type().into(); - let time = context.i64_type().into(); - let ret = module.layouts.result; - - ret.fn_type(&[state, proc, channel, time], false) - } - RuntimeFunction::ChannelSend => { - let state = module.layouts.state.ptr_type(space).into(); - let proc = context.pointer_type().into(); - let channel = context.pointer_type().into(); - let message = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, proc, channel, message], false) - } - RuntimeFunction::ChannelTryReceive => { - let proc = context.pointer_type().into(); - let channel = context.pointer_type().into(); - let ret = module.layouts.result; - - ret.fn_type(&[proc, channel], false) - } - RuntimeFunction::ChannelWait => { - let state = module.layouts.state.ptr_type(space).into(); - let proc = context.pointer_type().into(); - let channels = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, proc, channels], false) - } RuntimeFunction::DirectoryCreate | RuntimeFunction::DirectoryCreateRecursive | RuntimeFunction::DirectoryList @@ -1472,33 +1118,6 @@ impl RuntimeFunction { ret.fn_type(&[state, proc, buffer, size], false) } - RuntimeFunction::StringByte => { - let string = context.pointer_type().into(); - let index = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[string, index], false) - } - RuntimeFunction::StringCharacters => { - let string = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[string], false) - } - RuntimeFunction::StringCharactersDrop => { - let state = module.layouts.state.ptr_type(space).into(); - let input = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, input], false) - } - RuntimeFunction::StringCharactersNext => { - let state = module.layouts.state.ptr_type(space).into(); - let input = context.pointer_type().into(); - let ret = module.layouts.result; - - ret.fn_type(&[state, input], false) - } RuntimeFunction::StringConcat => { let state = module.layouts.state.ptr_type(space).into(); let strings = context.pointer_type().into(); @@ -1507,20 +1126,6 @@ impl RuntimeFunction { ret.fn_type(&[state, strings, length], false) } - RuntimeFunction::StringConcatArray => { - let state = module.layouts.state.ptr_type(space).into(); - let strings = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, strings], false) - } - RuntimeFunction::StringDrop => { - let state = module.layouts.state.ptr_type(space).into(); - let string = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, string], false) - } RuntimeFunction::StringNewPermanent => { let state = module.layouts.state.ptr_type(space).into(); let bytes = context.pointer_type().into(); @@ -1529,31 +1134,6 @@ impl RuntimeFunction { ret.fn_type(&[state, bytes, length], false) } - RuntimeFunction::StringSize => { - let state = module.layouts.state.ptr_type(space).into(); - let string = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, string], false) - } - RuntimeFunction::StringSliceBytes => { - let state = module.layouts.state.ptr_type(space).into(); - let string = context.pointer_type().into(); - let start = context.i64_type().into(); - let length = context.i64_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, string, start, length], false) - } - RuntimeFunction::StringToByteArray - | RuntimeFunction::StringToLower - | RuntimeFunction::StringToUpper => { - let state = module.layouts.state.ptr_type(space).into(); - let string = context.pointer_type().into(); - let ret = context.pointer_type(); - - ret.fn_type(&[state, string], false) - } RuntimeFunction::StringToFloat => { let state = module.layouts.state.ptr_type(space).into(); let string = context.pointer_type().into(); @@ -1563,17 +1143,6 @@ impl RuntimeFunction { ret.fn_type(&[state, string, start, end], false) } - RuntimeFunction::StringToInt => { - let state = module.layouts.state.ptr_type(space).into(); - let proc = context.pointer_type().into(); - let string = context.pointer_type().into(); - let radix = context.i64_type().into(); - let start = context.i64_type().into(); - let end = context.i64_type().into(); - let ret = module.layouts.result; - - ret.fn_type(&[state, proc, string, radix, start, end], false) - } RuntimeFunction::TimeSystem | RuntimeFunction::TimeSystemOffset => { let state = module.layouts.state.ptr_type(space).into(); let ret = context.pointer_type(); diff --git a/compiler/src/mir/mod.rs b/compiler/src/mir/mod.rs index f5e8f8a32..5c203fa4f 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -447,6 +447,21 @@ impl Block { ))); } + pub(crate) fn call_extern( + &mut self, + register: RegisterId, + method: types::MethodId, + arguments: Vec, + location: LocationId, + ) { + self.instructions.push(Instruction::CallExtern(Box::new(CallExtern { + register, + method, + arguments, + location, + }))); + } + pub(crate) fn call_dynamic( &mut self, register: RegisterId, @@ -544,6 +559,41 @@ impl Block { }))); } + pub(crate) fn pointer( + &mut self, + register: RegisterId, + value: RegisterId, + location: LocationId, + ) { + self.instructions.push(Instruction::Pointer(Box::new(Pointer { + register, + value, + location, + }))) + } + + pub(crate) fn read_pointer( + &mut self, + register: RegisterId, + pointer: RegisterId, + location: LocationId, + ) { + self.instructions.push(Instruction::ReadPointer(Box::new( + ReadPointer { register, pointer, location }, + ))); + } + + pub(crate) fn write_pointer( + &mut self, + pointer: RegisterId, + value: RegisterId, + location: LocationId, + ) { + self.instructions.push(Instruction::WritePointer(Box::new( + WritePointer { pointer, value, location }, + ))); + } + pub(crate) fn allocate( &mut self, register: RegisterId, @@ -589,6 +639,23 @@ impl Block { pub(crate) fn reduce_call(&mut self, location: LocationId) { self.reduce(CALL_COST, location); } + + pub(crate) fn cast( + &mut self, + register: RegisterId, + source: RegisterId, + from: CastType, + to: CastType, + location: LocationId, + ) { + self.instructions.push(Instruction::Cast(Box::new(Cast { + register, + source, + from, + to, + location, + }))); + } } #[derive(Clone, Debug)] @@ -845,6 +912,14 @@ pub(crate) struct CallInstance { pub(crate) location: LocationId, } +#[derive(Clone)] +pub(crate) struct CallExtern { + pub(crate) register: RegisterId, + pub(crate) method: types::MethodId, + pub(crate) arguments: Vec, + pub(crate) location: LocationId, +} + #[derive(Clone)] pub(crate) struct CallDynamic { pub(crate) register: RegisterId, @@ -929,6 +1004,45 @@ pub(crate) struct Finish { pub(crate) location: LocationId, } +#[derive(Clone)] +pub(crate) struct Cast { + pub(crate) register: RegisterId, + pub(crate) source: RegisterId, + pub(crate) from: CastType, + pub(crate) to: CastType, + pub(crate) location: LocationId, +} + +#[derive(Clone, Debug, Copy)] +pub(crate) enum CastType { + Int(u32), + Float(u32), + InkoInt, + InkoFloat, + Pointer, +} + +#[derive(Clone, Debug, Copy)] +pub(crate) struct Pointer { + pub(crate) register: RegisterId, + pub(crate) value: RegisterId, + pub(crate) location: LocationId, +} + +#[derive(Clone, Debug, Copy)] +pub(crate) struct ReadPointer { + pub(crate) register: RegisterId, + pub(crate) pointer: RegisterId, + pub(crate) location: LocationId, +} + +#[derive(Clone, Debug, Copy)] +pub(crate) struct WritePointer { + pub(crate) pointer: RegisterId, + pub(crate) value: RegisterId, + pub(crate) location: LocationId, +} + /// A MIR instruction. /// /// When adding a new instruction that acts as an exit for a basic block, make @@ -950,6 +1064,7 @@ pub(crate) enum Instruction { True(Box), CallStatic(Box), CallInstance(Box), + CallExtern(Box), CallDynamic(Box), CallClosure(Box), CallDropper(Box), @@ -971,6 +1086,10 @@ pub(crate) enum Instruction { GetConstant(Box), Reduce(Box), Finish(Box), + Cast(Box), + Pointer(Box), + ReadPointer(Box), + WritePointer(Box), } impl Instruction { @@ -991,6 +1110,7 @@ impl Instruction { Instruction::String(ref v) => v.location, Instruction::CallStatic(ref v) => v.location, Instruction::CallInstance(ref v) => v.location, + Instruction::CallExtern(ref v) => v.location, Instruction::CallDynamic(ref v) => v.location, Instruction::CallClosure(ref v) => v.location, Instruction::CallDropper(ref v) => v.location, @@ -1012,6 +1132,10 @@ impl Instruction { Instruction::GetConstant(ref v) => v.location, Instruction::Reduce(ref v) => v.location, Instruction::Finish(ref v) => v.location, + Instruction::Cast(ref v) => v.location, + Instruction::Pointer(ref v) => v.location, + Instruction::ReadPointer(ref v) => v.location, + Instruction::WritePointer(ref v) => v.location, } } @@ -1116,6 +1240,14 @@ impl Instruction { join(&v.arguments) ) } + Instruction::CallExtern(ref v) => { + format!( + "r{} = call_extern {}({})", + v.register.0, + v.method.name(db), + join(&v.arguments) + ) + } Instruction::CallDynamic(ref v) => { format!( "r{} = call_dynamic r{}.{}({})", @@ -1198,6 +1330,18 @@ impl Instruction { Instruction::Finish(v) => { if v.terminate { "terminate" } else { "finish" }.to_string() } + Instruction::Cast(v) => { + format!("r{} = r{} as {:?}", v.register.0, v.source.0, v.to) + } + Instruction::ReadPointer(v) => { + format!("r{} = *r{}", v.register.0, v.pointer.0) + } + Instruction::WritePointer(v) => { + format!("*r{} = r{}", v.pointer.0, v.value.0) + } + Instruction::Pointer(v) => { + format!("r{} = pointer r{}", v.register.0, v.value.0) + } } } } diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 6a8f27bd7..54b91096f 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -3,8 +3,8 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::mir::pattern_matching as pmatch; use crate::mir::{ - Block, BlockId, Class, CloneKind, Constant, Goto, Instruction, LocationId, - Method, Mir, Module, RegisterId, Trait, + Block, BlockId, CastType, Class, CloneKind, Constant, Goto, Instruction, + LocationId, Method, Mir, Module, RegisterId, Trait, }; use crate::state::State; use ast::source_location::SourceLocation; @@ -15,8 +15,9 @@ use std::path::PathBuf; use std::rc::Rc; use types::format::format_type; use types::{ - self, Block as _, TypeBounds, FIELDS_LIMIT, OPTION_NONE, OPTION_SOME, - RESULT_CLASS, RESULT_ERROR, RESULT_MODULE, RESULT_OK, + self, Block as _, ClassId, ForeignType, MethodId, TypeBounds, TypeId, + TypeRef, EQ_METHOD, FIELDS_LIMIT, OPTION_NONE, OPTION_SOME, RESULT_CLASS, + RESULT_ERROR, RESULT_MODULE, RESULT_OK, }; const SELF_NAME: &str = "self"; @@ -415,7 +416,7 @@ impl<'a> GenerateDropper<'a> { Vec::new(), loc, ); - lower.reduce_call(types::TypeRef::nil(), loc); + lower.reduce_call(TypeRef::nil(), loc); lower.current_block_mut().return_value(nil_reg, loc); assert_eq!(lower.method.arguments.len(), 1); @@ -444,7 +445,7 @@ impl<'a> GenerateDropper<'a> { let self_reg = lower.self_register; if let Some(id) = drop_method_opt { - let typ = types::TypeRef::nil(); + let typ = TypeRef::nil(); let res = lower.new_register(typ); lower.current_block_mut().call_instance( @@ -464,7 +465,7 @@ impl<'a> GenerateDropper<'a> { let enum_fields = class.enum_fields(lower.db()); let tag_field = class.field_by_index(lower.db(), types::ENUM_TAG_INDEX).unwrap(); - let tag_reg = lower.new_register(types::TypeRef::int()); + let tag_reg = lower.new_register(TypeRef::int()); for var in variants { let block = lower.add_current_block(); @@ -515,7 +516,7 @@ impl<'a> GenerateDropper<'a> { kind: types::MethodKind, free_self: bool, terminate: bool, - ) -> types::MethodId { + ) -> MethodId { let class = self.class.id; let drop_method_opt = class.method(&self.state.db, types::DROP_METHOD); let method_type = self.method_type(name, kind); @@ -529,7 +530,7 @@ impl<'a> GenerateDropper<'a> { let self_reg = lower.self_register; if let Some(id) = drop_method_opt { - let typ = types::TypeRef::nil(); + let typ = TypeRef::nil(); let res = lower.new_register(typ); lower.current_block_mut().call_instance( @@ -580,11 +581,7 @@ impl<'a> GenerateDropper<'a> { method_type } - fn method_type( - &mut self, - name: &str, - kind: types::MethodKind, - ) -> types::MethodId { + fn method_type(&mut self, name: &str, kind: types::MethodKind) -> MethodId { let id = types::Method::alloc( &mut self.state.db, self.module.id, @@ -599,14 +596,14 @@ impl<'a> GenerateDropper<'a> { self.class.id, &types::TypeBounds::new(), )); - let receiver = types::TypeRef::Mut(self_type); + let receiver = TypeRef::Mut(self_type); id.set_receiver(&mut self.state.db, receiver); - id.set_return_type(&mut self.state.db, types::TypeRef::nil()); + id.set_return_type(&mut self.state.db, TypeRef::nil()); id } - fn add_method(&mut self, name: &str, id: types::MethodId, method: Method) { + fn add_method(&mut self, name: &str, id: MethodId, method: Method) { self.class.id.add_method(&mut self.state.db, name.to_string(), id); self.class.methods.push(id); self.mir.methods.insert(id, method); @@ -845,6 +842,7 @@ impl<'a> LowerToMir<'a> { v, hir::TopLevelExpression::Trait(_) | hir::TopLevelExpression::Class(_) + | hir::TopLevelExpression::ExternClass(_) ) }); @@ -877,6 +875,9 @@ impl<'a> LowerToMir<'a> { hir::TopLevelExpression::Class(n) => { self.define_class(*n); } + hir::TopLevelExpression::ExternClass(n) => { + self.define_extern_class(*n); + } _ => {} } } @@ -984,22 +985,26 @@ impl<'a> LowerToMir<'a> { let mut class = Class::new(id); - if !id.kind(self.db()).is_extern() { - GenerateDropper { - state: self.state, - mir: self.mir, - module: self.module, - class: &mut class, - location: node.location, - } - .run(); + GenerateDropper { + state: self.state, + mir: self.mir, + module: self.module, + class: &mut class, + location: node.location, } + .run(); class.add_methods(&methods); self.mir.add_methods(methods); self.add_class(id, class); } + fn define_extern_class(&mut self, node: hir::DefineExternClass) { + let id = node.class_id.unwrap(); + + self.add_class(id, Class::new(id)); + } + fn reopen_class(&mut self, node: hir::ReopenClass) { let id = node.class_id.unwrap(); let mut methods = Vec::new(); @@ -1086,7 +1091,7 @@ impl<'a> LowerToMir<'a> { let mut method = Method::new(id, loc); let fields = class.enum_fields(self.db()); let bounds = TypeBounds::new(); - let ins = types::TypeRef::Owned(types::TypeId::ClassInstance( + let ins = TypeRef::Owned(types::TypeId::ClassInstance( types::ClassInstance::rigid(self.db_mut(), class, &bounds), )); let mut lower = @@ -1095,7 +1100,7 @@ impl<'a> LowerToMir<'a> { lower.prepare(loc); let ins_reg = lower.new_register(ins); - let tag_reg = lower.new_register(types::TypeRef::int()); + let tag_reg = lower.new_register(TypeRef::int()); let tag_val = variant_id.id(lower.db()) as i64; let tag_field = class.field_by_index(lower.db(), types::ENUM_TAG_INDEX).unwrap(); @@ -1222,7 +1227,7 @@ impl<'a> LowerMethod<'a> { mut self, nodes: Vec, self_field: types::FieldId, - self_type: types::TypeRef, + self_type: TypeRef, location: LocationId, ) { self.prepare(location); @@ -1334,7 +1339,7 @@ impl<'a> LowerMethod<'a> { fn define_captured_self_register( &mut self, field: types::FieldId, - field_type: types::TypeRef, + field_type: TypeRef, location: LocationId, ) { // Within a closure, explicit and implicit references to `self` should @@ -1536,7 +1541,7 @@ impl<'a> LowerMethod<'a> { // of the `if`). self.get_nil(loc) } else { - self.new_register(types::TypeRef::Never) + self.new_register(TypeRef::Never) } } @@ -1544,14 +1549,14 @@ impl<'a> LowerMethod<'a> { let target = self.loop_target().unwrap().1; self.jump_out_of_loop(target, node.location); - self.new_register(types::TypeRef::Never) + self.new_register(TypeRef::Never) } fn next_expression(&mut self, node: hir::Next) -> RegisterId { let target = self.loop_target().unwrap().0; self.jump_out_of_loop(target, node.location); - self.new_register(types::TypeRef::Never) + self.new_register(TypeRef::Never) } fn loop_target(&self) -> Option<(BlockId, BlockId)> { @@ -1743,7 +1748,7 @@ impl<'a> LowerMethod<'a> { value: String, location: SourceLocation, ) -> RegisterId { - let reg = self.new_register(types::TypeRef::string()); + let reg = self.new_register(TypeRef::string()); let loc = self.add_location(location); self.current_block_mut().string_literal(reg, value, loc); @@ -1765,9 +1770,7 @@ impl<'a> LowerMethod<'a> { }; let args = self.call_arguments(&info, node.arguments); - let result = self.handle_call(info, rec, args, loc); - - self.reduce_call(returns, loc); + let result = self.call_method(info, rec, args, loc); if returns.is_never(self.db()) { self.add_current_block(); @@ -1813,16 +1816,21 @@ impl<'a> LowerMethod<'a> { res } - _ => { - unreachable!() + types::CallKind::ReadPointer(typ) => { + let rec = self.expression(node.receiver.unwrap()); + let res = self.new_register(typ); + + self.current_block_mut().read_pointer(res, rec, loc); + res } + _ => unreachable!(), }; self.exit_call_scope(entered, reg); reg } - fn handle_call( + fn call_method( &mut self, info: types::CallInfo, receiver: Option, @@ -1848,10 +1856,29 @@ impl<'a> LowerMethod<'a> { reg } types::Receiver::Class(id) => { - // Static methods can't move, be dynamic or async, so we can - // skip the code that comes after this. - self.current_block_mut() - .call_static(result, id, info.id, arguments, location); + // These methods can't move, be dynamic or async, so we can skip + // the code that comes after this. + if info.id.is_extern(self.db()) { + self.current_block_mut() + .call_extern(result, info.id, arguments, location); + + if !info.id.returns_value(self.db()) { + self.current_block_mut().nil_literal(result, location); + } + + // We don't reduce for extern calls for two reasons: + // + // 1. They are typically exposed through regular Inko + // methods, which would result in two reductions per call + // instead of one. + // 2. This allows us to check `errno` after a call, without + // having to worry about the process being rescheduled in + // between the call and the check. + } else { + self.current_block_mut() + .call_static(result, id, info.id, arguments, location); + self.reduce_call(info.returns, location); + } return result; } @@ -1881,6 +1908,7 @@ impl<'a> LowerMethod<'a> { .call_instance(result, rec, info.id, arguments, location); } + self.reduce_call(info.returns, location); result } @@ -1896,13 +1924,13 @@ impl<'a> LowerMethod<'a> { hir::Argument::Positional(n) => { let exp = self.expected_argument_type(info.id, index); - args[index] = self.input_expression(*n, Some(exp)); + args[index] = self.argument_expression(info, *n, exp); } hir::Argument::Named(n) => { let index = n.index; let exp = self.expected_argument_type(info.id, index); - args[index] = self.input_expression(n.value, Some(exp)); + args[index] = self.argument_expression(info, n.value, exp); } } } @@ -1910,11 +1938,7 @@ impl<'a> LowerMethod<'a> { args } - fn check_inferred( - &mut self, - typ: types::TypeRef, - location: &SourceLocation, - ) { + fn check_inferred(&mut self, typ: TypeRef, location: &SourceLocation) { if typ.is_inferred(self.db()) { return; } @@ -1928,16 +1952,16 @@ impl<'a> LowerMethod<'a> { fn expected_argument_type( &self, - method: types::MethodId, + method: MethodId, index: usize, - ) -> types::TypeRef { + ) -> TypeRef { method.positional_argument_input_type(self.db(), index).unwrap() } fn input_expression( &mut self, expression: hir::Expression, - expected: Option, + expected: Option, ) -> RegisterId { let loc = self.add_location(expression.location().clone()); let reg = self.expression(expression); @@ -1946,6 +1970,25 @@ impl<'a> LowerMethod<'a> { self.input_register(reg, typ, expected, loc) } + fn argument_expression( + &mut self, + info: &types::CallInfo, + expression: hir::Expression, + expected: TypeRef, + ) -> RegisterId { + let loc = self.add_location(expression.location().clone()); + let reg = self.expression(expression); + + // Arguments passed to extern functions are passed as-is. This way we + // can pass values to the runtime's functions, without adjusting + // reference counts. + if info.id.is_extern(self.db()) { + return reg; + } + + self.input_register(reg, self.register_type(reg), Some(expected), loc) + } + fn assign_setter(&mut self, node: hir::AssignSetter) -> RegisterId { let entered = self.enter_call_scope(); let reg = match node.kind { @@ -1962,9 +2005,7 @@ impl<'a> LowerMethod<'a> { let args = vec![self.input_expression(node.value, Some(exp))]; let returns = info.returns; - let result = self.handle_call(info, rec, args, loc); - - self.reduce_call(returns, loc); + let result = self.call_method(info, rec, args, loc); if returns.is_never(self.db()) { self.add_current_block(); @@ -1989,6 +2030,14 @@ impl<'a> LowerMethod<'a> { .set_field(rec, info.class, info.id, arg, loc); self.get_nil(loc) } + types::CallKind::WritePointer => { + let rec = self.expression(node.receiver); + let arg = self.input_expression(node.value, None); + let loc = self.add_location(node.location); + + self.current_block_mut().write_pointer(rec, arg, loc); + self.get_nil(loc) + } _ => unreachable!(), }; @@ -2139,8 +2188,9 @@ impl<'a> LowerMethod<'a> { name => { let reg = self.new_register(returns); + // Builtin calls don't reduce as they're exposed through regular + // methods, which already trigger reductions. self.current_block_mut().call_builtin(reg, name, args, loc); - self.reduce_call(returns, loc); if returns.is_never(self.db()) { self.add_current_block(); @@ -2163,14 +2213,14 @@ impl<'a> LowerMethod<'a> { self.drop_all_registers(); self.return_register(reg, loc); self.add_current_block(); - self.new_register(types::TypeRef::Never) + self.new_register(TypeRef::Never) } fn try_expression(&mut self, node: hir::Try) -> RegisterId { let loc = self.add_location(node.location); let reg = self.expression(node.expression); let class = self.register_type(reg).class_id(self.db()).unwrap(); - let tag_reg = self.new_untracked_register(types::TypeRef::int()); + let tag_reg = self.new_untracked_register(TypeRef::int()); let tag_field = class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); let val_field = class.enum_fields(self.db())[0]; @@ -2179,7 +2229,7 @@ impl<'a> LowerMethod<'a> { let after_block = self.add_block(); let mut blocks = vec![BlockId(0), BlockId(0)]; let ret_reg = self.new_untracked_register(node.return_type); - let err_tag = self.new_untracked_register(types::TypeRef::int()); + let err_tag = self.new_untracked_register(TypeRef::int()); self.add_edge(self.current_block, ok_block); self.add_edge(self.current_block, err_block); @@ -2284,7 +2334,7 @@ impl<'a> LowerMethod<'a> { class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); let val_field = class.enum_fields(self.db())[0]; let result_reg = self.new_register(node.return_type); - let tag_reg = self.new_register(types::TypeRef::int()); + let tag_reg = self.new_register(TypeRef::int()); self.current_block_mut().allocate(result_reg, class, loc); self.current_block_mut().int_literal(tag_reg, err_id as _, loc); @@ -2300,7 +2350,7 @@ impl<'a> LowerMethod<'a> { self.current_block_mut().return_value(result_reg, loc); self.add_current_block(); - self.new_register(types::TypeRef::Never) + self.new_register(TypeRef::Never) } fn return_register(&mut self, register: RegisterId, location: LocationId) { @@ -2321,9 +2371,21 @@ impl<'a> LowerMethod<'a> { let src = self.expression(node.value); let reg = self.new_register(node.resolved_type); let loc = self.add_location(node.location); + let from_type = self.register_type(src); + let to_type = node.resolved_type; + + match (self.cast_type(from_type), self.cast_type(to_type)) { + (Some(from), Some(to)) => { + self.current_block_mut().cast(reg, src, from, to, loc); + } + _ => { + let out = self.input_register(src, from_type, None, loc); + + self.mark_register_as_moved(out); + self.current_block_mut().move_register(reg, out, loc); + } + } - self.mark_register_as_moved(src); - self.current_block_mut().move_register(reg, src, loc); reg } @@ -2332,13 +2394,22 @@ impl<'a> LowerMethod<'a> { } fn mut_expression(&mut self, node: hir::Mut) -> RegisterId { - self.increment(node.value, node.resolved_type, node.location) + if node.resolved_type.is_pointer(self.db()) { + let loc = self.add_location(node.location); + let val = self.expression(node.value); + let reg = self.new_register(node.resolved_type); + + self.current_block_mut().pointer(reg, val, loc); + reg + } else { + self.increment(node.value, node.resolved_type, node.location) + } } fn increment( &mut self, value: hir::Expression, - return_type: types::TypeRef, + return_type: TypeRef, location: SourceLocation, ) -> RegisterId { let loc = self.add_location(location); @@ -2879,15 +2950,18 @@ impl<'a> LowerMethod<'a> { fallback }; - let res_reg = - self.new_untracked_register(types::TypeRef::boolean()); - let val_reg = self.new_untracked_register(types::TypeRef::string()); + let res_reg = self.new_untracked_register(TypeRef::boolean()); + let val_reg = self.new_untracked_register(TypeRef::string()); + let eq_method = ClassId::string() + .method(self.db(), EQ_METHOD) + .expect("String.== is undefined"); self.block_mut(test_block).string_literal(val_reg, val, loc); - self.block_mut(test_block).call_builtin( + self.block_mut(test_block).call_instance( res_reg, - types::BuiltinFunction::StringEq, - vec![test_reg, val_reg], + test_reg, + eq_method, + vec![val_reg], loc, ); @@ -2933,12 +3007,11 @@ impl<'a> LowerMethod<'a> { fallback }; - let res_reg = - self.new_untracked_register(types::TypeRef::boolean()); + let res_reg = self.new_untracked_register(TypeRef::boolean()); let test_end_block = match case.constructor { pmatch::Constructor::Int(val) => { - let val_type = types::TypeRef::int(); + let val_type = TypeRef::int(); let val_reg = self.new_untracked_register(val_type); self.block_mut(test_block).int_literal(val_reg, val, loc); @@ -3025,7 +3098,7 @@ impl<'a> LowerMethod<'a> { let test_type = self.register_type(test_reg); let class = test_type.class_id(self.db()).unwrap(); - let tag_reg = self.new_untracked_register(types::TypeRef::int()); + let tag_reg = self.new_untracked_register(TypeRef::int()); let tag_field = class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); let member_fields = class.enum_fields(self.db()); @@ -3035,7 +3108,7 @@ impl<'a> LowerMethod<'a> { .get_field(tag_reg, test_reg, class, tag_field, loc); for &field in &member_fields { - let reg = self.new_untracked_register(types::TypeRef::Any); + let reg = self.new_untracked_register(TypeRef::Any); self.block_mut(test_block) .get_field(reg, test_reg, class, field, loc); @@ -3083,10 +3156,8 @@ impl<'a> LowerMethod<'a> { } types::IdentifierKind::Method(info) => { let entered = self.enter_call_scope(); - let ret = info.returns; - let reg = self.handle_call(info, None, Vec::new(), loc); + let reg = self.call_method(info, None, Vec::new(), loc); - self.reduce_call(ret, loc); self.exit_call_scope(entered, reg); reg } @@ -3153,10 +3224,8 @@ impl<'a> LowerMethod<'a> { types::ConstantKind::Method(info) => { let entered = self.enter_call_scope(); let loc = self.add_location(node.location); - let ret = info.returns; - let reg = self.handle_call(info, None, Vec::new(), loc); + let reg = self.call_method(info, None, Vec::new(), loc); - self.reduce_call(ret, loc); self.exit_call_scope(entered, reg); reg } @@ -3196,7 +3265,7 @@ impl<'a> LowerMethod<'a> { let gen_class_ins = types::TypeId::ClassInstance(types::ClassInstance::new(class_id)); - let call_rec_type = types::TypeRef::Mut(gen_class_ins); + let call_rec_type = TypeRef::Mut(gen_class_ins); let returns = closure_id.return_type(self.db()); method_id.set_receiver(self.db_mut(), call_rec_type); @@ -3216,7 +3285,7 @@ impl<'a> LowerMethod<'a> { method_id, ); - let gen_class_type = types::TypeRef::Owned(gen_class_ins); + let gen_class_type = TypeRef::Owned(gen_class_ins); let gen_class_reg = self.new_register(gen_class_type); let loc = self.add_location(node.location.clone()); @@ -3379,7 +3448,7 @@ impl<'a> LowerMethod<'a> { } fn get_nil(&mut self, location: LocationId) -> RegisterId { - let reg = self.new_register(types::TypeRef::nil()); + let reg = self.new_register(TypeRef::nil()); self.current_block_mut().nil_literal(reg, location); reg @@ -3564,23 +3633,14 @@ impl<'a> LowerMethod<'a> { fn input_register( &mut self, register: RegisterId, - register_type: types::TypeRef, - expected: Option, + register_type: TypeRef, + expected: Option, location: LocationId, ) -> RegisterId { if register_type.is_permanent(self.db()) { return register; } - // 'ref Any' is used to pass values through the FFI without giving up - // ownership. Because the value could be anything, we don't increment - // ref counts. - if let Some(exp) = expected { - if exp.is_ref_any(self.db()) { - return register; - } - } - // Value types are always passed as a new value, whether the receiving // argument is owned or a reference. // @@ -3664,11 +3724,13 @@ impl<'a> LowerMethod<'a> { fn clone_value_type( &mut self, source: RegisterId, - typ: types::TypeRef, + typ: TypeRef, force_clone: bool, location: LocationId, ) -> RegisterId { - if self.register_kind(source).is_regular() && !force_clone { + if typ.is_permanent(self.db()) + || (self.register_kind(source).is_regular() && !force_clone) + { self.mark_register_as_moved(source); // Value types not bound to any variables/fields don't need to be @@ -3809,7 +3871,7 @@ impl<'a> LowerMethod<'a> { let self_reg = self.surrounding_type_register; let self_type = self.register_type(self_reg); - if !self_type.is_owned_or_uni(self.db()) + if !self.method.id.is_moving(self.db()) || self_type.is_permanent(self.db()) { return; @@ -3978,27 +4040,24 @@ impl<'a> LowerMethod<'a> { return; } - let flag = self.new_register(types::TypeRef::boolean()); + let flag = self.new_register(TypeRef::boolean()); self.current_block_mut().true_literal(flag, location); self.drop_flags.insert(register, flag); } - fn new_untracked_register( - &mut self, - value_type: types::TypeRef, - ) -> RegisterId { + fn new_untracked_register(&mut self, value_type: TypeRef) -> RegisterId { self.add_register(RegisterKind::Regular, value_type) } fn new_untracked_match_variable( &mut self, - value_type: types::TypeRef, + value_type: TypeRef, ) -> RegisterId { self.add_register(RegisterKind::MatchVariable, value_type) } - fn new_register(&mut self, value_type: types::TypeRef) -> RegisterId { + fn new_register(&mut self, value_type: TypeRef) -> RegisterId { let id = self.add_register(RegisterKind::Regular, value_type); self.scope.created.push(id); @@ -4036,14 +4095,14 @@ impl<'a> LowerMethod<'a> { fn new_field( &mut self, id: types::FieldId, - value_type: types::TypeRef, + value_type: TypeRef, ) -> RegisterId { // We don't track these registers in a scope, as fields are dropped at // the end of the surrounding method, unless they are moved. self.add_register(RegisterKind::Field(id), value_type) } - fn new_self(&mut self, value_type: types::TypeRef) -> RegisterId { + fn new_self(&mut self, value_type: TypeRef) -> RegisterId { let id = self.add_register(RegisterKind::SelfObject, value_type); self.scope.created.push(id); @@ -4053,7 +4112,7 @@ impl<'a> LowerMethod<'a> { fn add_register( &mut self, kind: RegisterKind, - value_type: types::TypeRef, + value_type: TypeRef, ) -> RegisterId { let id = self.method.registers.alloc(value_type); let block = self.current_block; @@ -4066,7 +4125,7 @@ impl<'a> LowerMethod<'a> { fn field_register( &mut self, id: types::FieldId, - value_type: types::TypeRef, + value_type: TypeRef, location: LocationId, ) -> RegisterId { if let Some(reg) = self.field_mapping.get(&id).cloned() { @@ -4080,7 +4139,7 @@ impl<'a> LowerMethod<'a> { val_reg } - fn register_type(&self, register: RegisterId) -> types::TypeRef { + fn register_type(&self, register: RegisterId) -> TypeRef { self.method.registers.value_type(register) } @@ -4252,7 +4311,7 @@ impl<'a> LowerMethod<'a> { self.method.id.receiver_id(self.db()) } - fn surrounding_type(&self) -> types::TypeRef { + fn surrounding_type(&self) -> TypeRef { self.register_type(self.surrounding_type_register) } @@ -4260,7 +4319,7 @@ impl<'a> LowerMethod<'a> { self.self_register != self.surrounding_type_register } - fn reduce_call(&mut self, returns: types::TypeRef, location: LocationId) { + fn reduce_call(&mut self, returns: TypeRef, location: LocationId) { // If the method never returns there's no point in generating a // reduction. Doing this can also break the LLVM code generation // process. @@ -4274,6 +4333,44 @@ impl<'a> LowerMethod<'a> { fn warn_unreachable(&mut self, location: &SourceLocation) { self.state.diagnostics.unreachable(self.file(), location.clone()); } + + fn cast_type(&self, typ: TypeRef) -> Option { + if let TypeRef::Pointer(_) = typ { + Some(CastType::Pointer) + } else { + match typ.type_id(self.db()) { + Ok(TypeId::Foreign(ForeignType::Int(8))) => { + Some(CastType::Int(8)) + } + Ok(TypeId::Foreign(ForeignType::Int(16))) => { + Some(CastType::Int(16)) + } + Ok(TypeId::Foreign(ForeignType::Int(32))) => { + Some(CastType::Int(32)) + } + Ok(TypeId::Foreign(ForeignType::Int(64))) => { + Some(CastType::Int(64)) + } + Ok(TypeId::Foreign(ForeignType::Float(32))) => { + Some(CastType::Float(32)) + } + Ok(TypeId::Foreign(ForeignType::Float(64))) => { + Some(CastType::Float(64)) + } + Ok(TypeId::ClassInstance(ins)) + if ins.instance_of() == ClassId::int() => + { + Some(CastType::InkoInt) + } + Ok(TypeId::ClassInstance(ins)) + if ins.instance_of() == ClassId::float() => + { + Some(CastType::InkoFloat) + } + _ => None, + } + } + } } /// A compiler pass that cleans up basic blocks. @@ -4519,24 +4616,9 @@ impl<'a> ExpandDrop<'a> { check.decrement_atomic(value, drop_id, after_id, location); - if self.method.registers.value_type(value).is_string(self.db) { - // Strings can be dropped directly instead of going through the - // dropper. - let reg = self.method.registers.alloc(types::TypeRef::nil()); - - self.block_mut(drop_id).call_builtin( - reg, - types::BuiltinFunction::StringDrop, - vec![value], - location, - ); - self.block_mut(drop_id).free(value, location); - } else { - // Atomic values can't be pattern matched into sub-values, so we can - // call the dropper unconditionally. - self.call_dropper(drop_id, value, location); - } - + // Atomic values can't be pattern matched into sub-values, so we can + // call the dropper unconditionally. + self.call_dropper(drop_id, value, location); self.block_mut(drop_id).goto(after_id, location); self.method.body.add_edge(before_id, drop_id); @@ -4549,7 +4631,7 @@ impl<'a> ExpandDrop<'a> { before_id: BlockId, after_id: BlockId, value: RegisterId, - value_type: types::TypeRef, + value_type: TypeRef, location: LocationId, ) { if value_type.use_atomic_reference_counting(self.db) { @@ -4680,7 +4762,7 @@ impl<'a> ExpandDrop<'a> { location: LocationId, ) { let typ = self.method.registers.value_type(value); - let reg = self.method.registers.alloc(types::TypeRef::nil()); + let reg = self.method.registers.alloc(TypeRef::nil()); if let Some(class) = typ.class_id(self.db) { // If the type of the receiver is statically known to be a class, we diff --git a/compiler/src/state.rs b/compiler/src/state.rs index 07448d70d..46262daa9 100644 --- a/compiler/src/state.rs +++ b/compiler/src/state.rs @@ -1,6 +1,7 @@ //! Compiler state accessible to compiler passes. use crate::config::Config; use crate::diagnostics::Diagnostics; +use std::collections::HashSet; use types::Database; /// State that is accessible by the compiler passes. @@ -11,6 +12,9 @@ pub(crate) struct State { pub(crate) config: Config, pub(crate) diagnostics: Diagnostics, pub(crate) db: Database, + + /// The C libraries to import. + pub(crate) libraries: HashSet, } impl State { @@ -18,6 +22,6 @@ impl State { let diagnostics = Diagnostics::new(); let db = Database::new(); - Self { config, diagnostics, db } + Self { config, diagnostics, db, libraries: HashSet::new() } } } diff --git a/compiler/src/test.rs b/compiler/src/test.rs index 4ad77d5dd..4c0bc9961 100644 --- a/compiler/src/test.rs +++ b/compiler/src/test.rs @@ -56,12 +56,8 @@ pub(crate) fn define_drop_trait(state: &mut State) { "drop.inko".into(), ); - let drop_trait = Trait::alloc( - &mut state.db, - DROP_TRAIT.to_string(), - module, - Visibility::Public, - ); + let drop_trait = + Trait::alloc(&mut state.db, DROP_TRAIT.to_string(), Visibility::Public); module.new_symbol( &mut state.db, diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index 0bd203cd0..1b623027e 100644 --- a/compiler/src/type_check/define_types.rs +++ b/compiler/src/type_check/define_types.rs @@ -47,6 +47,9 @@ impl<'a> DefineTypes<'a> { hir::TopLevelExpression::Class(ref mut node) => { self.define_class(node); } + hir::TopLevelExpression::ExternClass(ref mut node) => { + self.define_extern_class(node); + } hir::TopLevelExpression::Trait(ref mut node) => { self.define_trait(node); } @@ -123,13 +126,36 @@ impl<'a> DefineTypes<'a> { node.class_id = Some(id); } - fn define_trait(&mut self, node: &mut hir::DefineTrait) { + fn define_extern_class(&mut self, node: &mut hir::DefineExternClass) { + let name = node.name.name.clone(); let module = self.module; + let vis = Visibility::public(node.public); + let id = Class::alloc( + self.db_mut(), + name.clone(), + ClassKind::Extern, + vis, + module, + ); + + if self.module.symbol_exists(self.db(), &name) { + self.state.diagnostics.duplicate_symbol( + &name, + self.file(), + node.name.location.clone(), + ); + } else { + self.module.new_symbol(self.db_mut(), name, Symbol::Class(id)); + } + + node.class_id = Some(id); + } + + fn define_trait(&mut self, node: &mut hir::DefineTrait) { let name = node.name.name.clone(); let id = Trait::alloc( self.db_mut(), name.clone(), - module, Visibility::public(node.public), ); @@ -234,10 +260,10 @@ impl<'a> ImplementTraits<'a> { } }; - if class_id.kind(self.db()).is_async() { + if !class_id.allow_trait_implementations(self.db()) { self.state.diagnostics.error( DiagnosticId::InvalidImplementation, - "Traits can't be implemented for async classes", + "Traits can't be implemented for this class", self.file(), node.location.clone(), ); @@ -245,15 +271,6 @@ impl<'a> ImplementTraits<'a> { return; } - if class_id.kind(self.db()).is_extern() { - self.state.diagnostics.error( - DiagnosticId::InvalidImplementation, - "Traits can't be implemented for extern classes", - self.file(), - node.location.clone(), - ); - } - let bounds = define_type_bounds( self.state, self.module, @@ -481,6 +498,8 @@ impl<'a> CheckTraitImplementations<'a> { } /// A compiler pass that defines the fields for the runtime's result type. +/// +/// TODO: move this to `std::runtime` pub(crate) fn define_runtime_result_fields(state: &mut State) -> bool { let class = ClassId::result(); let module = class.module(&state.db); @@ -532,8 +551,14 @@ impl<'a> DefineFields<'a> { fn run(mut self, module: &mut hir::Module) { for expr in &mut module.expressions { - if let hir::TopLevelExpression::Class(ref mut node) = expr { - self.define_class(node); + match expr { + hir::TopLevelExpression::Class(ref mut node) => { + self.define_class(node); + } + hir::TopLevelExpression::ExternClass(ref mut node) => { + self.define_extern_class(node); + } + _ => (), } } } @@ -552,24 +577,11 @@ impl<'a> DefineFields<'a> { continue; }; - if is_main { - self.state.diagnostics.error( - DiagnosticId::InvalidSymbol, - format!( - "Fields can't be defined for the '{}' process", - MAIN_CLASS - ), - self.file(), - node.location.clone(), - ); - - break; - } + let name = node.name.name.clone(); - if is_enum { - self.state.diagnostics.error( - DiagnosticId::InvalidSymbol, - "Fields can't be defined for enum classes", + if is_main || is_enum { + self.state.diagnostics.fields_not_allowed( + &name, self.file(), node.location.clone(), ); @@ -591,12 +603,57 @@ impl<'a> DefineFields<'a> { break; } + if class_id.field(self.db(), &name).is_some() { + self.state.diagnostics.duplicate_field( + &name, + self.file(), + node.location.clone(), + ); + + continue; + } + + let vis = Visibility::public(node.public); + let rules = Rules { + allow_private_types: vis.is_private(), + ..Default::default() + }; + + let typ = DefineAndCheckTypeSignature::new( + self.state, + self.module, + &scope, + rules, + ) + .define_type(&mut node.value_type); + + if !class_id.is_public(self.db()) && vis == Visibility::Public { + self.state.diagnostics.public_field_private_class( + self.file(), + node.location.clone(), + ); + } + + let module = self.module; + let field = + class_id.new_field(self.db_mut(), name, id, typ, vis, module); + + id += 1; + node.field_id = Some(field); + } + } + + fn define_extern_class(&mut self, node: &mut hir::DefineExternClass) { + let class_id = node.class_id.unwrap(); + let mut id: usize = 0; + let scope = TypeScope::new(self.module, TypeId::Class(class_id), None); + + for node in &mut node.fields { let name = node.name.name.clone(); if class_id.field(self.db(), &name).is_some() { - self.state.diagnostics.error( - DiagnosticId::DuplicateSymbol, - format!("The field '{}' is already defined", name), + self.state.diagnostics.duplicate_field( + &name, self.file(), node.location.clone(), ); @@ -619,9 +676,7 @@ impl<'a> DefineFields<'a> { .define_type(&mut node.value_type); if !class_id.is_public(self.db()) && vis == Visibility::Public { - self.state.diagnostics.error( - DiagnosticId::InvalidSymbol, - "Public fields can't be defined for private types", + self.state.diagnostics.public_field_private_class( self.file(), node.location.clone(), ); @@ -1269,7 +1324,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let string = Class::alloc( @@ -1309,7 +1363,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let string = Class::alloc( @@ -1357,7 +1410,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let array = Class::alloc( @@ -1399,7 +1451,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let array = Class::alloc( @@ -1438,7 +1489,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); @@ -1465,7 +1515,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); @@ -1497,7 +1546,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let mut modules = parse(&mut state, "trait Debug: ToString {}"); @@ -1526,14 +1574,12 @@ mod tests { let to_str = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let to_str_ins = TraitInstance::new(to_str); let debug = Trait::alloc( &mut state.db, "Debug".to_string(), - ModuleId(0), Visibility::Private, ); let string = Class::alloc( @@ -1581,14 +1627,12 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - ModuleId(0), Visibility::Private, ); let to_string_ins = TraitInstance::new(to_string); let debug = Trait::alloc( &mut state.db, "Debug".to_string(), - ModuleId(0), Visibility::Private, ); let string = Class::alloc( @@ -1851,7 +1895,6 @@ mod tests { let debug = Trait::alloc( &mut state.db, "Debug".to_string(), - ModuleId(0), Visibility::Private, ); let mut modules = parse(&mut state, "class Array[T: Debug] {}"); @@ -1883,7 +1926,6 @@ mod tests { let debug = Trait::alloc( &mut state.db, "Debug".to_string(), - module, Visibility::Private, ); let mut modules = parse(&mut state, "trait ToArray[T: Debug] {}"); @@ -1915,7 +1957,6 @@ mod tests { let debug = Trait::alloc( &mut state.db, "Debug".to_string(), - module, Visibility::Private, ); @@ -1948,7 +1989,6 @@ mod tests { let debug = Trait::alloc( &mut state.db, "Debug".to_string(), - module, Visibility::Private, ); diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs index 15b7fe228..939399731 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -16,7 +16,7 @@ use types::{ Database, FieldId, FieldInfo, IdentifierKind, MethodId, MethodKind, MethodLookup, MethodSource, ModuleId, Receiver, Symbol, ThrowKind, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, TypeRef, Variable, - VariableId, CALL_METHOD, + VariableId, CALL_METHOD, DEREF_POINTER_FIELD, }; const IGNORE_VARIABLE: &str = "_"; @@ -1628,7 +1628,12 @@ impl<'a> CheckMethodBody<'a> { } node.class_id = Some(class); - node.resolved_type = TypeRef::Owned(TypeId::ClassInstance(ins)); + node.resolved_type = if ins.instance_of().kind(self.db()).is_extern() { + TypeRef::foreign_struct(class) + } else { + TypeRef::Owned(TypeId::ClassInstance(ins)) + }; + node.resolved_type } @@ -3154,7 +3159,11 @@ impl<'a> CheckMethodBody<'a> { } node.resolved_type = if expr.is_value_type(self.db()) { - expr + if expr.is_foreign_type(self.db()) { + expr.as_pointer(self.db()) + } else { + expr + } } else { expr.as_mut(self.db()) }; @@ -3239,19 +3248,40 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } MethodLookup::None => { - return if self.assign_field_with_receiver( + if self.assign_field_with_receiver( node, receiver, rec_id, value, scope, ) { - TypeRef::nil() - } else { - self.state.diagnostics.undefined_method( - &setter, - self.fmt(receiver), - self.file(), - node.location.clone(), - ); + return TypeRef::nil(); + } - TypeRef::Error + return match receiver { + TypeRef::Pointer(id) + if node.name.name == DEREF_POINTER_FIELD => + { + let exp = TypeRef::Owned(id); + + if !TypeChecker::check(self.db(), value, exp) { + self.state.diagnostics.type_error( + self.fmt(value), + self.fmt(exp), + self.file(), + node.location.clone(), + ); + } + + node.kind = CallKind::WritePointer; + TypeRef::nil() + } + _ => { + self.state.diagnostics.undefined_method( + &setter, + self.fmt(receiver), + self.file(), + node.location.clone(), + ); + + TypeRef::Error + } }; } }; @@ -3551,14 +3581,26 @@ impl<'a> CheckMethodBody<'a> { return typ; } - self.state.diagnostics.undefined_method( - &node.name.name, - self.fmt(receiver), - self.file(), - node.location.clone(), - ); + return match receiver { + TypeRef::Pointer(id) + if node.name.name == DEREF_POINTER_FIELD => + { + let ret = TypeRef::Owned(id); - return TypeRef::Error; + node.kind = CallKind::ReadPointer(ret); + ret + } + _ => { + self.state.diagnostics.undefined_method( + &node.name.name, + self.fmt(receiver), + self.file(), + node.location.clone(), + ); + + TypeRef::Error + } + }; } MethodLookup::None => { self.state.diagnostics.undefined_method( @@ -3741,10 +3783,10 @@ impl<'a> CheckMethodBody<'a> { .with_immutable(immutable) .resolve(raw_type); - if returns.is_value_type(self.db_mut()) { + if returns.is_value_type(self.db()) { returns = returns.as_owned(self.db_mut()); - } else if !immutable && raw_type.is_owned_or_uni(self.db_mut()) { - returns = returns.as_mut(self.db_mut()); + } else if !immutable && raw_type.is_owned_or_uni(self.db()) { + returns = returns.as_mut(self.db()); } if receiver.require_sendable_arguments(self.db()) { @@ -3815,11 +3857,25 @@ impl<'a> CheckMethodBody<'a> { ) .define_type(&mut node.cast_to); + if expr_type == cast_type { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + format!( + "Can't cast '{}' to itself", + format_type(self.db(), expr_type), + ), + self.file(), + node.location.clone(), + ); + + return TypeRef::Error; + } + // Casting to/from Any is dangerous but necessary to make the standard // library work. if !expr_type.is_any(self.db()) && !cast_type.is_any(self.db()) - && !TypeChecker::check(self.db_mut(), expr_type, cast_type) + && !TypeChecker::check_cast(self.db_mut(), expr_type, cast_type) { self.state.diagnostics.error( DiagnosticId::InvalidType, diff --git a/compiler/src/type_check/imports.rs b/compiler/src/type_check/imports.rs index 2aed965b7..2422091ca 100644 --- a/compiler/src/type_check/imports.rs +++ b/compiler/src/type_check/imports.rs @@ -153,6 +153,32 @@ impl<'a> DefineImportedTypes<'a> { } } +/// A compiler pass that collects all externally imported libraries. +pub(crate) struct CollectExternImports<'a> { + state: &'a mut State, +} + +impl<'a> CollectExternImports<'a> { + pub(crate) fn run_all( + state: &'a mut State, + modules: &[hir::Module], + ) -> bool { + for module in modules { + CollectExternImports { state }.run(module); + } + + !state.diagnostics.has_errors() + } + + fn run(self, module: &hir::Module) { + for expr in &module.expressions { + if let hir::TopLevelExpression::ExternImport(ref node) = expr { + self.state.libraries.insert(node.source.clone()); + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/src/type_check/methods.rs b/compiler/src/type_check/methods.rs index b28b2e3ed..f09ced030 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -24,11 +24,16 @@ fn method_kind(kind: hir::MethodKind) -> MethodKind { } } -fn receiver_type(type_id: TypeId, kind: hir::MethodKind) -> TypeRef { - match kind { - hir::MethodKind::Regular => TypeRef::Ref(type_id), - hir::MethodKind::Moving => TypeRef::Owned(type_id), - hir::MethodKind::Mutable => TypeRef::Mut(type_id), +fn receiver_type(db: &Database, id: TypeId, kind: hir::MethodKind) -> TypeRef { + match id { + TypeId::ClassInstance(ins) if ins.instance_of().is_value_type(db) => { + TypeRef::Owned(id) + } + _ => match kind { + hir::MethodKind::Regular => TypeRef::Ref(id), + hir::MethodKind::Moving => TypeRef::Owned(id), + hir::MethodKind::Mutable => TypeRef::Mut(id), + }, } } @@ -219,7 +224,7 @@ trait MethodDefiner { &mut self, method: MethodId, class_id: ClassId, - name: &String, + name: &str, location: &SourceLocation, ) { if class_id.method_exists(self.db(), name) { @@ -233,7 +238,7 @@ trait MethodDefiner { location.clone(), ); } else { - class_id.add_method(self.db_mut(), name.clone(), method); + class_id.add_method(self.db_mut(), name.to_string(), method); } } } @@ -265,8 +270,14 @@ impl<'a> DefineModuleMethodNames<'a> { fn run(mut self, module: &mut hir::Module) { for expr in module.expressions.iter_mut() { - if let hir::TopLevelExpression::ModuleMethod(ref mut node) = expr { - self.define_module_method(node); + match expr { + hir::TopLevelExpression::ModuleMethod(ref mut node) => { + self.define_module_method(node); + } + hir::TopLevelExpression::ExternFunction(ref mut node) => { + self.define_extern_function(node); + } + _ => (), } } } @@ -283,9 +294,8 @@ impl<'a> DefineModuleMethodNames<'a> { ); if self.module.symbol_exists(self.db(), name) { - self.state.diagnostics.error( - DiagnosticId::DuplicateSymbol, - format!("The module method '{}' is already defined", name), + self.state.diagnostics.duplicate_symbol( + name, self.file(), node.location.clone(), ); @@ -302,6 +312,35 @@ impl<'a> DefineModuleMethodNames<'a> { node.method_id = Some(method); } + fn define_extern_function(&mut self, node: &mut hir::DefineExternFunction) { + let name = &node.name.name; + let module = self.module; + let method = Method::alloc( + self.db_mut(), + module, + name.clone(), + Visibility::public(node.public), + MethodKind::Extern, + ); + + if self.module.symbol_exists(self.db(), name) { + self.state.diagnostics.duplicate_symbol( + name, + self.file(), + node.location.clone(), + ); + } else { + self.module.new_symbol( + self.db_mut(), + name.clone(), + Symbol::Method(method), + ); + } + + node.method_id = Some(method); + self.module.add_extern_method(self.db_mut(), method); + } + fn file(&self) -> PathBuf { self.module.file(self.db()) } @@ -345,6 +384,9 @@ impl<'a> DefineMethods<'a> { hir::TopLevelExpression::ModuleMethod(ref mut node) => { self.define_module_method(node); } + hir::TopLevelExpression::ExternFunction(ref mut node) => { + self.define_extern_function(node); + } hir::TopLevelExpression::Reopen(ref mut node) => { self.reopen_class(node); } @@ -514,6 +556,31 @@ impl<'a> DefineMethods<'a> { ); } + fn define_extern_function(&mut self, node: &mut hir::DefineExternFunction) { + let self_type = TypeId::Module(self.module); + let func = node.method_id.unwrap(); + let scope = TypeScope::new(self.module, self_type, None); + let rules = Rules { + allow_private_types: func.is_private(self.db()), + ..Default::default() + }; + + for arg in &mut node.arguments { + let name = arg.name.name.clone(); + let typ = self.type_check(&mut arg.value_type, rules, &scope); + + func.new_argument(self.db_mut(), name, typ, typ); + } + + let ret = node + .return_type + .as_mut() + .map(|node| self.type_check(node, rules, &scope)) + .unwrap_or_else(TypeRef::nil); + + func.set_return_type(self.db_mut(), ret); + } + fn define_static_method( &mut self, class_id: ClassId, @@ -635,7 +702,7 @@ impl<'a> DefineMethods<'a> { class_id, &bounds, )); - let receiver = receiver_type(self_type, node.kind); + let receiver = receiver_type(self.db(), self_type, node.kind); method.set_receiver(self.db_mut(), receiver); @@ -790,7 +857,7 @@ impl<'a> DefineMethods<'a> { trait_id, &bounds, )); - let receiver = receiver_type(self_type, node.kind); + let receiver = receiver_type(self.db(), self_type, node.kind); method.set_receiver(self.db_mut(), receiver); @@ -857,7 +924,7 @@ impl<'a> DefineMethods<'a> { trait_id, &bounds, )); - let receiver = receiver_type(self_type, node.kind); + let receiver = receiver_type(self.db(), self_type, node.kind); method.set_receiver(self.db_mut(), receiver); @@ -1210,7 +1277,7 @@ impl<'a> ImplementTraitMethods<'a> { || method.is_private(self.db()), ..Default::default() }; - let receiver = receiver_type(self_type, node.kind); + let receiver = receiver_type(self.db(), self_type, node.kind); method.set_receiver(self.db_mut(), receiver); method.set_source( diff --git a/compiler/src/type_check/mod.rs b/compiler/src/type_check/mod.rs index 9351a266b..932253be6 100644 --- a/compiler/src/type_check/mod.rs +++ b/compiler/src/type_check/mod.rs @@ -2,6 +2,7 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; +use ast::source_location::SourceLocation; use std::path::PathBuf; use types::check::TypeChecker; use types::format::format_type; @@ -243,6 +244,9 @@ impl<'a> DefineTypeSignature<'a> { } match symbol { + Symbol::Class(id) if id.kind(&self.state.db).is_extern() => { + TypeRef::foreign_struct(id) + } Symbol::Class(id) => { kind.into_type_ref(self.define_class_instance(id, node)) } @@ -284,11 +288,10 @@ impl<'a> DefineTypeSignature<'a> { } "Any" => match kind { RefKind::Owned => TypeRef::Any, - RefKind::Ref | RefKind::Mut => TypeRef::RefAny, - RefKind::Uni => { + _ => { self.state.diagnostics.error( DiagnosticId::InvalidType, - "'uni Any' isn't a valid type", + "'Any' can only be used as an owned type", self.file(), node.location.clone(), ); @@ -296,14 +299,16 @@ impl<'a> DefineTypeSignature<'a> { return TypeRef::Error; } }, - _ => { - self.state.diagnostics.undefined_symbol( + name => { + if let Some(ctype) = self.resolve_foreign_type( name, - self.file(), - node.name.location.clone(), - ); - - return TypeRef::Error; + &node.arguments, + &node.location, + ) { + ctype + } else { + TypeRef::Error + } } } }; @@ -467,6 +472,96 @@ impl<'a> DefineTypeSignature<'a> { Some(targs) } + fn resolve_foreign_type( + &mut self, + name: &str, + arguments: &[hir::Type], + location: &SourceLocation, + ) -> Option { + match name { + "Int8" => Some(TypeRef::foreign_int(8)), + "Int16" => Some(TypeRef::foreign_int(16)), + "Int32" => Some(TypeRef::foreign_int(32)), + "Int64" => Some(TypeRef::foreign_int(64)), + "Float32" => Some(TypeRef::foreign_float(32)), + "Float64" => Some(TypeRef::foreign_float(64)), + "Pointer" => { + if arguments.len() != 1 { + self.state.diagnostics.incorrect_number_of_type_arguments( + 1, + arguments.len(), + self.file(), + location.clone(), + ); + + return None; + } + + let arg = if let hir::Type::Named(n) = &arguments[0] { + self.resolve_foreign_type( + &n.name.name, + &n.arguments, + &n.location, + ) + } else { + None + }?; + + // Pointers to Inko objects make no sense, as they're already + // represented as pointers. + if !arg.is_foreign_type(self.db()) { + self.state.diagnostics.invalid_c_type( + &format_type(self.db(), arg), + self.file(), + location.clone(), + ); + } + + match arg { + TypeRef::Owned(v) => Some(TypeRef::Pointer(v)), + TypeRef::Pointer(_) => { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + "Nested pointers (e.g. 'Pointer[Pointer[Int8]]') \ + aren't supported, you should use regular \ + pointers instead", + self.file(), + location.clone(), + ); + + None + } + _ => Some(arg), + } + } + name => match self.scope.symbol(self.db(), name) { + Some(Symbol::Class(id)) + if id.kind(&self.state.db).is_extern() => + { + Some(TypeRef::foreign_struct(id)) + } + Some(_) => { + self.state.diagnostics.invalid_c_type( + name, + self.file(), + location.clone(), + ); + + None + } + _ => { + self.state.diagnostics.undefined_symbol( + name, + self.file(), + location.clone(), + ); + + None + } + }, + } + } + fn db(&self) -> &Database { &self.state.db } @@ -611,12 +706,9 @@ impl<'a> CheckTypeSignature<'a> { } if given != required { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "Incorrect number of type arguments: expected {}, found {}", - required, given - ), + self.state.diagnostics.incorrect_number_of_type_arguments( + required, + given, self.file(), node.location.clone(), ); @@ -910,7 +1002,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - module, Visibility::Private, ); @@ -1229,7 +1320,6 @@ mod tests { let to_string = Trait::alloc( &mut state.db, "ToString".to_string(), - module, Visibility::Private, ); let list_class = Class::alloc( diff --git a/docs/source/guides/syntax.md b/docs/source/guides/syntax.md index 28585ca63..e2d4efa50 100644 --- a/docs/source/guides/syntax.md +++ b/docs/source/guides/syntax.md @@ -770,8 +770,8 @@ Type casting is done using `as` like so: expression as TypeName ``` -The `as` keyword has a higher precedence than the binary and logical operators, -meaning that this: +The `as` keyword has the same precedence as binary operators. This means that +this: ```inko 10 + 5 as ToString @@ -782,3 +782,15 @@ Is parsed as this: ```inko (10 + 5) as ToString ``` + +And this: + +```inko +foo as Int + 5 as Foo +``` + +Is parsed as this: + +```inko +(foo as Int + 5) as Foo +``` diff --git a/inko/src/command/build.rs b/inko/src/command/build.rs index 4e17bafad..a0605d2be 100644 --- a/inko/src/command/build.rs +++ b/inko/src/command/build.rs @@ -59,6 +59,7 @@ pub(crate) fn run(arguments: &[String]) -> Result { "none,balanced,aggressive", ); + options.optflag("", "static", "Statically link imported C libraries"); options.optflag("", "dot", "Output the MIR of every module as DOT files"); let matches = options.parse(arguments)?; @@ -86,6 +87,10 @@ pub(crate) fn run(arguments: &[String]) -> Result { config.dot = true; } + if matches.opt_present("static") { + config.static_linking = true; + } + for path in matches.opt_strs("i") { config.sources.add(path.into()); } diff --git a/inko/src/command/run.rs b/inko/src/command/run.rs index e17fa8a42..b9122e414 100644 --- a/inko/src/command/run.rs +++ b/inko/src/command/run.rs @@ -46,6 +46,8 @@ pub(crate) fn run(arguments: &[String]) -> Result { "PATH", ); + options.optflag("", "static", "Statically link imported C libraries"); + let matches = options.parse(arguments)?; if matches.opt_present("h") { @@ -65,6 +67,10 @@ pub(crate) fn run(arguments: &[String]) -> Result { config.sources.add(path.into()); } + if matches.opt_present("static") { + config.static_linking = true; + } + let time = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_else(|_| Duration::from_secs(0)) diff --git a/rt/src/immutable_string.rs b/rt/src/immutable_string.rs index 99e55e470..2e35c6416 100644 --- a/rt/src/immutable_string.rs +++ b/rt/src/immutable_string.rs @@ -45,6 +45,11 @@ impl ImmutableString { pub(crate) fn len(&self) -> usize { self.bytes.len() - 1 } + + /// Returns a pointer to the bytes, including the NULL byte. + pub(crate) fn as_ptr(&self) -> *const u8 { + self.bytes.as_ptr() as *const _ + } } impl Deref for ImmutableString { diff --git a/rt/src/mem.rs b/rt/src/mem.rs index 586a98efa..f252f678d 100644 --- a/rt/src/mem.rs +++ b/rt/src/mem.rs @@ -1,5 +1,4 @@ use crate::immutable_string::ImmutableString; -use crate::process::Process; use std::alloc::{alloc, alloc_zeroed, dealloc, handle_alloc_error, Layout}; use std::mem::{align_of, size_of, swap}; use std::ops::Deref; @@ -221,25 +220,19 @@ impl Class { /// Returns a new class for a regular object. pub(crate) fn object( name: RustString, - fields: u8, + size: u32, methods: u16, ) -> ClassPointer { - let size = - size_of::
() + (fields as usize * size_of::<*mut u8>()); - - Self::alloc(name, methods, size as u32) + Self::alloc(name, methods, size) } /// Returns a new class for a process. pub(crate) fn process( name: RustString, - fields: u8, + size: u32, methods: u16, ) -> ClassPointer { - let size = - size_of::() + (fields as usize * size_of::<*mut u8>()); - - Self::alloc(name, methods, size as u32) + Self::alloc(name, methods, size) } /// Returns the `Layout` for a class itself. @@ -605,7 +598,7 @@ mod tests { #[test] fn test_class_new_object() { - let class = Class::object("A".to_string(), 1, 0); + let class = Class::object("A".to_string(), 24, 0); assert_eq!(class.method_slots, 0); assert_eq!(class.instance_size, 24); @@ -615,9 +608,10 @@ mod tests { #[test] fn test_class_new_process() { - let class = Class::process("A".to_string(), 1, 0); + let class = Class::process("A".to_string(), 24, 0); assert_eq!(class.method_slots, 0); + assert_eq!(class.instance_size, 24); unsafe { Class::drop(class) }; } diff --git a/rt/src/result.rs b/rt/src/result.rs index 11d5ca467..ba70e04ba 100644 --- a/rt/src/result.rs +++ b/rt/src/result.rs @@ -2,9 +2,6 @@ use crate::mem::tagged_int; use std::io; use std::ptr::null_mut; -const INVALID_INPUT: i64 = 11; -const TIMED_OUT: i64 = 13; - const OK: i64 = 0; const NONE: i64 = 1; const ERROR: i64 = 2; @@ -50,27 +47,7 @@ impl Result { } pub(crate) fn io_error(error: io::Error) -> Result { - let code = match error.kind() { - io::ErrorKind::NotFound => 1, - io::ErrorKind::PermissionDenied => 2, - io::ErrorKind::ConnectionRefused => 3, - io::ErrorKind::ConnectionReset => 4, - io::ErrorKind::ConnectionAborted => 5, - io::ErrorKind::NotConnected => 6, - io::ErrorKind::AddrInUse => 7, - io::ErrorKind::AddrNotAvailable => 8, - io::ErrorKind::BrokenPipe => 9, - io::ErrorKind::AlreadyExists => 10, - io::ErrorKind::InvalidInput => INVALID_INPUT, - io::ErrorKind::InvalidData => 12, - io::ErrorKind::TimedOut => TIMED_OUT, - io::ErrorKind::WriteZero => 14, - io::ErrorKind::Interrupted => 15, - io::ErrorKind::UnexpectedEof => 16, - _ => 0, - }; - - Self::error(tagged_int(code) as _) + Self::error(tagged_int(error.raw_os_error().unwrap_or(0) as _) as _) } } diff --git a/rt/src/runtime/array.rs b/rt/src/runtime/array.rs index e9d4785f3..76fc70699 100644 --- a/rt/src/runtime/array.rs +++ b/rt/src/runtime/array.rs @@ -23,12 +23,10 @@ pub unsafe extern "system" fn inko_array_new_permanent( #[no_mangle] pub unsafe extern "system" fn inko_array_reserve( - state: *const State, array: *mut Array, length: i64, -) -> *const Nil { +) { (*array).value.reserve_exact(length as _); - (*state).nil_singleton } #[no_mangle] @@ -97,12 +95,8 @@ pub unsafe extern "system" fn inko_array_capacity( } #[no_mangle] -pub unsafe extern "system" fn inko_array_clear( - state: *const State, - array: *mut Array, -) -> *const Nil { +pub unsafe extern "system" fn inko_array_clear(array: *mut Array) { (*array).value.clear(); - (*state).nil_singleton } #[no_mangle] diff --git a/rt/src/runtime/byte_array.rs b/rt/src/runtime/byte_array.rs index 0be580dfd..b4c9bd682 100644 --- a/rt/src/runtime/byte_array.rs +++ b/rt/src/runtime/byte_array.rs @@ -1,5 +1,5 @@ use crate::immutable_string::ImmutableString; -use crate::mem::{tagged_int, Bool, ByteArray, Int, Nil, String as InkoString}; +use crate::mem::{tagged_int, Bool, ByteArray, Int, String as InkoString}; use crate::state::State; use std::cmp::min; @@ -12,12 +12,10 @@ pub unsafe extern "system" fn inko_byte_array_new( #[no_mangle] pub unsafe extern "system" fn inko_byte_array_push( - state: *const State, bytes: *mut ByteArray, value: i64, -) -> *const Nil { +) { (*bytes).value.push(value as u8); - (*state).nil_singleton } #[no_mangle] @@ -85,12 +83,8 @@ pub unsafe extern "system" fn inko_byte_array_eq( } #[no_mangle] -pub unsafe extern "system" fn inko_byte_array_clear( - state: *const State, - bytes: *mut ByteArray, -) -> *const Nil { +pub unsafe extern "system" fn inko_byte_array_clear(bytes: *mut ByteArray) { (*bytes).value.clear(); - (*state).nil_singleton } #[no_mangle] @@ -102,12 +96,8 @@ pub unsafe extern "system" fn inko_byte_array_clone( } #[no_mangle] -pub unsafe extern "system" fn inko_byte_array_drop( - state: *const State, - array: *mut ByteArray, -) -> *const Nil { +pub unsafe extern "system" fn inko_byte_array_drop(array: *mut ByteArray) { ByteArray::drop(array); - (*state).nil_singleton } #[no_mangle] @@ -150,12 +140,10 @@ pub unsafe extern "system" fn inko_byte_array_slice( #[no_mangle] pub unsafe extern "system" fn inko_byte_array_append( - state: *const State, target: *mut ByteArray, source: *mut ByteArray, -) -> *const Nil { +) { (*target).value.append(&mut (*source).value); - (*state).nil_singleton } #[no_mangle] @@ -178,11 +166,9 @@ pub unsafe extern "system" fn inko_byte_array_copy_from( #[no_mangle] pub unsafe extern "system" fn inko_byte_array_resize( - state: *const State, bytes: *mut ByteArray, size: i64, filler: i64, -) -> *const Nil { +) { (*bytes).value.resize(size as usize, filler as u8); - (*state).nil_singleton } diff --git a/rt/src/runtime/class.rs b/rt/src/runtime/class.rs index 228a3cda0..26427b1e6 100644 --- a/rt/src/runtime/class.rs +++ b/rt/src/runtime/class.rs @@ -4,25 +4,25 @@ use std::{ffi::CStr, os::raw::c_char}; #[no_mangle] pub unsafe extern "system" fn inko_class_object( name: *const c_char, - fields: u8, + size: u32, methods: u16, ) -> ClassPointer { let name = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned(); - Class::object(name, fields, methods) + Class::object(name, size, methods) } #[no_mangle] pub unsafe extern "system" fn inko_class_process( name: *const c_char, - fields: u8, + size: u32, methods: u16, ) -> ClassPointer { let name = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned(); - Class::process(name, fields, methods) + Class::process(name, size, methods) } #[no_mangle] diff --git a/rt/src/runtime/general.rs b/rt/src/runtime/general.rs index 4fc8785ed..51a83fa2f 100644 --- a/rt/src/runtime/general.rs +++ b/rt/src/runtime/general.rs @@ -4,6 +4,7 @@ use crate::process::ProcessPointer; use crate::runtime::exit; use crate::runtime::process::panic; use std::alloc::alloc; +use std::io::Error; #[no_mangle] pub unsafe extern "system" fn inko_exit(status: i64) { @@ -72,3 +73,8 @@ pub unsafe extern "system" fn inko_alloc(class: ClassPointer) -> *mut u8 { header_of(ptr).init(class); ptr } + +#[no_mangle] +pub unsafe extern "system" fn inko_last_error() -> i32 { + Error::last_os_error().raw_os_error().unwrap_or(0) +} diff --git a/rt/src/runtime/process.rs b/rt/src/runtime/process.rs index f335c92c3..dbc99cc2d 100644 --- a/rt/src/runtime/process.rs +++ b/rt/src/runtime/process.rs @@ -227,7 +227,7 @@ pub unsafe extern "system" fn inko_channel_send( mut process: ProcessPointer, channel: *const Channel, message: *mut u8, -) -> *const Nil { +) { let state = &*state; loop { @@ -245,8 +245,6 @@ pub unsafe extern "system" fn inko_channel_send( } } } - - state.nil_singleton } #[no_mangle] @@ -322,20 +320,15 @@ pub unsafe extern "system" fn inko_channel_receive_until( } #[no_mangle] -pub unsafe extern "system" fn inko_channel_drop( - state: *const State, - channel: *mut Channel, -) -> *const Nil { +pub unsafe extern "system" fn inko_channel_drop(channel: *mut Channel) { Channel::drop(channel); - (*state).nil_singleton } #[no_mangle] pub unsafe extern "system" fn inko_channel_wait( - state: *const State, process: ProcessPointer, channels: *mut Array, -) -> *const Nil { +) { let channels = &mut *channels; let mut guards = Vec::with_capacity(channels.value.len()); @@ -344,7 +337,7 @@ pub unsafe extern "system" fn inko_channel_wait( let guard = chan.state.lock().unwrap(); if guard.has_messages() { - return (*state).nil_singleton; + return; } guards.push(guard); @@ -373,6 +366,4 @@ pub unsafe extern "system" fn inko_channel_wait( chan.state.lock().unwrap().remove_waiting_for_message(process); } - - (*state).nil_singleton } diff --git a/rt/src/runtime/string.rs b/rt/src/runtime/string.rs index ec6cbc08b..2ef3e59e1 100644 --- a/rt/src/runtime/string.rs +++ b/rt/src/runtime/string.rs @@ -1,11 +1,12 @@ use crate::mem::{ - tagged_int, Array, Bool, ByteArray, Float, Int, Nil, String as InkoString, + tagged_int, Array, Bool, ByteArray, Float, Int, String as InkoString, }; use crate::process::ProcessPointer; use crate::result::Result as InkoResult; use crate::runtime::process::panic; use crate::state::State; use std::cmp::min; +use std::ffi::{c_char, CStr}; use std::slice; use unicode_segmentation::{Graphemes, UnicodeSegmentation}; @@ -75,12 +76,8 @@ pub unsafe extern "system" fn inko_string_byte( } #[no_mangle] -pub unsafe extern "system" fn inko_string_drop( - state: *const State, - pointer: *const InkoString, -) -> *const Nil { +pub unsafe extern "system" fn inko_string_drop(pointer: *const InkoString) { InkoString::drop(pointer); - (*state).nil_singleton } #[no_mangle] @@ -212,12 +209,8 @@ pub unsafe extern "system" fn inko_string_characters_next( } #[no_mangle] -pub unsafe extern "system" fn inko_string_characters_drop( - state: *const State, - iter: *mut u8, -) -> *const Nil { +pub unsafe extern "system" fn inko_string_characters_drop(iter: *mut u8) { drop(Box::from_raw(iter as *mut Graphemes)); - (*state).nil_singleton } #[no_mangle] @@ -255,3 +248,20 @@ pub unsafe extern "system" fn inko_string_slice_bytes( InkoString::alloc((*state).string_class, new_string) } + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_pointer( + string: *const InkoString, +) -> *const u8 { + (*string).value.as_ptr() +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_from_pointer( + state: *const State, + ptr: *const c_char, +) -> *const InkoString { + let val = CStr::from_ptr(ptr).to_string_lossy().into_owned(); + + InkoString::alloc((*state).string_class, val) +} diff --git a/rt/src/scheduler/process.rs b/rt/src/scheduler/process.rs index 6635496fe..514c582b0 100644 --- a/rt/src/scheduler/process.rs +++ b/rt/src/scheduler/process.rs @@ -1186,7 +1186,7 @@ mod tests { #[test] fn test_monitor_check_threads() { let scheduler = Scheduler::new(2, 2, 32); - let mut monitor = Monitor::new(&*scheduler.pool); + let mut monitor = Monitor::new(&scheduler.pool); assert!(!monitor.check_threads()); @@ -1214,7 +1214,7 @@ mod tests { #[test] fn test_monitor_update_epoch() { let scheduler = Scheduler::new(1, 1, 32); - let mut monitor = Monitor::new(&*scheduler.pool); + let mut monitor = Monitor::new(&scheduler.pool); assert_eq!(monitor.epoch, START_EPOCH); assert_eq!(scheduler.pool.epoch.load(Ordering::Acquire), START_EPOCH); @@ -1228,7 +1228,7 @@ mod tests { #[test] fn test_monitor_sleep() { let scheduler = Scheduler::new(1, 1, 32); - let monitor = Monitor::new(&*scheduler.pool); + let monitor = Monitor::new(&scheduler.pool); let start = Instant::now(); scheduler.pool.monitor.status.store(MonitorStatus::Notified); @@ -1241,7 +1241,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_termination() { let scheduler = Scheduler::new(1, 1, 32); - let monitor = Monitor::new(&*scheduler.pool); + let monitor = Monitor::new(&scheduler.pool); scheduler.terminate(); monitor.deep_sleep(); @@ -1252,7 +1252,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_notification() { let scheduler = Scheduler::new(1, 1, 32); - let monitor = Monitor::new(&*scheduler.pool); + let monitor = Monitor::new(&scheduler.pool); let _ = scope(|s| { s.spawn(|_| monitor.deep_sleep()); @@ -1274,7 +1274,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_blocked_threads() { let scheduler = Scheduler::new(1, 1, 32); - let monitor = Monitor::new(&*scheduler.pool); + let monitor = Monitor::new(&scheduler.pool); scheduler.pool.threads[0].blocked_at.store(1, Ordering::Release); monitor.deep_sleep(); diff --git a/rt/src/test.rs b/rt/src/test.rs index 53a2e1a98..2f4750da2 100644 --- a/rt/src/test.rs +++ b/rt/src/test.rs @@ -4,7 +4,7 @@ use crate::mem::{Class, ClassPointer}; use crate::process::{NativeAsyncMethod, Process, ProcessPointer}; use crate::stack::Stack; use crate::state::{MethodCounts, RcState, State}; -use std::mem::forget; +use std::mem::{forget, size_of}; use std::ops::{Deref, DerefMut, Drop}; /// Processes normally drop themselves when they finish running. But in tests we @@ -102,5 +102,9 @@ pub(crate) fn empty_class(name: &str) -> OwnedClass { } pub(crate) fn empty_process_class(name: &str) -> OwnedClass { - OwnedClass::new(Class::process(name.to_string(), 0, 0)) + OwnedClass::new(Class::process( + name.to_string(), + size_of::() as _, + 0, + )) } diff --git a/std/src/std/array.inko b/std/src/std/array.inko index 0c27781c9..14ecff73d 100644 --- a/std/src/std/array.inko +++ b/std/src/std/array.inko @@ -8,6 +8,21 @@ import std::iter::(Enum, Iter) import std::option::Option import std::rand::Shuffle +class extern AnyResult { + let @tag: Int + let @value: Any +} + +fn extern inko_array_reserve(array: Any, length: Int) +fn extern inko_array_set(array: Any, index: Int, value: Any) -> Any +fn extern inko_array_get(array: Any, index: Int) -> Any +fn extern inko_array_clear(array: Any) +fn extern inko_array_pop(array: Any) -> AnyResult +fn extern inko_array_remove(array: Any, index: Int) -> Any +fn extern inko_array_capacity(state: Pointer[Int8], array: Any) -> Int +fn extern inko_array_drop(state: Pointer[Int8], array: Any) +fn extern inko_array_length(state: Pointer[Int8], array: Any) -> Int + fn stable_sort[T: Compare[T]]( array: mut Array[T], compare: mut fn (ref T, ref T) -> Bool, @@ -24,7 +39,10 @@ fn stable_sort[T: Compare[T]]( # values. This works because merge() doesn't perform any bounds checking. let tmp: Array[T] = Array.with_capacity(len) - len.times fn (i) { _INKO.array_set(tmp, i, _INKO.array_get(array, i)) } + len.times fn (i) { + inko_array_set(tmp, i, inko_array_get(array, i)) + } + merge_sort(tmp, array, start: 0, end: len, compare: compare) } @@ -51,10 +69,10 @@ fn merge_sort[T: Compare[T]]( if i < mid and (j >= end or compare.call(a.get_unchecked(i), a.get_unchecked(j))) { - _INKO.array_set(b, k, _INKO.array_get(a, i)) + inko_array_set(b, k, inko_array_get(a, i)) i += 1 } else { - _INKO.array_set(b, k, _INKO.array_get(a, j)) + inko_array_set(b, k, inko_array_get(a, j)) j += 1 } @@ -95,7 +113,7 @@ class builtin Array[T] { fn pub static with_capacity(amount: Int) -> Array[T] { let vals = [] - _INKO.array_reserve(vals, amount) + inko_array_reserve(vals, amount) vals } @@ -131,10 +149,10 @@ class builtin Array[T] { let max = length while index < max { - _INKO.array_get(self, index := index + 1) as T + inko_array_get(self, (index := index + 1)) as T } - _INKO.array_clear(self) + inko_array_clear(self) } # Pushes a value to the back of the Array. @@ -171,7 +189,7 @@ class builtin Array[T] { # # array.pop # => Option.None fn pub mut pop -> Option[T] { - match _INKO.array_pop(self) { + match inko_array_pop(self) { case { @tag = 0, @value = v } -> Option.Some(v as T) case _ -> Option.None } @@ -194,7 +212,7 @@ class builtin Array[T] { # This method panics if the index is out of bounds. fn pub mut remove_at(index: Int) -> T { bounds_check(index, length) - _INKO.array_remove(self, index) as T + inko_array_remove(self, index) as T } # Returns an optional immutable reference to the value at the given index. @@ -253,7 +271,7 @@ class builtin Array[T] { # array # => [10] fn pub mut set(index: Int, value: T) { bounds_check(index, length) - _INKO.array_set(self, index, value) as T + inko_array_set(self, index, value) as T _INKO.moved(value) } @@ -272,7 +290,7 @@ class builtin Array[T] { fn pub mut swap(index: Int, with: T) -> T { bounds_check(index, length) - let result = _INKO.array_set(self, index, with) as T + let result = inko_array_set(self, index, with) as T _INKO.moved(with) result @@ -353,7 +371,7 @@ class builtin Array[T] { # # [10].length # => 1 fn pub length -> Int { - _INKO.array_length(self) + inko_array_length(_INKO.state, self) } # Returns the number of values that can be stored in `self` before `self` @@ -363,7 +381,7 @@ class builtin Array[T] { # # Array.with_capacity(2).capacity # => 4 fn pub capacity -> Int { - _INKO.array_capacity(self) + inko_array_capacity(_INKO.state, self) } # Returns `true` if `self` is empty. @@ -402,17 +420,16 @@ class builtin Array[T] { let mut b = length - 1 while a < b { - let a_val = _INKO.array_get(self, a) - - _INKO.array_set(self, a, _INKO.array_set(self, b, a_val)) + let a_val = inko_array_get(self, a) + inko_array_set(self, a, inko_array_set(self, b, a_val)) a += 1 b -= 1 } } fn get_unchecked(index: Int) -> ref T { - (ref _INKO.array_get(self, index)) as ref T + (ref inko_array_get(self, index)) as ref T } } @@ -460,7 +477,7 @@ impl Array if T: mut { } fn mut get_unchecked_mut(index: Int) -> mut T { - (mut _INKO.array_get(self, index)) as mut T + (mut inko_array_get(self, index)) as mut T } } @@ -468,9 +485,11 @@ impl Drop for Array { fn mut drop { let mut index = 0 - while index < length { _INKO.array_get(self, index := index + 1) as T } + while index < length { + inko_array_get(self, index := index + 1) as T + } - _INKO.array_drop(self) + inko_array_drop(_INKO.state, self) } } @@ -604,11 +623,11 @@ class pub IntoIter[T] { let @index: Int fn mut take_next -> T { - _INKO.array_get(@array, @index := @index + 1) as T + inko_array_get(@array, @index := @index + 1) as T } fn length -> Int { - _INKO.array_length(@array) + inko_array_length(_INKO.state, @array) } } @@ -616,7 +635,7 @@ impl Drop for IntoIter { fn mut drop { while @index < length { take_next } - _INKO.array_drop(@array) + inko_array_drop(_INKO.state, @array) } } diff --git a/std/src/std/byte_array.inko b/std/src/std/byte_array.inko index 467329afb..383de0916 100644 --- a/std/src/std/byte_array.inko +++ b/std/src/std/byte_array.inko @@ -10,6 +10,62 @@ import std::iter::(Bytes as BytesTrait, EOF, Enum, Iter) import std::option::Option import std::string::(IntoString, ToString) +fn extern inko_byte_array_new(state: Pointer[Int8]) -> ByteArray +fn extern inko_byte_array_clear(bytes: mut ByteArray) +fn extern inko_byte_array_append(target: mut ByteArray, source: mut ByteArray) +fn extern inko_byte_array_clone( + state: Pointer[Int8], + bytes: ref ByteArray, +) -> ByteArray + +fn extern inko_byte_array_copy_from( + state: Pointer[Int8], + target: mut ByteArray, + source: ref ByteArray, + start: Int, + length: Int, +) -> Int + +fn extern inko_byte_array_drain_to_string( + state: Pointer[Int8], + bytes: mut ByteArray +) -> String + +fn extern inko_byte_array_drop(bytes: mut ByteArray) +fn extern inko_byte_array_eq( + state: Pointer[Int8], + left: ref ByteArray, + right: ref ByteArray, +) -> Bool + +fn extern inko_byte_array_get(bytes: ref ByteArray, index: Int) -> Int +fn extern inko_byte_array_length( + state: Pointer[Int8], + bytes: ref ByteArray, +) -> Int + +fn extern inko_byte_array_push(bytes: mut ByteArray, byte: Int) +fn extern inko_byte_array_pop(bytes: mut ByteArray) -> Int +fn extern inko_byte_array_remove(bytes: mut ByteArray, index: Int) -> Int +fn extern inko_byte_array_resize(bytes: mut ByteArray, size: Int, filler: Int) +fn extern inko_byte_array_set( + bytes: mut ByteArray, + index: Int, + value: Int, +) -> Int + +fn extern inko_byte_array_slice( + state: Pointer[Int8], + bytes: ref ByteArray, + start: Int, + length: Int +) -> ByteArray + +fn extern inko_byte_array_to_string( + state: Pointer[Int8], + bytes: ref ByteArray, +) -> String + # A type from which a new `ByteArray` can be created. trait pub ToByteArray { fn pub to_byte_array -> ByteArray @@ -32,7 +88,7 @@ trait pub IntoByteArray { class builtin ByteArray { # Returns a new empty `ByteArray`. fn pub static new -> ByteArray { - _INKO.byte_array_new + inko_byte_array_new(_INKO.state) } # Returns a new `ByteArray` created from the given `Array`. @@ -74,7 +130,7 @@ class builtin ByteArray { fn pub mut clear { # Bytes always fit in a tagged pointer, so there's no need to run any # destructors them. - _INKO.byte_array_clear(self) + inko_byte_array_clear(self) } # Appends the bytes of the given `ByteArray` to `self`. @@ -87,7 +143,7 @@ class builtin ByteArray { # a.append(b) # a # => ByteArray.from_array([10, 20]) fn pub mut append(other: ByteArray) { - _INKO.byte_array_append(self, other) + inko_byte_array_append(self, other) } # Pushes a value to the back of the `ByteArray`, returning the pushed value. @@ -101,7 +157,7 @@ class builtin ByteArray { # bytes.push(10) # => 10 # bytes.length # => 1 fn pub mut push(value: Int) { - _INKO.byte_array_push(self, value) + inko_byte_array_push(self, value) _INKO.moved(value) } @@ -125,7 +181,7 @@ class builtin ByteArray { # # bytes.pop # => Option.None fn pub mut pop -> Option[Int] { - match _INKO.byte_array_pop(self) { + match inko_byte_array_pop(self) { case -1 -> Option.None case val -> Option.Some(val) } @@ -147,7 +203,7 @@ class builtin ByteArray { # This method panics if the index is out of bounds. fn pub mut remove_at(index: Int) -> Int { bounds_check(index, length) - _INKO.byte_array_remove(self, index) + inko_byte_array_remove(self, index) } # Returns a new `String` using the bytes in this `ByteArray`, draining it in @@ -166,7 +222,7 @@ class builtin ByteArray { # bytes.drain_to_string # => 'inko' # bytes.empty? # => True fn pub mut drain_to_string -> String { - _INKO.byte_array_drain_to_string(self) + inko_byte_array_drain_to_string(_INKO.state, self) } # Slices `self` into a new `ByteArray`. @@ -186,7 +242,7 @@ class builtin ByteArray { # sliced[1] # => 3 fn pub slice(start: Int, length: Int) -> ByteArray { bounds_check(start, self.length) - _INKO.byte_array_slice(self, start, length) + inko_byte_array_slice(_INKO.state, self, start, length) } # Returns the byte at the given index, returning None if the index is out of @@ -208,7 +264,7 @@ class builtin ByteArray { fn pub opt(index: Int) -> Option[Int] { if index < 0 or index >= length { return Option.None } - Option.Some(_INKO.byte_array_get(self, index)) + Option.Some(inko_byte_array_get(self, index)) } # Returns the byte at the given index. @@ -226,7 +282,7 @@ class builtin ByteArray { # bytes[0] # => 10 fn pub get(index: Int) -> Int { bounds_check(index, length) - _INKO.byte_array_get(self, index) + inko_byte_array_get(self, index) } # Stores a byte at the given index, then returns it. @@ -245,7 +301,7 @@ class builtin ByteArray { # bytes[0] # => 30 fn pub mut set(index: Int, value: Int) { bounds_check(index, length) - _INKO.byte_array_set(self, index, value) + inko_byte_array_set(self, index, value) _INKO.moved(value) } @@ -258,7 +314,7 @@ class builtin ByteArray { # ByteArray.new.length # => 0 # ByteArray.from_array([10]).length # => 1 fn pub length -> Int { - _INKO.byte_array_length(self) + inko_byte_array_length(_INKO.state, self) } # Returns `true` if `self` is empty. @@ -302,10 +358,9 @@ class builtin ByteArray { let mut b = length - 1 while a < b { - let a_val = _INKO.byte_array_get(self, a) - - _INKO.byte_array_set(self, a, _INKO.byte_array_set(self, b, a_val)) + let a_val = inko_byte_array_get(self, a) + inko_byte_array_set(self, a, inko_byte_array_set(self, b, a_val)) a += 1 b -= 1 } @@ -326,7 +381,7 @@ class builtin ByteArray { # # b # => ByteArray.from_array([1, 2]) fn pub mut copy_from(bytes: ref ByteArray, at: Int, length: Int) -> Int { - _INKO.byte_array_copy_from(self, bytes, at, length) + inko_byte_array_copy_from(_INKO.state, self, bytes, at, length) } # Resizes `self` to the new length. @@ -351,7 +406,7 @@ class builtin ByteArray { fn pub mut resize(length: Int, value: Int) { if length < 0 { panic('The new length must be greater than zero') } - _INKO.byte_array_resize(self, length, value) + inko_byte_array_resize(self, length, value) } } @@ -359,9 +414,9 @@ impl Drop for ByteArray { fn mut drop { let mut index = 0 - while index < length { _INKO.byte_array_get(self, index := index + 1) } + while index < length { inko_byte_array_get(self, index := index + 1) } - _INKO.byte_array_drop(self) + inko_byte_array_drop(self) } } @@ -391,7 +446,7 @@ impl ToString for ByteArray { # # bytes.to_string # => 'inko' fn pub to_string -> String { - _INKO.byte_array_to_string(self) + inko_byte_array_to_string(_INKO.state, self) } } @@ -414,13 +469,13 @@ impl Equal[ByteArray] for ByteArray { # ByteArray.from_array([10]) == ByteArray.from_array([10]) # => True # ByteArray.from_array([10]) == ByteArray.from_array([20]) # => False fn pub ==(other: ref ByteArray) -> Bool { - _INKO.byte_array_eq(self, other) + inko_byte_array_eq(_INKO.state, self, other) } } impl Clone[ByteArray] for ByteArray { fn pub clone -> ByteArray { - _INKO.byte_array_clone(self) + inko_byte_array_clone(_INKO.state, self) } } @@ -478,7 +533,7 @@ impl Iter[Int] for Bytes { impl BytesTrait for Bytes { fn pub mut next_byte -> Int { if @index < @bytes.length { - _INKO.byte_array_get(@bytes, @index := @index + 1) + inko_byte_array_get(@bytes, @index := @index + 1) } else { EOF } diff --git a/std/src/std/channel.inko b/std/src/std/channel.inko index a3709d32c..d21481b63 100644 --- a/std/src/std/channel.inko +++ b/std/src/std/channel.inko @@ -3,6 +3,35 @@ import std::clone::Clone import std::drop::Drop import std::time::Instant +class extern AnyResult { + let @tag: Int + let @value: Any +} + +fn extern inko_channel_drop(channel: Any) +fn extern inko_channel_new(state: Pointer[Int8], size: Int) -> Any +fn extern inko_channel_receive(process: Pointer[Int8], channel: Any) -> Any +fn extern inko_channel_receive_until( + state: Pointer[Int8], + process: Pointer[Int8], + channel: Any, + time: Int, +) -> AnyResult + +fn extern inko_channel_send( + state: Pointer[Int8], + process: Pointer[Int8], + channel: Any, + message: Any, +) + +fn extern inko_channel_try_receive( + process: Pointer[Int8], + channel: Any, +) -> AnyResult + +fn extern inko_channel_wait(process: Pointer[Int8], channels: Any) + # Blocks the current process until one or more of the given channels have a # message. # @@ -44,7 +73,7 @@ import std::time::Instant # chan2.send(1) # wait([chan1, chan2]) fn pub wait[T](channels: ref Array[Channel[T]]) { - _INKO.channel_wait(channels) + inko_channel_wait(_INKO.process, channels) } # A multi-producer, multi-consumer FIFO queue. @@ -62,7 +91,7 @@ class builtin Channel[T] { # # If you specify a value less than 1, the size is set to 1. fn pub static new(size: Int) -> Channel[uni T] { - _INKO.channel_new(size) as Channel[uni T] + inko_channel_new(_INKO.state, size) as Channel[uni T] } # Sends a message to the channel. @@ -77,7 +106,7 @@ class builtin Channel[T] { # chan.send(1) # chan.send(2) fn pub send(value: uni T) { - _INKO.channel_send(self, value) + inko_channel_send(_INKO.state, _INKO.process, self, value) _INKO.moved(value) } @@ -92,7 +121,7 @@ class builtin Channel[T] { # chan.send(1) # chan.receive # => 1 fn pub receive -> uni T { - _INKO.channel_receive(self) as uni T + inko_channel_receive(_INKO.process, self) as uni T } # Receives a message from the channel without blocking the sender. @@ -108,7 +137,7 @@ class builtin Channel[T] { # chan.send(1) # chan.try_receive # => Option.Some(1) fn pub try_receive -> Option[uni T] { - match _INKO.channel_try_receive(self) { + match inko_channel_try_receive(_INKO.process, self) { case { @tag = 0, @value = v } -> Option.Some(v as uni T) case _ -> Option.None } @@ -127,7 +156,9 @@ class builtin Channel[T] { # chan.send(1) # chan.receive_until(deadline: Instant.new + duration) # => Option.Some(1) fn pub receive_until(deadline: ref Instant) -> Option[uni T] { - match _INKO.channel_receive_until(self, deadline.to_int) { + match inko_channel_receive_until( + _INKO.state, _INKO.process, self, deadline.to_int + ) { case { @tag = 0, @value = v } -> Option.Some(v as uni T) case _ -> Option.None } @@ -143,11 +174,13 @@ impl Clone[Channel[T]] for Channel { impl Drop for Channel { fn mut drop { loop { - match _INKO.channel_try_receive(self) { + match inko_channel_try_receive(_INKO.process, self) { # The value is dropped at the end of this scope. case { @tag = 0, @value = v } -> v as T case _ -> break } } + + inko_channel_drop(self) } } diff --git a/std/src/std/env.inko b/std/src/std/env.inko index 9148f45b3..39e9de6c3 100644 --- a/std/src/std/env.inko +++ b/std/src/std/env.inko @@ -117,7 +117,7 @@ fn pub temporary_directory -> Path { fn pub working_directory -> Result[Path, Error] { match _INKO.env_get_working_directory { case { @tag = 0, @value = val } -> Result.Ok(Path.new(val as String)) - case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + case { @tag = _, @value = err } -> Result.Error(Error.from_os_error(err as Int)) } } @@ -133,7 +133,7 @@ fn pub working_directory -> Result[Path, Error] { fn pub working_directory=(directory: ref ToString) -> Result[Nil, Error] { match _INKO.env_set_working_directory(directory.to_string) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + case { @tag = _, @value = err } -> Result.Error(Error.from_os_error(err as Int)) } } @@ -157,6 +157,6 @@ fn pub arguments -> Array[String] { fn pub executable -> Result[Path, Error] { match _INKO.env_executable { case { @tag = 0, @value = val } -> Result.Ok(Path.new(val as String)) - case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + case { @tag = _, @value = err } -> Result.Error(Error.from_os_error(err as Int)) } } diff --git a/std/src/std/float.inko b/std/src/std/float.inko index 7dcd465bf..74b5fd110 100644 --- a/std/src/std/float.inko +++ b/std/src/std/float.inko @@ -203,7 +203,7 @@ class builtin Float { impl ToInt for Float { fn pub to_int -> Int { - _INKO.float_to_int(self) + self as Int } } diff --git a/std/src/std/fs/dir.inko b/std/src/std/fs/dir.inko index c35bedd7a..6a4b75beb 100644 --- a/std/src/std/fs/dir.inko +++ b/std/src/std/fs/dir.inko @@ -22,7 +22,7 @@ import std::string::ToString fn pub create(path: ref ToString) -> Result[Nil, Error] { match _INKO.directory_create(path.to_string) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -45,7 +45,7 @@ fn pub create(path: ref ToString) -> Result[Nil, Error] { fn pub create_all(path: ref ToString) -> Result[Nil, Error] { match _INKO.directory_create_recursive(path.to_string) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -70,7 +70,7 @@ fn pub create_all(path: ref ToString) -> Result[Nil, Error] { fn pub remove(path: ref ToString) -> Result[Nil, Error] { match _INKO.directory_remove(path.to_string) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -94,7 +94,7 @@ fn pub remove(path: ref ToString) -> Result[Nil, Error] { fn pub remove_all(path: ref ToString) -> Result[Nil, Error] { match _INKO.directory_remove_recursive(path.to_string) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -122,6 +122,6 @@ fn pub list(path: ref ToString) -> Result[Array[Path], Error] { case { @tag = 0, @value = v } -> Result.Ok( (v as Array[String]).into_iter.map fn (path) { Path.new(path) }.to_array ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } diff --git a/std/src/std/fs/file.inko b/std/src/std/fs/file.inko index 3e3a18cd3..1e3b9e6f1 100644 --- a/std/src/std/fs/file.inko +++ b/std/src/std/fs/file.inko @@ -37,7 +37,7 @@ let FILE_READ_APPEND = 4 fn pub remove(path: ref ToString) -> Result[Nil, Error] { match _INKO.file_remove(path.to_string) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -57,7 +57,7 @@ fn pub remove(path: ref ToString) -> Result[Nil, Error] { fn pub copy(from: ref ToString, to: ref ToString) -> Result[Int, Error] { match _INKO.file_copy(from.to_string, to.to_string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -85,7 +85,7 @@ class pub ReadOnlyFile { case { @tag = 0, @value = v } -> Result.Ok( ReadOnlyFile { @path = path, @fd = v } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -100,7 +100,7 @@ impl Read for ReadOnlyFile { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.file_read(@fd, into, size) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -109,7 +109,7 @@ impl Seek for ReadOnlyFile { fn pub mut seek(position: Int) -> Result[Int, Error] { match _INKO.file_seek(@fd, position) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -118,7 +118,7 @@ impl Size for ReadOnlyFile { fn pub size -> Result[Int, Error] { match _INKO.file_size(@path.to_string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -145,7 +145,7 @@ class pub WriteOnlyFile { case { @tag = 0, @value = v } -> Result.Ok( WriteOnlyFile { @path = path, @fd = v } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -163,7 +163,7 @@ class pub WriteOnlyFile { case { @tag = 0, @value = v } -> Result.Ok( WriteOnlyFile { @path = path, @fd = v } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -178,21 +178,21 @@ impl Write for WriteOnlyFile { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.file_write_bytes(@fd, bytes) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut write_string(string: String) -> Result[Int, Error] { match _INKO.file_write_string(@fd, string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut flush -> Result[Nil, Error] { match _INKO.file_flush(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -201,7 +201,7 @@ impl Seek for WriteOnlyFile { fn pub mut seek(position: Int) -> Result[Int, Error] { match _INKO.file_seek(@fd, position) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -228,7 +228,7 @@ class pub ReadWriteFile { case { @tag = 0, @value = v } -> Result.Ok( ReadWriteFile { @path = path, @fd = v } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -246,7 +246,7 @@ class pub ReadWriteFile { case { @tag = 0, @value = v } -> Result.Ok( ReadWriteFile { @path = path, @fd = v } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -261,7 +261,7 @@ impl Read for ReadWriteFile { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.file_read(@fd, into, size) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -270,21 +270,21 @@ impl Write for ReadWriteFile { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.file_write_bytes(@fd, bytes) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut write_string(string: String) -> Result[Int, Error] { match _INKO.file_write_string(@fd, string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut flush -> Result[Nil, Error] { match _INKO.file_flush(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -293,7 +293,7 @@ impl Seek for ReadWriteFile { fn pub mut seek(position: Int) -> Result[Int, Error] { match _INKO.file_seek(@fd, position) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -302,7 +302,7 @@ impl Size for ReadWriteFile { fn pub size -> Result[Int, Error] { match _INKO.file_size(@path.to_string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } diff --git a/std/src/std/fs/path.inko b/std/src/std/fs/path.inko index cacab3a32..97f0e8da6 100644 --- a/std/src/std/fs/path.inko +++ b/std/src/std/fs/path.inko @@ -109,7 +109,7 @@ class pub Path { case { @tag = 0, @value = val } -> Result.Ok( DateTime.from_timestamp(val as Float, _INKO.time_system_offset) ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -129,7 +129,7 @@ class pub Path { case { @tag = 0, @value = val } -> Result.Ok( DateTime.from_timestamp(val as Float, _INKO.time_system_offset) ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -149,7 +149,7 @@ class pub Path { case { @tag = 0, @value = val } -> Result.Ok( DateTime.from_timestamp(val as Float, _INKO.time_system_offset) ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -270,7 +270,7 @@ class pub Path { fn pub expand -> Result[Path, Error] { match _INKO.path_expand(@path) { case { @tag = 0, @value = v } -> Result.Ok(Path.new(v as String)) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -346,7 +346,7 @@ impl Size for Path { fn pub size -> Result[Int, Error] { match _INKO.file_size(@path) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } diff --git a/std/src/std/int.inko b/std/src/std/int.inko index b72fc699b..169d67e83 100644 --- a/std/src/std/int.inko +++ b/std/src/std/int.inko @@ -23,6 +23,20 @@ let pub MIN = -9_223_372_036_854_775_808 # The maximum value an `Int` can represent. let pub MAX = 9_223_372_036_854_775_807 +class extern IntResult { + let @tag: Int + let @value: Int +} + +fn extern inko_string_to_int( + state: Pointer[Int8], + process: Pointer[Int8], + string: String, + radix: Int, + start: Int, + end: Int, +) -> IntResult + # A 64-bits signed integer type. # # `Int` values can represent values in the range @@ -42,8 +56,8 @@ class builtin Int { # Int.from_base2('11') # => Option.Some(3) # Int.from_base2('ff') # => Option.None fn pub static from_base2(string: String) -> Option[Int] { - match _INKO.string_to_int(string, 2, -1, -1) { - case { @tag = 0, @value = v } -> Option.Some(v as Int) + match inko_string_to_int(_INKO.state, _INKO.process, string, 2, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v) case _ -> Option.None } } @@ -58,8 +72,8 @@ class builtin Int { # Int.from_base10('12') # => Option.Some(12) # Int.from_base10('ff') # => Option.None fn pub static from_base10(string: String) -> Option[Int] { - match _INKO.string_to_int(string, 10, -1, -1) { - case { @tag = 0, @value = v } -> Option.Some(v as Int) + match inko_string_to_int(_INKO.state, _INKO.process, string, 10, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v) case _ -> Option.None } } @@ -78,8 +92,8 @@ class builtin Int { # Int.from_base16('ef') # => Option.Some(239) # Int.from_base16('zz') # => Option.None fn pub static from_base16(string: String) -> Option[Int] { - match _INKO.string_to_int(string, 16, -1, -1) { - case { @tag = 0, @value = v } -> Option.Some(v as Int) + match inko_string_to_int(_INKO.state, _INKO.process, string, 16, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v) case _ -> Option.None } } @@ -299,7 +313,7 @@ impl ToInt for Int { impl ToFloat for Int { fn pub to_float -> Float { - _INKO.int_to_float(self) + self as Float } } diff --git a/std/src/std/io.inko b/std/src/std/io.inko index 748104730..d86d85e06 100644 --- a/std/src/std/io.inko +++ b/std/src/std/io.inko @@ -5,6 +5,7 @@ import std::cmp::Equal import std::fmt::(Format, Formatter) import std::string::ToString +import std::sys::linux::errors # The initial number of bytes to read in `Read.read_all` let INITIAL_READ_ALL_SIZE = 1024 @@ -12,161 +13,195 @@ let INITIAL_READ_ALL_SIZE = 1024 # The maximum number of bytes to read when using `Read.read_all`. let MAX_READ_ALL_SIZE = 1024 * 1024 -# An IO error. +fn extern inko_last_error -> Int32 + +# An error type for I/O operations. # -# `Error` is thrown whenever an IO operation resulted in an error, such as when -# trying to access a non-existing file. +# This type is typically constructed from raw OS error codes such as `ENOENT` on +# Unix systems. This enum doesn't define a variant for every possible error. +# Instead, we define a variant for the most commonly used errors, and represent +# other errors using the `Other` variant. class pub enum Error { - case Other - case NotFound - case PermissionDenied - case ConnectionRefused - case ConnectionReset - case ConnectionAborted - case NotConnected + # The address is already in use. case AddressInUse + + # The address isn't available. case AddressUnavailable - case BrokenPipe + + # A connection is already established. + case AlreadyConnected + + # A resource already exists. case AlreadyExists - case InvalidInput - case InvalidData - case TimedOut - case WriteZero + + # The operation failed because a pipe was closed. + case BrokenPipe + + # The connection was aborted by the remote server. + case ConnectionAborted + + # The connection was refused by the remote server. + case ConnectionRefused + + # The connection was reset by the remote server. + case ConnectionReset + + # An operation would result in a deadlock. + case Deadlock + + # A directory isn't empty. + case DirectoryNotEmpty + + # A file is too large. + case FileTooLarge + + # The remote host is unreachable. + case HostUnreachable + + # The operation is in progress. + case InProgress + + # The operation was interrupted. case Interrupted - case UnexpectedEof - # Returns a new `Error` according to the given error code. - fn pub static from_int(code: Int) -> Error { + # One or more arguments are invalid. + case InvalidArgument + + # The file name is invalid. + case InvalidFileName + + # The seek operation is invalid. + case InvalidSeek + + # The resource is a directory. + case IsADirectory + + # The network is down. + case NetworkDown + + # The network is unreachable. + case NetworkUnreachable + + # The resource isn't a directory. + case NotADirectory + + # A connection isn't established. + case NotConnected + + # The resource isn't found. + case NotFound + + # The operation failed because not enough memory could be allocated. + case OutOfMemory + + # The operation failed because it lacked the necessary privileges. + case PermissionDenied + + # The filesystem is read-only. + case ReadOnlyFilesystem + + # The resource is busy. + case ResourceBusy + + # The underlying storage is full. + case StorageFull + + # The operation timed out. + case TimedOut + + # The operation would block. + case WouldBlock + + # An error not covered by the other variants. + # + # The wrapped `Int` is the raw error code. + case Other(Int) + + # Returns an `Error` from a raw OS error code. + # + # # Examples + # + # import std::unix::errors::ENOENT + # + # Error.from_os_error(ENOENT) # => Error.NotFound + fn pub static from_os_error(code: Int) -> Error { match code { - case 0 -> Other - case 1 -> NotFound - case 2 -> PermissionDenied - case 3 -> ConnectionRefused - case 4 -> ConnectionReset - case 5 -> ConnectionAborted - case 6 -> NotConnected - case 7 -> AddressInUse - case 8 -> AddressUnavailable - case 9 -> BrokenPipe - case 10 -> AlreadyExists - case 11 -> InvalidInput - case 12 -> InvalidData - case 13 -> TimedOut - case 14 -> WriteZero - case 15 -> Interrupted - case 16 -> UnexpectedEof - case _ -> Other + case errors::EPERM -> Error.PermissionDenied + case errors::ENOENT -> Error.NotFound + case errors::EINTR -> Error.Interrupted + case errors::EAGAIN -> Error.WouldBlock + case errors::ENOMEM -> Error.OutOfMemory + case errors::EACCES -> Error.PermissionDenied + case errors::EBUSY -> Error.ResourceBusy + case errors::EEXIST -> Error.AlreadyExists + case errors::ENOTDIR -> Error.NotADirectory + case errors::EISDIR -> Error.IsADirectory + case errors::EINVAL -> Error.InvalidArgument + case errors::EFBIG -> Error.FileTooLarge + case errors::ENOSPC -> Error.StorageFull + case errors::ESPIPE -> Error.InvalidSeek + case errors::EROFS -> Error.ReadOnlyFilesystem + case errors::EPIPE -> Error.BrokenPipe + case errors::EDEADLK -> Error.Deadlock + case errors::ENAMETOOLONG -> Error.InvalidFileName + case errors::ENOTEMPTY -> Error.DirectoryNotEmpty + case errors::ETIME -> Error.TimedOut + case errors::EADDRINUSE -> Error.AddressInUse + case errors::EADDRNOTAVAIL -> Error.AddressUnavailable + case errors::ENETDOWN -> Error.NetworkDown + case errors::ENETUNREACH -> Error.NetworkUnreachable + case errors::ECONNABORTED -> Error.ConnectionAborted + case errors::ECONNRESET -> Error.ConnectionReset + case errors::EISCONN -> Error.AlreadyConnected + case errors::ENOTCONN -> Error.NotConnected + case errors::ETIMEDOUT -> Error.TimedOut + case errors::ECONNREFUSED -> Error.ConnectionRefused + case errors::EHOSTUNREACH -> Error.HostUnreachable + case errors::EINPROGRESS -> Error.InProgress + case val -> Error.Other(val) } } -} -impl ToString for Error { - fn pub to_string -> String { - match self { - case NotFound -> 'The resource could not be found' - case PermissionDenied -> { - 'The operation lacked the necessary privileges to complete' - } - case ConnectionRefused -> { - 'The connection was refused by the remote server' - } - case ConnectionReset -> 'The connection was reset by the remote server' - case ConnectionAborted -> { - 'The connection was terminated by the remote server' - } - case NotConnected -> { - "The operation failed because the connection hasn't been established" - } - case AddressInUse -> 'The address is already in use' - case AddressUnavailable -> 'The address is not available' - case BrokenPipe -> 'The operation failed because a pipe was closed' - case AlreadyExists -> 'The resource already exists' - case InvalidInput -> 'An input parameter is invalid' - case InvalidData -> 'The supplied data is not valid for this operation' - case TimedOut -> 'The operation timed out' - case WriteZero -> { - 'The operation failed because not enough bytes were written' - } - case Interrupted -> 'The operation was interrupted' - case UnexpectedEof -> { - 'The operation failed because of an unexpected end-of-file' - } - case Other -> 'An unknown IO error occurred' - } + # Returns the last OS error produced by the current OS thread. + fn pub static last_os_error -> Error { + from_os_error(inko_last_error as Int) } } -impl Equal[Error] for Error { - fn pub ==(other: ref Error) -> Bool { +impl ToString for Error { + fn pub to_string -> String { match self { - case Other -> match other { - case Other -> true - case _ -> false - } - case NotFound -> match other { - case NotFound -> true - case _ -> false - } - case PermissionDenied -> match other { - case PermissionDenied -> true - case _ -> false - } - case ConnectionRefused -> match other { - case ConnectionRefused -> true - case _ -> false - } - case ConnectionReset -> match other { - case ConnectionReset -> true - case _ -> false - } - case ConnectionAborted -> match other { - case ConnectionAborted -> true - case _ -> false - } - case NotConnected -> match other { - case NotConnected -> true - case _ -> false - } - case AddressInUse -> match other { - case AddressInUse -> true - case _ -> false - } - case AddressUnavailable -> match other { - case AddressUnavailable -> true - case _ -> false - } - case BrokenPipe -> match other { - case BrokenPipe -> true - case _ -> false - } - case AlreadyExists -> match other { - case AlreadyExists -> true - case _ -> false - } - case InvalidInput -> match other { - case InvalidInput -> true - case _ -> false - } - case InvalidData -> match other { - case InvalidData -> true - case _ -> false - } - case TimedOut -> match other { - case TimedOut -> true - case _ -> false - } - case WriteZero -> match other { - case WriteZero -> true - case _ -> false - } - case Interrupted -> match other { - case Interrupted -> true - case _ -> false - } - case UnexpectedEof -> match other { - case UnexpectedEof -> true - case _ -> false - } + case AddressInUse -> 'the address is already in use' + case AddressUnavailable -> "the address isn't available" + case AlreadyConnected -> 'the connection is already established' + case AlreadyExists -> 'the resource already exists' + case BrokenPipe -> 'the operation failed because a pipe was closed' + case ConnectionAborted -> 'the connection was terminated by the server' + case ConnectionRefused -> 'the connection was refused by the server' + case ConnectionReset -> 'the connection was reset by the server' + case Deadlock -> 'the resource would deadlock' + case DirectoryNotEmpty -> "the directory isn't empty" + case FileTooLarge -> 'the file is too large' + case HostUnreachable -> 'the host is unreachable' + case InProgress -> 'the operation is in progress' + case Interrupted -> 'the operation was interrupted' + case InvalidArgument -> 'one or more arguments are invalid' + case InvalidFileName -> 'the file name is too long' + case InvalidSeek -> 'the seek operation is invalid' + case IsADirectory -> 'the resource is a directory' + case NetworkDown -> 'the network is down' + case NetworkUnreachable -> 'the network is unreachable' + case NotADirectory -> "the resource isn't a directory" + case NotConnected -> "a connection isn't established" + case NotFound -> "the resource isn't found" + case OutOfMemory -> 'we ran out of memory' + case PermissionDenied -> 'the operation lacks the necessary privileges' + case ReadOnlyFilesystem -> 'the file system is read-only' + case ResourceBusy -> 'the resource is busy' + case StorageFull -> 'the storage is full' + case TimedOut -> 'the operation timed out' + case WouldBlock -> 'the operation would block' + case Other(code) -> "an other error with code {code} occurred" } } } @@ -174,29 +209,87 @@ impl Equal[Error] for Error { impl Format for Error { fn pub fmt(formatter: mut Formatter) { let string = match self { - case Other -> 'Other' - case NotFound -> 'NotFound' - case PermissionDenied -> 'PermissionDenied' - case ConnectionRefused -> 'ConnectionRefused' - case ConnectionReset -> 'ConnectionReset' - case ConnectionAborted -> 'ConnectionAborted' - case NotConnected -> 'NotConnected' case AddressInUse -> 'AddressInUse' case AddressUnavailable -> 'AddressUnavailable' - case BrokenPipe -> 'BrokenPipe' + case AlreadyConnected -> 'AlreadyConnected' case AlreadyExists -> 'AlreadyExists' - case InvalidInput -> 'InvalidInput' - case InvalidData -> 'InvalidData' - case TimedOut -> 'TimedOut' - case WriteZero -> 'WriteZero' + case BrokenPipe -> 'BrokenPipe' + case ConnectionAborted -> 'ConnectionAborted' + case ConnectionRefused -> 'ConnectionRefused' + case ConnectionReset -> 'ConnectionReset' + case Deadlock -> 'Deadlock' + case DirectoryNotEmpty -> 'DirectoryNotEmpty' + case FileTooLarge -> 'FileTooLarge' + case HostUnreachable -> 'HostUnreachable' + case InProgress -> 'InProgress' case Interrupted -> 'Interrupted' - case UnexpectedEof -> 'UnexpectedEof' + case InvalidArgument -> 'InvalidArgument' + case InvalidFileName -> 'InvalidFileName' + case InvalidSeek -> 'InvalidSeek' + case IsADirectory -> 'IsADirectory' + case NetworkDown -> 'NetworkDown' + case NetworkUnreachable -> 'NetworkUnreachable' + case NotADirectory -> 'NotADirectory' + case NotConnected -> 'NotConnected' + case NotFound -> 'NotFound' + case OutOfMemory -> 'OutOfMemory' + case PermissionDenied -> 'PermissionDenied' + case ReadOnlyFilesystem -> 'ReadOnlyFilesystem' + case ResourceBusy -> 'ResourceBusy' + case StorageFull -> 'StorageFull' + case TimedOut -> 'TimedOut' + case WouldBlock -> 'WouldBlock' + case Other(code) -> { + formatter.write('Other(') + code.fmt(formatter) + formatter.write(')') + return + } } formatter.write(string) } } +impl Equal[Error] for Error { + fn pub ==(other: ref Error) -> Bool { + match (self, other) { + case (AddressInUse, AddressInUse) -> true + case (AddressUnavailable, AddressUnavailable) -> true + case (AlreadyConnected, AlreadyConnected) -> true + case (AlreadyExists, AlreadyExists) -> true + case (BrokenPipe, BrokenPipe) -> true + case (ConnectionAborted, ConnectionAborted) -> true + case (ConnectionRefused, ConnectionRefused) -> true + case (ConnectionReset, ConnectionReset) -> true + case (Deadlock, Deadlock) -> true + case (DirectoryNotEmpty, DirectoryNotEmpty) -> true + case (FileTooLarge, FileTooLarge) -> true + case (HostUnreachable, HostUnreachable) -> true + case (InProgress, InProgress) -> true + case (Interrupted, Interrupted) -> true + case (InvalidArgument, InvalidArgument) -> true + case (InvalidFileName, InvalidFileName) -> true + case (InvalidSeek, InvalidSeek) -> true + case (IsADirectory, IsADirectory) -> true + case (NetworkDown, NetworkDown) -> true + case (NetworkUnreachable, NetworkUnreachable) -> true + case (NotADirectory, NotADirectory) -> true + case (NotConnected, NotConnected) -> true + case (NotFound, NotFound) -> true + case (OutOfMemory, OutOfMemory) -> true + case (PermissionDenied, PermissionDenied) -> true + case (ReadOnlyFilesystem, ReadOnlyFilesystem) -> true + case (ResourceBusy, ResourceBusy) -> true + case (StorageFull, StorageFull) -> true + case (TimedOut, TimedOut) -> true + case (WouldBlock, WouldBlock) -> true + case (Other(a), Other(b)) -> a == b + case _ -> false + } + } +} + # Trait for retrieving the size of an IO object. trait pub Size { fn pub size -> Result[Int, Error] diff --git a/std/src/std/json.inko b/std/src/std/json.inko index 52ac58263..e7fde3937 100644 --- a/std/src/std/json.inko +++ b/std/src/std/json.inko @@ -63,6 +63,21 @@ import std::fmt::(Format, Formatter) import std::string::(StringBuffer, ToString) import std::utf8 +class extern IntResult { + let @tag: Int + let @value: Int +} + +fn extern inko_string_byte(string: String, index: Int) -> Int +fn extern inko_string_to_int( + state: Pointer[Int8], + process: Pointer[Int8], + string: String, + radix: Int, + start: Int, + end: Int, +) -> IntResult + let EOF = -1 let BRACKET_OPEN = 0x5B let BRACKET_CLOSE = 0x5D @@ -506,8 +521,10 @@ class pub Parser { throw error("Expected four hexadecimal digits, but we ran out of input") } - match _INKO.string_to_int(@string, 16, start, @index) { - case { @tag = 0, @value = v } -> Result.Ok(v as Int) + match inko_string_to_int( + _INKO.state, _INKO.process, @string, 16, start, @index + ) { + case { @tag = 0, @value = v } -> Result.Ok(v) case _ -> Result.Error( error("'{slice_string(start)}' is an invalid Unicode codepoint") ) @@ -607,10 +624,12 @@ class pub Parser { # number parser. As part of parsing the JSON number we already validate # it. This means we can bypass `Int.from_base10` (and `Float.parse` # below), and instead use the underlying runtime functions. - match _INKO.string_to_int(@string, 10, start, @index) { + match inko_string_to_int( + _INKO.state, _INKO.process, @string, 10, start, @index + ) { # If the number is too big to fit in an integer, we'll promote the # number to a float. - case { @tag = 0, @value = v } -> return Result.Ok(Json.Int(v as Int)) + case { @tag = 0, @value = v } -> return Result.Ok(Json.Int(v)) case _ -> {} } } @@ -645,9 +664,7 @@ class pub Parser { fn current -> Int { if @index < @size { - # We use a raw instruction here to avoid the overhead of bounds checking; - # something not yet optimised away at the time of writing. - _INKO.string_byte(@string, @index) + inko_string_byte(@string, @index) } else { EOF } @@ -656,7 +673,7 @@ class pub Parser { fn peek -> Int { let index = @index + 1 - if index < @size { _INKO.string_byte(@string, index) } else { EOF } + if index < @size { inko_string_byte(@string, index) } else { EOF } } fn mut identifier(name: String) -> Result[Nil, Error] { diff --git a/std/src/std/net/socket.inko b/std/src/std/net/socket.inko index 494f67d5e..228f52505 100644 --- a/std/src/std/net/socket.inko +++ b/std/src/std/net/socket.inko @@ -165,7 +165,7 @@ class pub Socket { case { @tag = 0, @value = v } -> Result.Ok( Socket { @fd = v, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -181,7 +181,7 @@ class pub Socket { case { @tag = 0, @value = v } -> Result.Ok( Socket { @fd = v, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -236,7 +236,7 @@ class pub Socket { fn pub mut bind(ip: ref ToString, port: Int) -> Result[Nil, Error] { match _INKO.socket_bind(@fd, ip.to_string, port) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -258,7 +258,7 @@ class pub Socket { fn pub mut connect(ip: ref ToString, port: Int) -> Result[Nil, Error] { match _INKO.socket_connect(@fd, ip.to_string, port, @deadline) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -279,7 +279,7 @@ class pub Socket { fn pub mut listen -> Result[Nil, Error] { match _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -314,7 +314,7 @@ class pub Socket { case { @tag = 0, @value = fd } -> Result.Ok( Socket { @fd = fd, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -344,7 +344,7 @@ class pub Socket { ) -> Result[Int, Error] { match _INKO.socket_send_string_to(@fd, string, ip.to_string, port, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -375,7 +375,7 @@ class pub Socket { ) -> Result[Int, Error] { match _INKO.socket_send_bytes_to(@fd, bytes, ip.to_string, port, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -414,7 +414,7 @@ class pub Socket { ) -> Result[SocketAddress, Error] { let raw_addr = match _INKO.socket_receive_from(@fd, bytes, size, @deadline) { case { @tag = 0, @value = v } -> v - case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + case { @tag = _, @value = e } -> throw Error.from_os_error(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) @@ -428,7 +428,7 @@ class pub Socket { fn pub local_address -> Result[SocketAddress, Error] { let raw_addr = match _INKO.socket_local_address(@fd) { case { @tag = 0, @value = v } -> v - case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + case { @tag = _, @value = e } -> throw Error.from_os_error(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) @@ -442,7 +442,7 @@ class pub Socket { fn pub peer_address -> Result[SocketAddress, Error] { let raw_addr = match _INKO.socket_peer_address(@fd) { case { @tag = 0, @value = v } -> v - case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + case { @tag = _, @value = e } -> throw Error.from_os_error(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) @@ -456,7 +456,7 @@ class pub Socket { fn pub mut ttl=(value: Int) -> Result[Nil, Error] { match _INKO.socket_set_ttl(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -464,7 +464,7 @@ class pub Socket { fn pub mut only_ipv6=(value: Bool) -> Result[Nil, Error] { match _INKO.socket_set_only_v6(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -472,7 +472,7 @@ class pub Socket { fn pub mut no_delay=(value: Bool) -> Result[Nil, Error] { match _INKO.socket_set_nodelay(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -480,7 +480,7 @@ class pub Socket { fn pub mut broadcast=(value: Bool) -> Result[Nil, Error] { match _INKO.socket_set_broadcast(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -488,7 +488,7 @@ class pub Socket { fn pub mut linger=(value: ref Duration) -> Result[Nil, Error] { match _INKO.socket_set_linger(@fd, value.to_nanos) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -496,7 +496,7 @@ class pub Socket { fn pub mut receive_buffer_size=(value: Int) -> Result[Nil, Error] { match _INKO.socket_set_recv_size(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -504,7 +504,7 @@ class pub Socket { fn pub mut send_buffer_size=(value: Int) -> Result[Nil, Error] { match _INKO.socket_set_send_size(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -512,7 +512,7 @@ class pub Socket { fn pub mut keepalive=(value: Bool) -> Result[Nil, Error] { match _INKO.socket_set_keepalive(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -520,7 +520,7 @@ class pub Socket { fn pub mut reuse_address=(value: Bool) -> Result[Nil, Error] { match _INKO.socket_set_reuse_address(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -531,7 +531,7 @@ class pub Socket { fn pub mut reuse_port=(value: Bool) -> Result[Nil, Error] { match _INKO.socket_set_reuse_port(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -539,7 +539,7 @@ class pub Socket { fn pub mut shutdown_read -> Result[Nil, Error] { match _INKO.socket_shutdown_read(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -547,7 +547,7 @@ class pub Socket { fn pub mut shutdown_write -> Result[Nil, Error] { match _INKO.socket_shutdown_write(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -555,7 +555,7 @@ class pub Socket { fn pub mut shutdown -> Result[Nil, Error] { match _INKO.socket_shutdown_read_write(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -568,7 +568,7 @@ class pub Socket { case { @tag = 0, @value = fd } -> Result.Ok( Socket { @fd = fd, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -583,7 +583,7 @@ impl Read for Socket { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.socket_read(@fd, into, size, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -592,14 +592,14 @@ impl Write for Socket { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.socket_write_bytes(@fd, bytes, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut write_string(string: String) -> Result[Int, Error] { match _INKO.socket_write_string(@fd, string, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1128,7 +1128,7 @@ class pub UnixSocket { case { @tag = 0, @value = fd } -> Result.Ok( UnixSocket { @fd = fd, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1182,7 +1182,7 @@ class pub UnixSocket { fn pub mut bind(path: ref ToString) -> Result[Nil, Error] { match _INKO.socket_bind(@fd, path.to_string, 0, @deadline) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1204,7 +1204,7 @@ class pub UnixSocket { fn pub mut connect(path: ref ToString) -> Result[Nil, Error] { match _INKO.socket_connect(@fd, path.to_string, 0, @deadline) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1224,7 +1224,7 @@ class pub UnixSocket { fn pub mut listen -> Result[Nil, Error] { match _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1258,7 +1258,7 @@ class pub UnixSocket { case { @tag = 0, @value = fd } -> Result.Ok( UnixSocket { @fd = fd, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1282,7 +1282,7 @@ class pub UnixSocket { match _INKO.socket_send_string_to(@fd, string, addr, 0, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1307,7 +1307,7 @@ class pub UnixSocket { match _INKO.socket_send_bytes_to(@fd, bytes, addr, 0, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1338,7 +1338,7 @@ class pub UnixSocket { ) -> Result[UnixAddress, Error] { let raw_addr = match _INKO.socket_receive_from(@fd, bytes, size, @deadline) { case { @tag = 0, @value = v } -> v - case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + case { @tag = _, @value = e } -> throw Error.from_os_error(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) @@ -1351,7 +1351,7 @@ class pub UnixSocket { fn pub local_address -> Result[UnixAddress, Error] { let raw_addr = match _INKO.socket_local_address(@fd) { case { @tag = 0, @value = v } -> v - case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + case { @tag = _, @value = e } -> throw Error.from_os_error(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) @@ -1364,7 +1364,7 @@ class pub UnixSocket { fn pub peer_address -> Result[UnixAddress, Error] { let raw_addr = match _INKO.socket_peer_address(@fd) { case { @tag = 0, @value = v } -> v - case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + case { @tag = _, @value = e } -> throw Error.from_os_error(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) @@ -1377,7 +1377,7 @@ class pub UnixSocket { fn pub mut receive_buffer_size=(value: Int) -> Result[Nil, Error] { match _INKO.socket_set_recv_size(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1385,7 +1385,7 @@ class pub UnixSocket { fn pub mut send_buffer_size=(value: Int) -> Result[Nil, Error] { match _INKO.socket_set_send_size(@fd, value) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1393,7 +1393,7 @@ class pub UnixSocket { fn pub mut shutdown_read -> Result[Nil, Error] { match _INKO.socket_shutdown_read(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1401,7 +1401,7 @@ class pub UnixSocket { fn pub mut shutdown_write -> Result[Nil, Error] { match _INKO.socket_shutdown_write(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1409,7 +1409,7 @@ class pub UnixSocket { fn pub mut shutdown -> Result[Nil, Error] { match _INKO.socket_shutdown_read_write(@fd) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -1422,7 +1422,7 @@ class pub UnixSocket { case { @tag = 0, @value = fd } -> Result.Ok( UnixSocket { @fd = fd, @deadline = NO_DEADLINE } ) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -1437,7 +1437,7 @@ impl Read for UnixSocket { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.socket_read(@fd, into, size, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -1446,14 +1446,14 @@ impl Write for UnixSocket { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.socket_write_bytes(@fd, bytes, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut write_string(string: String) -> Result[Int, Error] { match _INKO.socket_write_string(@fd, string, @deadline) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } diff --git a/std/src/std/rand.inko b/std/src/std/rand.inko index ad95497e6..69e1132ef 100644 --- a/std/src/std/rand.inko +++ b/std/src/std/rand.inko @@ -1,6 +1,9 @@ # Cryptographically secure random number generation. import std::drop::Drop +fn extern inko_array_get(array: Any, index: Int) -> Any +fn extern inko_array_set(array: Any, index: Int, value: Any) -> Any + # A cryptographically secure pseudo random number generator (CSPRNG). # # The algorithm used is unspecified but guaranteed to be cryptographically @@ -127,16 +130,16 @@ class pub Shuffle { # Shuffle.new.sort(a) # a # => [20, 10] fn pub mut sort[T](array: mut Array[T]) { - # Note that the types produced by `array_get()` and `array_set()` are `Any`. - # These values aren't dropped automatically, so there's no need to mark them - # as moved to prevent them from being dropped after the swap. + # Note that the types produced by `inko_array_get()` and `inko_array_set()` + # are `Any`. These values aren't dropped automatically, so there's no need + # to mark them as moved to prevent them from being dropped after the swap. let mut swap = array.length - 1 while swap > 0 { let swap_with = @rng.int_between(min: 0, max: swap) - let swap_val = _INKO.array_get(array, swap) + let swap_val = inko_array_get(array, swap) - _INKO.array_set(array, swap, _INKO.array_set(array, swap_with, swap_val)) + inko_array_set(array, swap, inko_array_set(array, swap_with, swap_val)) swap -= 1 } diff --git a/std/src/std/stdio.inko b/std/src/std/stdio.inko index 8e2874e71..7be91668e 100644 --- a/std/src/std/stdio.inko +++ b/std/src/std/stdio.inko @@ -13,7 +13,7 @@ impl Read for STDIN { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.stdin_read(into, size) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -33,14 +33,14 @@ impl Write for STDOUT { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.stdout_write_bytes(bytes) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut write_string(string: String) -> Result[Int, Error] { match _INKO.stdout_write_string(string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -64,14 +64,14 @@ impl Write for STDERR { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.stderr_write_bytes(bytes) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut write_string(string: String) -> Result[Int, Error] { match _INKO.stderr_write_string(string) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } diff --git a/std/src/std/string.inko b/std/src/std/string.inko index 3cda3d9e2..1c2a5af2c 100644 --- a/std/src/std/string.inko +++ b/std/src/std/string.inko @@ -17,6 +17,52 @@ import std::io::Read import std::iter::(Bytes as BytesTrait, EOF, Enum, Iter) import std::ops::Add +class extern StringResult { + let @tag: Int + let @value: String +} + +fn extern inko_string_equals( + state: Pointer[Int8], + left: String, + right: String, +) -> Bool + +fn extern inko_string_to_lower(state: Pointer[Int8], string: String) -> String +fn extern inko_string_to_upper(state: Pointer[Int8], string: String) -> String +fn extern inko_string_size(state: Pointer[Int8], string: String) -> Int +fn extern inko_string_byte(string: String, index: Int) -> Int +fn extern inko_string_slice_bytes( + state: Pointer[Int8], + string: String, + start: Int, + length: Int, +) -> String + +fn extern inko_string_characters(string: String) -> Pointer[Int8] +fn extern inko_string_characters_next( + state: Pointer[Int8], + iter: Pointer[Int8], +) -> StringResult + +fn extern inko_string_characters_drop(iter: Pointer[Int8]) +fn extern inko_string_drop(string: String) +fn extern inko_string_to_byte_array( + state: Pointer[Int8], + string: String, +) -> ByteArray + +fn extern inko_string_concat_array( + state: Pointer[Int8], + strings: ref Array[String], +) -> String + +fn extern inko_string_to_pointer(string: String) -> Pointer[Int8] +fn extern inko_string_from_pointer( + state: Pointer[Int8], + pointer: Pointer[Int8], +) -> String + let TAB = 0x9 let LF = 0xA let CR = 0xD @@ -87,6 +133,22 @@ trait pub ToString { # An UTF-8 encoded and immutable string type. class builtin String { + # Returns a `String` created from the given NULL terminated pointer. + # + # The purpose of this method is to allow creating a `String` from a pointer + # returned by C code. While this method ensures the input is valid UTF-8, it + # may crash your program if given an invalid pointer (e.g. a NULL pointer). + # + # Do not use this method unless you have somehow verified that the pointer is + # a valid NULL terminated C string. + # + # # Examples + # + # String.from_pointer("hello".to_pointer) == "hello" # => true + fn pub static from_pointer(pointer: Pointer[Int8]) -> String { + inko_string_from_pointer(_INKO.state, pointer) + } + # Return a `String` that contains the values of the iterator, separated by the # value of the `with` argument. # @@ -122,7 +184,7 @@ class builtin String { # # 'aä'.to_upper # => 'AÄ' fn pub to_upper -> String { - _INKO.string_to_upper(self) + inko_string_to_upper(_INKO.state, self) } # Returns the lowercase equivalent of the current `String`. @@ -141,7 +203,7 @@ class builtin String { # # 'AÄ'.to_lower # => 'aä' fn pub to_lower -> String { - _INKO.string_to_lower(self) + inko_string_to_lower(_INKO.state, self) } # Returns the size of the `String` in bytes. @@ -153,7 +215,7 @@ class builtin String { # 'foo'.size # => 3 # '😀'.size # => 4 fn pub size -> Int { - _INKO.string_size(self) + inko_string_size(_INKO.state, self) } # Returns the byte at the given byte index. @@ -165,7 +227,7 @@ class builtin String { # 'inko'.byte(0) # => 105 fn pub byte(index: Int) -> Int { bounds_check(index, size) - _INKO.string_byte(self, index) + inko_string_byte(self, index) } # Slices `self` into a substring. @@ -202,7 +264,7 @@ class builtin String { # '😊'.slice_bytes(start: 0, length: 4) # => '😊' # '😊'.slice_bytes(start: 0, length: 3) # => "\u{FFFD}" fn pub slice_bytes(start: Int, length: Int) -> String { - _INKO.string_slice_bytes(self, start, length) + inko_string_slice_bytes(_INKO.state, self, start, length) } # Returns the _byte_ index of the first occurrence of the given `String`, @@ -371,7 +433,7 @@ class builtin String { # # '😀😃'.characters.next # => Option.Some('😀 ') fn pub characters -> Characters { - Characters { @string = self, @iter = _INKO.string_characters(self) } + Characters { @string = self, @iter = inko_string_characters(self) } } # Returns a new `String` without the given prefix. @@ -488,6 +550,14 @@ class builtin String { buff.into_string } + + # Returns a raw pointer to the bytes of `self`. + # + # This method is meant to be used when passing strings to foreign functions + # (i.e. `*char` arguments). You should avoid using it for anything else. + fn pub to_pointer -> Pointer[Int8] { + inko_string_to_pointer(self) + } } impl Contains[String] for String { @@ -498,13 +568,13 @@ impl Contains[String] for String { impl Drop for String { fn mut drop { - _INKO.string_drop(self) + inko_string_drop(self) } } impl ToByteArray for String { fn pub to_byte_array -> ByteArray { - _INKO.string_to_byte_array(self) + inko_string_to_byte_array(_INKO.state, self) } } @@ -545,7 +615,7 @@ impl Equal[String] for String { # # 'foo' == 'bar' # => false fn pub ==(other: ref String) -> Bool { - _INKO.string_eq(self, other) + inko_string_equals(_INKO.state, self, other) } } @@ -554,7 +624,7 @@ impl Hash for String { let mut index = 0 while index < size { - hasher.write(_INKO.string_byte(self, index)) + hasher.write(inko_string_byte(self, index)) index += 1 } @@ -606,13 +676,13 @@ class pub Characters { let @string: String # The native iterator provided by the VM. - let @iter: Any + let @iter: Pointer[Int8] } impl Iter[String] for Characters { fn pub mut next -> Option[String] { - match _INKO.string_characters_next(@iter) { - case { @tag = 0, @value = v } -> Option.Some(v as String) + match inko_string_characters_next(_INKO.state, @iter) { + case { @tag = 0, @value = v } -> Option.Some(v) case _ -> Option.None } } @@ -620,7 +690,7 @@ impl Iter[String] for Characters { impl Drop for Characters { fn mut drop { - _INKO.string_characters_drop(@iter) + inko_string_characters_drop(@iter) } } @@ -642,7 +712,7 @@ impl Iter[Int] for Bytes { impl BytesTrait for Bytes { fn pub mut next_byte -> Int { if @index < @string.size { - _INKO.string_byte(@string, @index := @index + 1) + inko_string_byte(@string, (@index := @index + 1)) } else { EOF } @@ -768,7 +838,7 @@ impl ToString for StringBuffer { # # buffer.to_string # => 'hello world' fn pub to_string -> String { - _INKO.string_concat_array(@strings) + inko_string_concat_array(_INKO.state, @strings) } } diff --git a/std/src/std/sys.inko b/std/src/std/sys.inko index 0590cc0bd..a1ec550bb 100644 --- a/std/src/std/sys.inko +++ b/std/src/std/sys.inko @@ -286,7 +286,7 @@ class pub Command { @directory.as_ref.unwrap_or(ref ''), ) { case { @tag = 0, @value = v } -> Result.Ok(ChildProcess { @raw = v }) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -339,7 +339,7 @@ impl Write for Stdin { fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { match _INKO.child_process_stdin_write_bytes(@process.raw, bytes) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -349,14 +349,14 @@ impl Write for Stdin { string.to_string ) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } fn pub mut flush -> Result[Nil, Error] { match _INKO.child_process_stdin_flush(@process.raw) { case { @tag = 0, @value = _ } -> Result.Ok(nil) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -381,7 +381,7 @@ impl Read for Stdout { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.child_process_stdout_read(@process.raw, into, size) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -406,7 +406,7 @@ impl Read for Stderr { fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { match _INKO.child_process_stderr_read(@process.raw, into, size) { case { @tag = 0, @value = v } -> Result.Ok(v as Int) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } @@ -437,7 +437,7 @@ class pub ChildProcess { fn pub wait -> Result[ExitStatus, Error] { match _INKO.child_process_wait(@raw) { case { @tag = 0, @value = v } -> Result.Ok(ExitStatus.new(v as Int)) - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } @@ -454,7 +454,7 @@ class pub ChildProcess { case { @tag = 0, @value = v } -> { Result.Ok(Option.Some(ExitStatus.new(v as Int))) } - case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e as Int)) } } } diff --git a/std/src/std/sys/linux/errors.inko b/std/src/std/sys/linux/errors.inko new file mode 100644 index 000000000..bb8db5ee3 --- /dev/null +++ b/std/src/std/sys/linux/errors.inko @@ -0,0 +1,33 @@ +# Linux specific error codes. +let pub EPERM = 1 +let pub ENOENT = 2 +let pub EINTR = 4 +let pub EAGAIN = 11 +let pub ENOMEM = 12 +let pub EACCES = 13 +let pub EBUSY = 16 +let pub EEXIST = 17 +let pub ENOTDIR = 20 +let pub EISDIR = 21 +let pub EINVAL = 22 +let pub EFBIG = 27 +let pub ENOSPC = 28 +let pub ESPIPE = 29 +let pub EROFS = 30 +let pub EPIPE = 32 +let pub EDEADLK = 35 +let pub ENAMETOOLONG = 36 +let pub ENOTEMPTY = 39 +let pub ETIME = 62 +let pub EADDRINUSE = 98 +let pub EADDRNOTAVAIL = 99 +let pub ENETDOWN = 100 +let pub ENETUNREACH = 101 +let pub ECONNABORTED = 103 +let pub ECONNRESET = 104 +let pub EISCONN = 106 +let pub ENOTCONN = 107 +let pub ETIMEDOUT = 110 +let pub ECONNREFUSED = 111 +let pub EHOSTUNREACH = 113 +let pub EINPROGRESS = 115 diff --git a/std/src/sys/bsd/errors.inko b/std/src/sys/bsd/errors.inko new file mode 100644 index 000000000..e5cce0eb8 --- /dev/null +++ b/std/src/sys/bsd/errors.inko @@ -0,0 +1,33 @@ +# BSD specific error codes. +let pub EPERM = 1 +let pub ENOENT = 2 +let pub EINTR = 4 +let pub EDEADLK = 11 +let pub ENOMEM = 12 +let pub EACCES = 13 +let pub EBUSY = 16 +let pub EEXIST = 17 +let pub ENOTDIR = 20 +let pub EISDIR = 21 +let pub EINVAL = 22 +let pub EFBIG = 27 +let pub ENOSPC = 28 +let pub ESPIPE = 29 +let pub EROFS = 30 +let pub EPIPE = 32 +let pub EAGAIN = 35 +let pub EINPROGRESS = 36 +let pub EADDRINUSE = 48 +let pub EADDRNOTAVAIL = 49 +let pub ENETDOWN = 50 +let pub ENETUNREACH = 51 +let pub ECONNABORTED = 53 +let pub ECONNRESET = 54 +let pub EISCONN = 56 +let pub ENOTCONN = 57 +let pub ETIMEDOUT = 60 +let pub ECONNREFUSED = 61 +let pub ENAMETOOLONG = 63 +let pub EHOSTUNREACH = 65 +let pub ENOTEMPTY = 66 +let pub ETIME = 110 diff --git a/std/test/std/test_io.inko b/std/test/std/test_io.inko index 8fcedae0d..d07e2d02d 100644 --- a/std/test/std/test_io.inko +++ b/std/test/std/test_io.inko @@ -1,5 +1,6 @@ import helpers::(fmt) import std::io::(Error, Read, Write) +import std::sys::linux::errors import std::test::Tests class Reader { @@ -53,45 +54,74 @@ impl Write for Writer { } fn pub tests(t: mut Tests) { - t.test('Error.from_int') fn (t) { - t.equal(Error.from_int(0), Error.Other) - t.equal(Error.from_int(1), Error.NotFound) - t.equal(Error.from_int(2), Error.PermissionDenied) - t.equal(Error.from_int(3), Error.ConnectionRefused) - t.equal(Error.from_int(4), Error.ConnectionReset) - t.equal(Error.from_int(5), Error.ConnectionAborted) - t.equal(Error.from_int(6), Error.NotConnected) - t.equal(Error.from_int(7), Error.AddressInUse) - t.equal(Error.from_int(8), Error.AddressUnavailable) - t.equal(Error.from_int(9), Error.BrokenPipe) - t.equal(Error.from_int(10), Error.AlreadyExists) - t.equal(Error.from_int(11), Error.InvalidInput) - t.equal(Error.from_int(12), Error.InvalidData) - t.equal(Error.from_int(13), Error.TimedOut) - t.equal(Error.from_int(14), Error.WriteZero) - t.equal(Error.from_int(15), Error.Interrupted) - t.equal(Error.from_int(16), Error.UnexpectedEof) - t.equal(Error.from_int(100), Error.Other) + t.test('Error.from_os_error') fn (t) { + t.equal(Error.from_os_error(errors::EPERM), Error.PermissionDenied) + t.equal(Error.from_os_error(errors::ENOENT), Error.NotFound) + t.equal(Error.from_os_error(errors::EINTR), Error.Interrupted) + t.equal(Error.from_os_error(errors::EAGAIN), Error.WouldBlock) + t.equal(Error.from_os_error(errors::ENOMEM), Error.OutOfMemory) + t.equal(Error.from_os_error(errors::EACCES), Error.PermissionDenied) + t.equal(Error.from_os_error(errors::EBUSY), Error.ResourceBusy) + t.equal(Error.from_os_error(errors::EEXIST), Error.AlreadyExists) + t.equal(Error.from_os_error(errors::ENOTDIR), Error.NotADirectory) + t.equal(Error.from_os_error(errors::EISDIR), Error.IsADirectory) + t.equal(Error.from_os_error(errors::EINVAL), Error.InvalidArgument) + t.equal(Error.from_os_error(errors::EFBIG), Error.FileTooLarge) + t.equal(Error.from_os_error(errors::ENOSPC), Error.StorageFull) + t.equal(Error.from_os_error(errors::ESPIPE), Error.InvalidSeek) + t.equal(Error.from_os_error(errors::EROFS), Error.ReadOnlyFilesystem) + t.equal(Error.from_os_error(errors::EPIPE), Error.BrokenPipe) + t.equal(Error.from_os_error(errors::EDEADLK), Error.Deadlock) + t.equal(Error.from_os_error(errors::ENAMETOOLONG), Error.InvalidFileName) + t.equal(Error.from_os_error(errors::ENOTEMPTY), Error.DirectoryNotEmpty) + t.equal(Error.from_os_error(errors::ETIME), Error.TimedOut) + t.equal(Error.from_os_error(errors::EADDRINUSE), Error.AddressInUse) + t.equal(Error.from_os_error(errors::EADDRNOTAVAIL), Error.AddressUnavailable) + t.equal(Error.from_os_error(errors::ENETDOWN), Error.NetworkDown) + t.equal(Error.from_os_error(errors::ENETUNREACH), Error.NetworkUnreachable) + t.equal(Error.from_os_error(errors::ECONNABORTED), Error.ConnectionAborted) + t.equal(Error.from_os_error(errors::ECONNRESET), Error.ConnectionReset) + t.equal(Error.from_os_error(errors::EISCONN), Error.AlreadyConnected) + t.equal(Error.from_os_error(errors::ENOTCONN), Error.NotConnected) + t.equal(Error.from_os_error(errors::ETIMEDOUT), Error.TimedOut) + t.equal(Error.from_os_error(errors::ECONNREFUSED), Error.ConnectionRefused) + t.equal(Error.from_os_error(errors::EHOSTUNREACH), Error.HostUnreachable) + t.equal(Error.from_os_error(errors::EINPROGRESS), Error.InProgress) + t.equal(Error.from_os_error(999), Error.Other(999)) } t.test('Error.fmt') fn (t) { - t.equal(fmt(Error.Other), 'Other') - t.equal(fmt(Error.NotFound), 'NotFound') - t.equal(fmt(Error.PermissionDenied), 'PermissionDenied') - t.equal(fmt(Error.ConnectionRefused), 'ConnectionRefused') - t.equal(fmt(Error.ConnectionReset), 'ConnectionReset') - t.equal(fmt(Error.ConnectionAborted), 'ConnectionAborted') - t.equal(fmt(Error.NotConnected), 'NotConnected') t.equal(fmt(Error.AddressInUse), 'AddressInUse') t.equal(fmt(Error.AddressUnavailable), 'AddressUnavailable') - t.equal(fmt(Error.BrokenPipe), 'BrokenPipe') + t.equal(fmt(Error.AlreadyConnected), 'AlreadyConnected') t.equal(fmt(Error.AlreadyExists), 'AlreadyExists') - t.equal(fmt(Error.InvalidInput), 'InvalidInput') - t.equal(fmt(Error.InvalidData), 'InvalidData') - t.equal(fmt(Error.TimedOut), 'TimedOut') - t.equal(fmt(Error.WriteZero), 'WriteZero') + t.equal(fmt(Error.BrokenPipe), 'BrokenPipe') + t.equal(fmt(Error.ConnectionAborted), 'ConnectionAborted') + t.equal(fmt(Error.ConnectionRefused), 'ConnectionRefused') + t.equal(fmt(Error.ConnectionReset), 'ConnectionReset') + t.equal(fmt(Error.Deadlock), 'Deadlock') + t.equal(fmt(Error.DirectoryNotEmpty), 'DirectoryNotEmpty') + t.equal(fmt(Error.FileTooLarge), 'FileTooLarge') + t.equal(fmt(Error.HostUnreachable), 'HostUnreachable') + t.equal(fmt(Error.InProgress), 'InProgress') t.equal(fmt(Error.Interrupted), 'Interrupted') - t.equal(fmt(Error.UnexpectedEof), 'UnexpectedEof') + t.equal(fmt(Error.InvalidArgument), 'InvalidArgument') + t.equal(fmt(Error.InvalidFileName), 'InvalidFileName') + t.equal(fmt(Error.InvalidSeek), 'InvalidSeek') + t.equal(fmt(Error.IsADirectory), 'IsADirectory') + t.equal(fmt(Error.NetworkDown), 'NetworkDown') + t.equal(fmt(Error.NetworkUnreachable), 'NetworkUnreachable') + t.equal(fmt(Error.NotADirectory), 'NotADirectory') + t.equal(fmt(Error.NotConnected), 'NotConnected') + t.equal(fmt(Error.NotFound), 'NotFound') + t.equal(fmt(Error.OutOfMemory), 'OutOfMemory') + t.equal(fmt(Error.PermissionDenied), 'PermissionDenied') + t.equal(fmt(Error.ReadOnlyFilesystem), 'ReadOnlyFilesystem') + t.equal(fmt(Error.ResourceBusy), 'ResourceBusy') + t.equal(fmt(Error.StorageFull), 'StorageFull') + t.equal(fmt(Error.TimedOut), 'TimedOut') + t.equal(fmt(Error.WouldBlock), 'WouldBlock') + t.equal(fmt(Error.Other(999)), 'Other(999)') } t.test('Read.read_all') fn (t) { diff --git a/types/src/check.rs b/types/src/check.rs index 8fbf2766e..51e0585d2 100644 --- a/types/src/check.rs +++ b/types/src/check.rs @@ -1,7 +1,8 @@ //! Type checking of types. use crate::{ - Arguments, ClassInstance, Database, MethodId, TraitInstance, TypeArguments, - TypeBounds, TypeId, TypeParameterId, TypePlaceholderId, TypeRef, + Arguments, ClassInstance, Database, ForeignType, MethodId, TraitInstance, + TypeArguments, TypeBounds, TypeId, TypeParameterId, TypePlaceholderId, + TypeRef, FLOAT_ID, INT_ID, }; use std::collections::HashSet; @@ -34,6 +35,11 @@ struct Rules { /// When encountering an Infer() type, turn it into a rigid type. infer_as_rigid: bool, + + /// If we're performing a type-check as part of a type cast. + /// + /// When enabled, certain type-checking rules may be relaxed. + type_cast: bool, } impl Rules { @@ -42,6 +48,7 @@ impl Rules { subtyping: TraitSubtyping::Yes, relaxed_ownership: false, infer_as_rigid: false, + type_cast: false, } } @@ -63,12 +70,17 @@ impl Rules { self } - fn relaxed(mut self) -> Rules { + fn with_relaxed_ownership(mut self) -> Rules { self.relaxed_ownership = true; self } - fn strict(mut self) -> Rules { + fn with_type_cast(mut self) -> Rules { + self.type_cast = true; + self + } + + fn with_strict_ownership(mut self) -> Rules { self.relaxed_ownership = false; self } @@ -124,6 +136,16 @@ impl<'a> TypeChecker<'a> { TypeChecker::new(db).run(left, right, &mut env) } + pub fn check_cast(db: &'a Database, left: TypeRef, right: TypeRef) -> bool { + let mut env = + Environment::new(left.type_arguments(db), right.type_arguments(db)); + + let mut checker = TypeChecker::new(db); + let rules = Rules::new().with_type_cast(); + + checker.check_type_ref(left, right, &mut env, rules) + } + pub fn new(db: &'a Database) -> TypeChecker<'a> { TypeChecker { db, checked: HashSet::new() } } @@ -226,7 +248,7 @@ impl<'a> TypeChecker<'a> { env.left.assign(bound, val); let mut env = env.with_left_as_right(); - let rules = rules.relaxed(); + let rules = rules.with_relaxed_ownership(); if bound.is_mutable(self.db) && !val.is_mutable(self.db) { return false; @@ -253,7 +275,7 @@ impl<'a> TypeChecker<'a> { // sub checks. The approach we take here means we only need to "reset" // this once for the sub checks. let relaxed = rules.relaxed_ownership; - let rules = rules.strict(); + let rules = rules.with_strict_ownership(); // Resolve any assigned type parameters/placeholders to the types // they're assigned to. @@ -353,6 +375,11 @@ impl<'a> TypeChecker<'a> { true } + TypeRef::Pointer(_) if rules.type_cast => match left_id { + TypeId::ClassInstance(ins) => ins.instance_of().0 == INT_ID, + TypeId::Foreign(ForeignType::Int(_)) => true, + _ => false, + }, TypeRef::Error => true, _ => false, }, @@ -492,6 +519,18 @@ impl<'a> TypeChecker<'a> { left_id.assign(self.db, right); true } + TypeRef::Pointer(left_id) => match right { + TypeRef::Pointer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Owned(TypeId::Foreign(ForeignType::Int(_))) => { + rules.type_cast + } + TypeRef::Owned(TypeId::ClassInstance(ins)) => { + rules.type_cast && ins.instance_of().0 == INT_ID + } + _ => false, + }, _ => false, } } @@ -525,6 +564,13 @@ impl<'a> TypeChecker<'a> { TypeId::ClassInstance(lhs) => match right_id { TypeId::ClassInstance(rhs) => { if lhs.instance_of != rhs.instance_of { + if rules.type_cast + && lhs.instance_of.is_numeric() + && rhs.instance_of.is_numeric() + { + return true; + } + return false; } @@ -558,6 +604,7 @@ impl<'a> TypeChecker<'a> { self.check_class_with_trait(lhs, req, env, rules) }) } + TypeId::Foreign(_) => rules.type_cast, _ => false, }, TypeId::TraitInstance(lhs) => match right_id { @@ -610,6 +657,42 @@ impl<'a> TypeChecker<'a> { } _ => false, }, + TypeId::Foreign(ForeignType::Int(lsize)) => { + if rules.type_cast { + match right_id { + TypeId::Foreign(_) => true, + TypeId::ClassInstance(ins) => { + matches!(ins.instance_of().0, INT_ID | FLOAT_ID) + } + _ => false, + } + } else { + match right_id { + TypeId::Foreign(ForeignType::Int(rsize)) => { + lsize == rsize + } + _ => false, + } + } + } + TypeId::Foreign(ForeignType::Float(lsize)) => { + if rules.type_cast { + match right_id { + TypeId::Foreign(_) => true, + TypeId::ClassInstance(ins) => { + matches!(ins.instance_of().0, INT_ID | FLOAT_ID) + } + _ => false, + } + } else { + match right_id { + TypeId::Foreign(ForeignType::Float(rsize)) => { + lsize == rsize + } + _ => false, + } + } + } } } @@ -753,7 +836,7 @@ impl<'a> TypeChecker<'a> { // value), it's up to the implementation of the method to do so. // References in turn can be created from both owned values and other // references. - let rules = rules.relaxed(); + let rules = rules.with_relaxed_ownership(); if left.instance_of.is_generic(self.db) { // The implemented trait may refer to type parameters of the @@ -974,7 +1057,8 @@ mod tests { type_arguments, type_bounds, uni, }; use crate::{ - Block, ClassId, Closure, TraitImplementation, TypePlaceholder, + Block, Class, ClassId, ClassKind, Closure, ModuleId, + TraitImplementation, TypePlaceholder, Visibility, }; #[track_caller] @@ -988,6 +1072,17 @@ mod tests { } } + #[track_caller] + fn check_ok_cast(db: &Database, left: TypeRef, right: TypeRef) { + if !TypeChecker::check_cast(db, left, right) { + panic!( + "Expected {} to be compatible with {}", + format_type(db, left), + format_type(db, right) + ); + } + } + #[track_caller] fn check_err(db: &Database, left: TypeRef, right: TypeRef) { assert!( @@ -2143,4 +2238,91 @@ mod tests { &mut env, )); } + + #[test] + fn test_check_c_types() { + let mut db = Database::new(); + let foo = Class::alloc( + &mut db, + "foo".to_string(), + ClassKind::Extern, + Visibility::Public, + ModuleId(0), + ); + + let bar = Class::alloc( + &mut db, + "bar".to_string(), + ClassKind::Extern, + Visibility::Public, + ModuleId(0), + ); + + check_ok(&db, TypeRef::foreign_int(8), TypeRef::foreign_int(8)); + check_ok(&db, TypeRef::foreign_float(32), TypeRef::foreign_float(32)); + check_ok( + &db, + TypeRef::foreign_struct(foo), + TypeRef::foreign_struct(foo), + ); + + check_ok_cast(&db, TypeRef::foreign_int(8), TypeRef::foreign_int(16)); + check_ok_cast( + &db, + TypeRef::foreign_float(32), + TypeRef::foreign_float(64), + ); + check_ok_cast(&db, TypeRef::foreign_int(32), TypeRef::int()); + check_ok_cast(&db, TypeRef::foreign_float(32), TypeRef::int()); + check_ok_cast(&db, TypeRef::int(), TypeRef::foreign_int(8)); + check_ok_cast(&db, TypeRef::float(), TypeRef::foreign_float(32)); + check_ok_cast(&db, TypeRef::float(), TypeRef::int()); + check_ok_cast(&db, TypeRef::int(), TypeRef::float()); + check_ok_cast( + &db, + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))), + TypeRef::foreign_int(8), + ); + check_ok_cast( + &db, + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))), + TypeRef::int(), + ); + check_ok_cast( + &db, + TypeRef::int(), + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))), + ); + check_ok_cast( + &db, + TypeRef::foreign_int(8), + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))), + ); + + check_err(&db, TypeRef::foreign_int(32), TypeRef::foreign_int(8)); + check_err(&db, TypeRef::foreign_int(8), TypeRef::foreign_int(16)); + check_err(&db, TypeRef::foreign_float(32), TypeRef::foreign_float(64)); + check_err(&db, TypeRef::foreign_int(8), TypeRef::foreign_float(32)); + check_err(&db, TypeRef::foreign_float(8), TypeRef::foreign_int(32)); + check_err( + &db, + TypeRef::foreign_struct(foo), + TypeRef::foreign_struct(bar), + ); + check_err( + &db, + TypeRef::foreign_struct(foo), + TypeRef::pointer(TypeId::ClassInstance(ClassInstance::new(foo))), + ); + check_err( + &db, + TypeRef::pointer(TypeId::ClassInstance(ClassInstance::new(foo))), + TypeRef::pointer(TypeId::ClassInstance(ClassInstance::new(bar))), + ); + check_err( + &db, + TypeRef::pointer(TypeId::ClassInstance(ClassInstance::new(foo))), + TypeRef::Any, + ); + } } diff --git a/types/src/format.rs b/types/src/format.rs index ec539e27b..d8540df81 100644 --- a/types/src/format.rs +++ b/types/src/format.rs @@ -1,8 +1,9 @@ //! Formatting of types. use crate::{ Arguments, ClassId, ClassInstance, ClassKind, ClosureId, Database, - MethodId, MethodKind, ModuleId, TraitId, TraitInstance, TypeArguments, - TypeId, TypeParameterId, TypePlaceholderId, TypeRef, Visibility, + ForeignType, MethodId, MethodKind, ModuleId, TraitId, TraitInstance, + TypeArguments, TypeId, TypeParameterId, TypePlaceholderId, TypeRef, + Visibility, }; const MAX_FORMATTING_DEPTH: usize = 8; @@ -137,6 +138,7 @@ impl<'a> TypeFormatter<'a> { pub(crate) fn return_type(&mut self, typ: TypeRef) { match typ { TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} + TypeRef::Unknown => {} _ if typ == TypeRef::nil() => {} _ => { self.write(" -> "); @@ -282,7 +284,8 @@ impl FormatType for MethodId { MethodKind::Mutable | MethodKind::Destructor => { buffer.write("mut ") } - _ => {} + MethodKind::Extern => buffer.write("extern "), + MethodKind::Instance => {} } buffer.write(&block.name); @@ -358,10 +361,14 @@ impl FormatType for TypeRef { } TypeRef::Never => buffer.write("Never"), TypeRef::Any => buffer.write("Any"), - TypeRef::RefAny => buffer.write("ref Any"), TypeRef::Error => buffer.write(""), TypeRef::Unknown => buffer.write(""), TypeRef::Placeholder(id) => id.format_type(buffer), + TypeRef::Pointer(typ) => { + buffer.write("Pointer["); + typ.format_type(buffer); + buffer.write("]"); + } }; } } @@ -379,6 +386,12 @@ impl FormatType for TypeId { id.format_type_without_argument(buffer); } TypeId::Closure(id) => id.format_type(buffer), + TypeId::Foreign(ForeignType::Int(size)) => { + buffer.write(&format!("Int{}", size)) + } + TypeId::Foreign(ForeignType::Float(size)) => { + buffer.write(&format!("Float{}", size)) + } } } } @@ -395,12 +408,8 @@ mod tests { #[test] fn test_trait_instance_format_type_with_regular_trait() { let mut db = Database::new(); - let trait_id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); + let trait_id = + Trait::alloc(&mut db, "A".to_string(), Visibility::Private); let trait_ins = TraitInstance::new(trait_id); assert_eq!(format_type(&db, trait_ins), "A".to_string()); @@ -409,12 +418,8 @@ mod tests { #[test] fn test_trait_instance_format_type_with_generic_trait() { let mut db = Database::new(); - let trait_id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); + let trait_id = + Trait::alloc(&mut db, "ToString".to_string(), Visibility::Private); let param1 = trait_id.new_type_parameter(&mut db, "A".to_string()); trait_id.new_type_parameter(&mut db, "B".to_string()); @@ -584,7 +589,6 @@ mod tests { let id = TypeId::Trait(Trait::alloc( &mut db, "ToString".to_string(), - ModuleId(0), Visibility::Private, )); @@ -644,12 +648,8 @@ mod tests { #[test] fn test_type_id_format_type_with_trait_instance() { let mut db = Database::new(); - let id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); + let id = + Trait::alloc(&mut db, "ToString".to_string(), Visibility::Private); let ins = TypeId::TraitInstance(TraitInstance::new(id)); assert_eq!(format_type(&db, ins), "ToString"); @@ -682,12 +682,8 @@ mod tests { #[test] fn test_type_id_format_type_with_generic_trait_instance() { let mut db = Database::new(); - let id = Trait::alloc( - &mut db, - "ToFoo".to_string(), - ModuleId(0), - Visibility::Private, - ); + let id = + Trait::alloc(&mut db, "ToFoo".to_string(), Visibility::Private); let param1 = id.new_type_parameter(&mut db, "T".to_string()); id.new_type_parameter(&mut db, "E".to_string()); @@ -706,12 +702,8 @@ mod tests { fn test_type_id_format_type_with_type_parameter() { let mut db = Database::new(); let param = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); + let to_string = + Trait::alloc(&mut db, "ToString".to_string(), Visibility::Private); let param_ins = TypeId::TypeParameter(param); let to_string_ins = TraitInstance::new(to_string); @@ -724,12 +716,8 @@ mod tests { fn test_type_id_format_type_with_rigid_type_parameter() { let mut db = Database::new(); let param = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); + let to_string = + Trait::alloc(&mut db, "ToString".to_string(), Visibility::Private); let param_ins = TypeId::RigidTypeParameter(param); let to_string_ins = TraitInstance::new(to_string); @@ -824,4 +812,29 @@ mod tests { assert_eq!(format_type(&db, TypeRef::Error), "".to_string()); assert_eq!(format_type(&db, TypeRef::Unknown), "".to_string()); } + + #[test] + fn test_ctype_format() { + let mut db = Database::new(); + let foo = Class::alloc( + &mut db, + "Foo".to_string(), + ClassKind::Extern, + Visibility::Public, + ModuleId(0), + ); + + assert_eq!(format_type(&db, TypeRef::foreign_int(8)), "Int8"); + assert_eq!(format_type(&db, TypeRef::foreign_int(16)), "Int16"); + assert_eq!(format_type(&db, TypeRef::foreign_int(32)), "Int32"); + assert_eq!(format_type(&db, TypeRef::foreign_int(64)), "Int64"); + assert_eq!( + format_type( + &db, + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))) + ), + "Pointer[Int8]" + ); + assert_eq!(format_type(&db, TypeRef::foreign_struct(foo)), "Foo"); + } } diff --git a/types/src/lib.rs b/types/src/lib.rs index 259c3685b..fd92e79b5 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -69,6 +69,7 @@ pub const STRING_MODULE: &str = "std::string"; pub const TO_STRING_TRAIT: &str = "ToString"; pub const TO_STRING_METHOD: &str = "to_string"; pub const CALL_METHOD: &str = "call"; +pub const EQ_METHOD: &str = "=="; pub const MAIN_CLASS: &str = "Main"; pub const MAIN_METHOD: &str = "main"; pub const DROP_MODULE: &str = "std::drop"; @@ -85,6 +86,9 @@ pub const OPTION_NONE: &str = "None"; pub const RESULT_OK: &str = "Ok"; pub const RESULT_ERROR: &str = "Error"; +/// The name of the pseudo field used to deference a pointer. +pub const DEREF_POINTER_FIELD: &str = "0"; + pub const ENUM_TAG_FIELD: &str = "tag"; pub const ENUM_TAG_INDEX: usize = 0; @@ -355,7 +359,6 @@ impl TypeArguments { /// An Inko trait. pub struct Trait { name: String, - module: ModuleId, implemented_by: Vec, visibility: Visibility, type_parameters: IndexMap, @@ -401,22 +404,20 @@ impl Trait { pub fn alloc( db: &mut Database, name: String, - module: ModuleId, visibility: Visibility, ) -> TraitId { assert!(db.traits.len() <= u32::MAX as usize); let id = db.traits.len() as u32; - let trait_type = Trait::new(name, module, visibility); + let trait_type = Trait::new(name, visibility); db.traits.push(trait_type); TraitId(id) } - fn new(name: String, module: ModuleId, visibility: Visibility) -> Self { + fn new(name: String, visibility: Visibility) -> Self { Self { name, - module, visibility, implemented_by: Vec::new(), type_parameters: IndexMap::new(), @@ -552,10 +553,6 @@ impl TraitId { !self.is_public(db) } - fn module(self, db: &Database) -> ModuleId { - self.get(db).module - } - fn named_type(self, db: &Database, name: &str) -> Option { self.get(db) .type_parameters @@ -1110,6 +1107,10 @@ impl ClassId { self.get(db).kind } + pub fn allow_trait_implementations(self, db: &Database) -> bool { + !matches!(self.kind(db), ClassKind::Async | ClassKind::Extern) + } + pub fn type_parameters(self, db: &Database) -> Vec { self.get(db).type_parameters.values().clone() } @@ -1298,6 +1299,14 @@ impl ClassId { self.0 <= CHANNEL_ID } + pub fn is_value_type(self, db: &Database) -> bool { + self.get(db).value_type + } + + pub fn is_numeric(self) -> bool { + matches!(self.0, INT_ID | FLOAT_ID) + } + fn get(self, db: &Database) -> &Class { &db.classes[self.0 as usize] } @@ -1543,40 +1552,7 @@ impl Visibility { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum BuiltinFunction { - ArrayCapacity, - ArrayClear, - ArrayDrop, - ArrayGet, - ArrayLength, - ArrayPop, ArrayPush, - ArrayRemove, - ArrayReserve, - ArraySet, - ByteArrayAppend, - ByteArrayClear, - ByteArrayClone, - ByteArrayCopyFrom, - ByteArrayDrainToString, - ByteArrayDrop, - ByteArrayEq, - ByteArrayGet, - ByteArrayLength, - ByteArrayNew, - ByteArrayPop, - ByteArrayPush, - ByteArrayRemove, - ByteArrayResize, - ByteArraySet, - ByteArraySlice, - ByteArrayToString, - ChannelDrop, - ChannelNew, - ChannelReceive, - ChannelReceiveUntil, - ChannelSend, - ChannelTryReceive, - ChannelWait, ChildProcessDrop, ChildProcessSpawn, ChildProcessStderrClose, @@ -1631,7 +1607,6 @@ pub enum BuiltinFunction { FloatRound, FloatSub, FloatToBits, - FloatToInt, FloatToString, IntAdd, IntBitAnd, @@ -1652,7 +1627,6 @@ pub enum BuiltinFunction { IntShl, IntShr, IntSub, - IntToFloat, IntToString, IntUnsignedShr, IntWrappingAdd, @@ -1721,58 +1695,21 @@ pub enum BuiltinFunction { StdoutFlush, StdoutWriteBytes, StdoutWriteString, - StringByte, - StringCharacters, - StringCharactersDrop, - StringCharactersNext, StringConcat, - StringConcatArray, - StringDrop, - StringEq, - StringSize, - StringSliceBytes, - StringToByteArray, StringToFloat, - StringToInt, - StringToLower, - StringToUpper, TimeMonotonic, TimeSystem, TimeSystemOffset, HashKey0, HashKey1, + State, + Process, } impl BuiltinFunction { pub fn mapping() -> HashMap { vec![ - BuiltinFunction::ArrayCapacity, - BuiltinFunction::ArrayClear, - BuiltinFunction::ArrayDrop, - BuiltinFunction::ArrayGet, - BuiltinFunction::ArrayLength, - BuiltinFunction::ArrayPop, BuiltinFunction::ArrayPush, - BuiltinFunction::ArrayRemove, - BuiltinFunction::ArrayReserve, - BuiltinFunction::ArraySet, - BuiltinFunction::ByteArrayNew, - BuiltinFunction::ByteArrayAppend, - BuiltinFunction::ByteArrayClear, - BuiltinFunction::ByteArrayClone, - BuiltinFunction::ByteArrayCopyFrom, - BuiltinFunction::ByteArrayDrainToString, - BuiltinFunction::ByteArrayDrop, - BuiltinFunction::ByteArrayEq, - BuiltinFunction::ByteArrayGet, - BuiltinFunction::ByteArrayLength, - BuiltinFunction::ByteArrayPop, - BuiltinFunction::ByteArrayPush, - BuiltinFunction::ByteArrayRemove, - BuiltinFunction::ByteArrayResize, - BuiltinFunction::ByteArraySet, - BuiltinFunction::ByteArraySlice, - BuiltinFunction::ByteArrayToString, BuiltinFunction::ChildProcessDrop, BuiltinFunction::ChildProcessSpawn, BuiltinFunction::ChildProcessStderrClose, @@ -1827,15 +1764,7 @@ impl BuiltinFunction { BuiltinFunction::FloatRound, BuiltinFunction::FloatSub, BuiltinFunction::FloatToBits, - BuiltinFunction::FloatToInt, BuiltinFunction::FloatToString, - BuiltinFunction::ChannelDrop, - BuiltinFunction::ChannelNew, - BuiltinFunction::ChannelReceive, - BuiltinFunction::ChannelReceiveUntil, - BuiltinFunction::ChannelSend, - BuiltinFunction::ChannelTryReceive, - BuiltinFunction::ChannelWait, BuiltinFunction::IntAdd, BuiltinFunction::IntBitAnd, BuiltinFunction::IntBitNot, @@ -1855,7 +1784,6 @@ impl BuiltinFunction { BuiltinFunction::IntShl, BuiltinFunction::IntShr, BuiltinFunction::IntSub, - BuiltinFunction::IntToFloat, BuiltinFunction::IntToString, BuiltinFunction::IntUnsignedShr, BuiltinFunction::IntWrappingAdd, @@ -1924,26 +1852,15 @@ impl BuiltinFunction { BuiltinFunction::StdoutFlush, BuiltinFunction::StdoutWriteBytes, BuiltinFunction::StdoutWriteString, - BuiltinFunction::StringByte, - BuiltinFunction::StringCharacters, - BuiltinFunction::StringCharactersDrop, - BuiltinFunction::StringCharactersNext, BuiltinFunction::StringConcat, - BuiltinFunction::StringConcatArray, - BuiltinFunction::StringDrop, - BuiltinFunction::StringEq, - BuiltinFunction::StringSize, - BuiltinFunction::StringSliceBytes, - BuiltinFunction::StringToByteArray, BuiltinFunction::StringToFloat, - BuiltinFunction::StringToInt, - BuiltinFunction::StringToLower, - BuiltinFunction::StringToUpper, BuiltinFunction::TimeMonotonic, BuiltinFunction::TimeSystem, BuiltinFunction::TimeSystemOffset, BuiltinFunction::HashKey0, BuiltinFunction::HashKey1, + BuiltinFunction::State, + BuiltinFunction::Process, ] .into_iter() .fold(HashMap::new(), |mut map, func| { @@ -1954,35 +1871,7 @@ impl BuiltinFunction { pub fn name(self) -> &'static str { match self { - BuiltinFunction::ArrayCapacity => "array_capacity", - BuiltinFunction::ArrayClear => "array_clear", - BuiltinFunction::ArrayDrop => "array_drop", - BuiltinFunction::ArrayGet => "array_get", - BuiltinFunction::ArrayLength => "array_length", - BuiltinFunction::ArrayPop => "array_pop", BuiltinFunction::ArrayPush => "array_push", - BuiltinFunction::ArrayRemove => "array_remove", - BuiltinFunction::ArrayReserve => "array_reserve", - BuiltinFunction::ArraySet => "array_set", - BuiltinFunction::ByteArrayNew => "byte_array_new", - BuiltinFunction::ByteArrayAppend => "byte_array_append", - BuiltinFunction::ByteArrayClear => "byte_array_clear", - BuiltinFunction::ByteArrayClone => "byte_array_clone", - BuiltinFunction::ByteArrayCopyFrom => "byte_array_copy_from", - BuiltinFunction::ByteArrayDrainToString => { - "byte_array_drain_to_string" - } - BuiltinFunction::ByteArrayDrop => "byte_array_drop", - BuiltinFunction::ByteArrayEq => "byte_array_eq", - BuiltinFunction::ByteArrayGet => "byte_array_get", - BuiltinFunction::ByteArrayLength => "byte_array_length", - BuiltinFunction::ByteArrayPop => "byte_array_pop", - BuiltinFunction::ByteArrayPush => "byte_array_push", - BuiltinFunction::ByteArrayRemove => "byte_array_remove", - BuiltinFunction::ByteArrayResize => "byte_array_resize", - BuiltinFunction::ByteArraySet => "byte_array_set", - BuiltinFunction::ByteArraySlice => "byte_array_slice", - BuiltinFunction::ByteArrayToString => "byte_array_to_string", BuiltinFunction::ChildProcessDrop => "child_process_drop", BuiltinFunction::ChildProcessSpawn => "child_process_spawn", BuiltinFunction::ChildProcessStderrClose => { @@ -2061,15 +1950,7 @@ impl BuiltinFunction { BuiltinFunction::FloatRound => "float_round", BuiltinFunction::FloatSub => "float_sub", BuiltinFunction::FloatToBits => "float_to_bits", - BuiltinFunction::FloatToInt => "float_to_int", BuiltinFunction::FloatToString => "float_to_string", - BuiltinFunction::ChannelReceive => "channel_receive", - BuiltinFunction::ChannelReceiveUntil => "channel_receive_until", - BuiltinFunction::ChannelDrop => "channel_drop", - BuiltinFunction::ChannelWait => "channel_wait", - BuiltinFunction::ChannelNew => "channel_new", - BuiltinFunction::ChannelSend => "channel_send", - BuiltinFunction::ChannelTryReceive => "channel_try_receive", BuiltinFunction::IntAdd => "int_add", BuiltinFunction::IntBitAnd => "int_bit_and", BuiltinFunction::IntBitNot => "int_bit_not", @@ -2089,7 +1970,6 @@ impl BuiltinFunction { BuiltinFunction::IntShl => "int_shl", BuiltinFunction::IntShr => "int_shr", BuiltinFunction::IntSub => "int_sub", - BuiltinFunction::IntToFloat => "int_to_float", BuiltinFunction::IntToString => "int_to_string", BuiltinFunction::IntUnsignedShr => "int_unsigned_shr", BuiltinFunction::IntWrappingAdd => "int_wrapping_add", @@ -2176,62 +2056,23 @@ impl BuiltinFunction { BuiltinFunction::StdoutFlush => "stdout_flush", BuiltinFunction::StdoutWriteBytes => "stdout_write_bytes", BuiltinFunction::StdoutWriteString => "stdout_write_string", - BuiltinFunction::StringByte => "string_byte", - BuiltinFunction::StringCharacters => "string_characters", - BuiltinFunction::StringCharactersDrop => "string_characters_drop", - BuiltinFunction::StringCharactersNext => "string_characters_next", BuiltinFunction::StringConcat => "string_concat", - BuiltinFunction::StringConcatArray => "string_concat_array", - BuiltinFunction::StringDrop => "string_drop", - BuiltinFunction::StringEq => "string_eq", - BuiltinFunction::StringSize => "string_size", - BuiltinFunction::StringSliceBytes => "string_slice_bytes", - BuiltinFunction::StringToByteArray => "string_to_byte_array", BuiltinFunction::StringToFloat => "string_to_float", - BuiltinFunction::StringToInt => "string_to_int", - BuiltinFunction::StringToLower => "string_to_lower", - BuiltinFunction::StringToUpper => "string_to_upper", BuiltinFunction::TimeMonotonic => "time_monotonic", BuiltinFunction::TimeSystem => "time_system", BuiltinFunction::TimeSystemOffset => "time_system_offset", BuiltinFunction::HashKey0 => "hash_key0", BuiltinFunction::HashKey1 => "hash_key1", + BuiltinFunction::State => "state", + BuiltinFunction::Process => "process", } } pub fn return_type(self) -> TypeRef { - let result = TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new( - ClassId::result(), - ))); + let result = TypeRef::foreign_struct(ClassId::result()); match self { - BuiltinFunction::ArrayCapacity => TypeRef::int(), - BuiltinFunction::ArrayClear => TypeRef::nil(), - BuiltinFunction::ArrayDrop => TypeRef::nil(), - BuiltinFunction::ArrayGet => TypeRef::Any, - BuiltinFunction::ArrayLength => TypeRef::int(), - BuiltinFunction::ArrayPop => result, BuiltinFunction::ArrayPush => TypeRef::nil(), - BuiltinFunction::ArrayRemove => TypeRef::Any, - BuiltinFunction::ArrayReserve => TypeRef::nil(), - BuiltinFunction::ArraySet => TypeRef::Any, - BuiltinFunction::ByteArrayNew => TypeRef::byte_array(), - BuiltinFunction::ByteArrayAppend => TypeRef::nil(), - BuiltinFunction::ByteArrayClear => TypeRef::nil(), - BuiltinFunction::ByteArrayClone => TypeRef::byte_array(), - BuiltinFunction::ByteArrayCopyFrom => TypeRef::int(), - BuiltinFunction::ByteArrayDrainToString => TypeRef::string(), - BuiltinFunction::ByteArrayDrop => TypeRef::nil(), - BuiltinFunction::ByteArrayEq => TypeRef::boolean(), - BuiltinFunction::ByteArrayGet => TypeRef::int(), - BuiltinFunction::ByteArrayLength => TypeRef::int(), - BuiltinFunction::ByteArrayPop => TypeRef::int(), - BuiltinFunction::ByteArrayPush => TypeRef::nil(), - BuiltinFunction::ByteArrayRemove => TypeRef::int(), - BuiltinFunction::ByteArrayResize => TypeRef::nil(), - BuiltinFunction::ByteArraySet => TypeRef::int(), - BuiltinFunction::ByteArraySlice => TypeRef::byte_array(), - BuiltinFunction::ByteArrayToString => TypeRef::string(), BuiltinFunction::ChildProcessDrop => TypeRef::nil(), BuiltinFunction::ChildProcessSpawn => result, BuiltinFunction::ChildProcessStderrClose => TypeRef::nil(), @@ -2286,15 +2127,7 @@ impl BuiltinFunction { BuiltinFunction::FloatRound => TypeRef::float(), BuiltinFunction::FloatSub => TypeRef::float(), BuiltinFunction::FloatToBits => TypeRef::int(), - BuiltinFunction::FloatToInt => TypeRef::int(), BuiltinFunction::FloatToString => TypeRef::string(), - BuiltinFunction::ChannelReceive => TypeRef::Any, - BuiltinFunction::ChannelReceiveUntil => result, - BuiltinFunction::ChannelDrop => TypeRef::nil(), - BuiltinFunction::ChannelWait => TypeRef::nil(), - BuiltinFunction::ChannelNew => TypeRef::Any, - BuiltinFunction::ChannelSend => TypeRef::nil(), - BuiltinFunction::ChannelTryReceive => result, BuiltinFunction::IntAdd => TypeRef::int(), BuiltinFunction::IntBitAnd => TypeRef::int(), BuiltinFunction::IntBitNot => TypeRef::int(), @@ -2314,7 +2147,6 @@ impl BuiltinFunction { BuiltinFunction::IntShl => TypeRef::int(), BuiltinFunction::IntShr => TypeRef::int(), BuiltinFunction::IntSub => TypeRef::int(), - BuiltinFunction::IntToFloat => TypeRef::float(), BuiltinFunction::IntToString => TypeRef::string(), BuiltinFunction::IntUnsignedShr => TypeRef::int(), BuiltinFunction::IntWrappingAdd => TypeRef::int(), @@ -2383,26 +2215,19 @@ impl BuiltinFunction { BuiltinFunction::StdoutFlush => TypeRef::nil(), BuiltinFunction::StdoutWriteBytes => result, BuiltinFunction::StdoutWriteString => result, - BuiltinFunction::StringByte => TypeRef::int(), - BuiltinFunction::StringCharacters => TypeRef::Any, - BuiltinFunction::StringCharactersDrop => TypeRef::nil(), - BuiltinFunction::StringCharactersNext => result, BuiltinFunction::StringConcat => TypeRef::string(), - BuiltinFunction::StringConcatArray => TypeRef::string(), - BuiltinFunction::StringDrop => TypeRef::nil(), - BuiltinFunction::StringEq => TypeRef::boolean(), - BuiltinFunction::StringSize => TypeRef::int(), - BuiltinFunction::StringSliceBytes => TypeRef::string(), - BuiltinFunction::StringToByteArray => TypeRef::byte_array(), BuiltinFunction::StringToFloat => result, - BuiltinFunction::StringToInt => result, - BuiltinFunction::StringToLower => TypeRef::string(), - BuiltinFunction::StringToUpper => TypeRef::string(), BuiltinFunction::TimeMonotonic => TypeRef::int(), BuiltinFunction::TimeSystem => TypeRef::float(), BuiltinFunction::TimeSystemOffset => TypeRef::int(), BuiltinFunction::HashKey0 => TypeRef::int(), BuiltinFunction::HashKey1 => TypeRef::int(), + BuiltinFunction::State => { + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))) + } + BuiltinFunction::Process => { + TypeRef::pointer(TypeId::Foreign(ForeignType::Int(8))) + } } } } @@ -2463,6 +2288,9 @@ pub enum MethodKind { /// The method is a destructor. Destructor, + + /// The method is an external/FFI function. + Extern, } #[derive(Copy, Clone)] @@ -2524,19 +2352,7 @@ impl Method { kind: MethodKind, ) -> MethodId { let id = db.methods.len(); - let method = Method::new(module, name, visibility, kind); - - db.methods.push(method); - MethodId(id) - } - - fn new( - module: ModuleId, - name: String, - visibility: Visibility, - kind: MethodKind, - ) -> Self { - Self { + let method = Method { module, name, kind, @@ -2549,7 +2365,10 @@ impl Method { receiver: TypeRef::Unknown, field_types: HashMap::new(), main: false, - } + }; + + db.methods.push(method); + MethodId(id) } } @@ -2632,6 +2451,10 @@ impl MethodId { ) } + pub fn is_extern(self, db: &Database) -> bool { + matches!(self.get(db).kind, MethodKind::Extern) + } + pub fn is_moving(self, db: &Database) -> bool { matches!(self.get(db).kind, MethodKind::Moving) } @@ -2734,6 +2557,15 @@ impl MethodId { self.get_mut(db).bounds = bounds; } + pub fn returns_value(self, db: &Database) -> bool { + let method = self.get(db); + + match method.kind { + MethodKind::Extern => method.return_type != TypeRef::nil(), + _ => true, + } + } + fn get(self, db: &Database) -> &Method { &db.methods[self.0] } @@ -2835,6 +2667,8 @@ pub enum CallKind { CallClosure(ClosureCallInfo), GetField(FieldInfo), SetField(FieldInfo), + ReadPointer(TypeRef), + WritePointer, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -2917,16 +2751,6 @@ impl Symbol { } } - pub fn defined_in(self, db: &Database, module: ModuleId) -> bool { - match self { - Symbol::Method(id) => id.module(db) == module, - Symbol::Class(id) => id.module(db) == module, - Symbol::Trait(id) => id.module(db) == module, - Symbol::Constant(id) => id.module(db) == module, - _ => false, - } - } - pub fn is_private(self, db: &Database) -> bool { !self.is_public(db) } @@ -2939,6 +2763,7 @@ pub struct Module { file: PathBuf, constants: Vec, symbols: HashMap, + extern_methods: Vec, } impl Module { @@ -2959,18 +2784,15 @@ impl Module { ); db.module_mapping.insert(name.to_string(), id); - db.modules.push(Module::new(name, class_id, file)); - id - } - - fn new(name: ModuleName, class: ClassId, file: PathBuf) -> Module { - Module { + db.modules.push(Module { name, - class, + class: class_id, file, constants: Vec::new(), symbols: HashMap::default(), - } + extern_methods: Vec::new(), + }); + id } } @@ -3014,6 +2836,14 @@ impl ModuleId { self.get(db).class.add_method(db, name, method); } + pub fn add_extern_method(self, db: &mut Database, method: MethodId) { + self.get_mut(db).extern_methods.push(method); + } + + pub fn extern_methods(self, db: &Database) -> &Vec { + &self.get(db).extern_methods + } + pub fn is_std(self, db: &Database) -> bool { self.get(db).name.is_std() } @@ -3357,10 +3187,6 @@ pub enum TypeRef { /// library isn't allowed. Any, - /// A value that could be anything but shouldn't have its ownership - /// transferred. - RefAny, - /// A value indicating a typing error. /// /// This type is produced whenever a type couldn't be produced, for example @@ -3374,6 +3200,9 @@ pub enum TypeRef { /// A placeholder for a yet-to-infer type. Placeholder(TypePlaceholderId), + + /// A pointer to a value. + Pointer(TypeId), } impl TypeRef { @@ -3413,6 +3242,22 @@ impl TypeRef { ))) } + pub fn foreign_int(size: u32) -> TypeRef { + TypeRef::Owned(TypeId::Foreign(ForeignType::Int(size))) + } + + pub fn foreign_float(size: u32) -> TypeRef { + TypeRef::Owned(TypeId::Foreign(ForeignType::Float(size))) + } + + pub fn pointer(of: TypeId) -> TypeRef { + TypeRef::Pointer(of) + } + + pub fn foreign_struct(id: ClassId) -> TypeRef { + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(id))) + } + pub fn module(id: ModuleId) -> TypeRef { TypeRef::Owned(TypeId::Module(id)) } @@ -3426,7 +3271,8 @@ impl TypeRef { pub fn type_id(self, db: &Database) -> Result { match self { - TypeRef::Owned(id) + TypeRef::Pointer(id) + | TypeRef::Owned(id) | TypeRef::Uni(id) | TypeRef::Ref(id) | TypeRef::Mut(id) @@ -3460,32 +3306,45 @@ impl TypeRef { pub fn allow_in_array(self, db: &Database) -> bool { match self { - TypeRef::Any - | TypeRef::RefAny - | TypeRef::UniRef(_) - | TypeRef::UniMut(_) => false, + TypeRef::Any | TypeRef::UniRef(_) | TypeRef::UniMut(_) => false, TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.allow_in_array(db)) } - _ => true, + _ => !self.is_foreign_type(db), } } - pub fn is_any(self, db: &Database) -> bool { + pub fn is_foreign_type(self, db: &Database) -> bool { match self { - TypeRef::Any | TypeRef::RefAny => true, + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of.kind(db).is_extern() => + { + true + } + TypeRef::Owned(TypeId::Foreign(_)) => true, + TypeRef::Pointer(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(false, |v| v.is_any(db)) + id.value(db).map_or(false, |v| v.is_foreign_type(db)) } _ => false, } } - pub fn is_ref_any(self, db: &Database) -> bool { + pub fn is_pointer(self, db: &Database) -> bool { + match self { + TypeRef::Pointer(_) => true, + TypeRef::Placeholder(id) => { + id.value(db).map_or(false, |v| v.is_pointer(db)) + } + _ => false, + } + } + + pub fn is_any(self, db: &Database) -> bool { match self { - TypeRef::RefAny => true, + TypeRef::Any => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(false, |v| v.is_ref_any(db)) + id.value(db).map_or(false, |v| v.is_any(db)) } _ => false, } @@ -3679,6 +3538,7 @@ impl TypeRef { | TypeRef::Uni(_) | TypeRef::Mut(_) | TypeRef::Infer(_) + | TypeRef::Pointer(_) | TypeRef::Error | TypeRef::Unknown | TypeRef::Never => true, @@ -3733,6 +3593,7 @@ impl TypeRef { | TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::UniMut(_) + | TypeRef::Pointer(_) ) } @@ -3800,6 +3661,8 @@ impl TypeRef { if self.is_value_type(db) { return if other.is_uni(db) { self.as_uni(db) + // } else if self.is_foreign_type(db) && other.is_pointer(db) { + // self.as_pointer(db) } else { self.as_owned(db) }; @@ -3837,11 +3700,12 @@ impl TypeRef { pub fn allow_as_ref(self, db: &Database) -> bool { match self { - TypeRef::Any => true, TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Ref(_) - | TypeRef::Uni(_) => true, + | TypeRef::Uni(_) + | TypeRef::Any + | TypeRef::Error => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.allow_as_ref(db)) } @@ -3854,9 +3718,11 @@ impl TypeRef { TypeRef::Any => true, TypeRef::Owned(TypeId::RigidTypeParameter(id)) => id.is_mutable(db), TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Uni(_) => true, + TypeRef::Pointer(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.allow_as_mut(db)) } + TypeRef::Error => true, _ => false, } } @@ -3879,6 +3745,16 @@ impl TypeRef { } } + pub fn as_pointer(self, db: &Database) -> TypeRef { + match self { + TypeRef::Owned(id) | TypeRef::Uni(id) => TypeRef::Pointer(id), + TypeRef::Placeholder(id) => { + id.value(db).map_or(self, |v| v.as_pointer(db)) + } + _ => self, + } + } + pub fn as_uni_ref(self, db: &Database) -> Self { match self { TypeRef::Owned(id) | TypeRef::Mut(id) => TypeRef::UniMut(id), @@ -3987,8 +3863,10 @@ impl TypeRef { | TypeRef::UniRef(TypeId::ClassInstance(ins)) | TypeRef::UniMut(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { - ins.instance_of().get(db).value_type + ins.instance_of().is_value_type(db) } + TypeRef::Owned(TypeId::Foreign(_)) => true, + TypeRef::Pointer(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.is_value_type(db)) } @@ -4007,14 +3885,15 @@ impl TypeRef { ins.instance_of.kind(db).is_extern() || matches!(ins.instance_of.0, BOOLEAN_ID | NIL_ID) } + TypeRef::Owned(TypeId::Foreign(_)) => true, TypeRef::Owned(TypeId::Module(_)) => true, TypeRef::Owned(TypeId::Class(_)) => true, TypeRef::Never => true, TypeRef::Any => true, - TypeRef::RefAny => true, TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.is_permanent(db)) } + TypeRef::Pointer(_) => true, _ => false, } } @@ -4142,6 +4021,12 @@ impl TypeRef { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum ForeignType { + Int(u32), + Float(u32), +} + /// An ID pointing to a type. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum TypeId { @@ -4153,6 +4038,7 @@ pub enum TypeId { TypeParameter(TypeParameterId), RigidTypeParameter(TypeParameterId), Closure(ClosureId), + Foreign(ForeignType), } impl TypeId { @@ -4205,7 +4091,7 @@ impl TypeId { TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { id.method(db, name) } - TypeId::Closure(_) => None, + _ => None, } } @@ -4426,6 +4312,7 @@ mod tests { fn test_type_sizes() { assert_eq!(size_of::(), 16); assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 8); } #[test] @@ -4457,12 +4344,8 @@ mod tests { fn test_type_parameter_id_add_requirements() { let mut db = Database::new(); let id = TypeParameter::alloc(&mut db, "A".to_string()); - let trait_id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); + let trait_id = + Trait::alloc(&mut db, "ToString".to_string(), Visibility::Private); let requirement = TraitInstance::new(trait_id); id.add_requirements(&mut db, vec![requirement]); @@ -4487,12 +4370,7 @@ mod tests { #[test] fn test_trait_alloc() { let mut db = Database::new(); - let id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); + let id = Trait::alloc(&mut db, "A".to_string(), Visibility::Private); assert_eq!(id.0, 0); assert_eq!(&db.traits[0].name, &"A".to_string()); @@ -4500,8 +4378,7 @@ mod tests { #[test] fn test_trait_new() { - let trait_type = - Trait::new("A".to_string(), ModuleId(0), Visibility::Private); + let trait_type = Trait::new("A".to_string(), Visibility::Private); assert_eq!(&trait_type.name, &"A"); } @@ -4509,12 +4386,7 @@ mod tests { #[test] fn test_trait_id_new_type_parameter() { let mut db = Database::new(); - let id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); + let id = Trait::alloc(&mut db, "A".to_string(), Visibility::Private); let param = id.new_type_parameter(&mut db, "A".to_string()); assert_eq!(id.type_parameters(&db), vec![param]); @@ -4523,12 +4395,7 @@ mod tests { #[test] fn test_trait_instance_new() { let mut db = Database::new(); - let id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); + let id = Trait::alloc(&mut db, "A".to_string(), Visibility::Private); let ins = TraitInstance::new(id); let index = db.traits.len() as u32 - 1; @@ -4539,12 +4406,7 @@ mod tests { #[test] fn test_trait_instance_generic() { let mut db = Database::new(); - let id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); + let id = Trait::alloc(&mut db, "A".to_string(), Visibility::Private); let ins1 = TraitInstance::generic(&mut db, id, TypeArguments::new()); let ins2 = TraitInstance::generic(&mut db, id, TypeArguments::new()); let index = db.traits.len() as u32 - 1; @@ -4810,12 +4672,8 @@ mod tests { #[test] fn test_type_id_named_type_with_trait() { let mut db = Database::new(); - let to_array = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); + let to_array = + Trait::alloc(&mut db, "ToArray".to_string(), Visibility::Private); let param = to_array.new_type_parameter(&mut db, "T".to_string()); assert_eq!( @@ -4873,12 +4731,8 @@ mod tests { #[test] fn test_type_id_named_type_with_trait_instance() { let mut db = Database::new(); - let to_array = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); + let to_array = + Trait::alloc(&mut db, "ToArray".to_string(), Visibility::Private); let param = to_array.new_type_parameter(&mut db, "T".to_string()); let ins = TypeId::TraitInstance(TraitInstance::generic( &mut db, diff --git a/types/src/test.rs b/types/src/test.rs index ad9a9ad2b..99ef93f58 100644 --- a/types/src/test.rs +++ b/types/src/test.rs @@ -26,7 +26,7 @@ pub(crate) fn new_extern_class(db: &mut Database, name: &str) -> ClassId { } pub(crate) fn new_trait(db: &mut Database, name: &str) -> TraitId { - Trait::alloc(db, name.to_string(), ModuleId(0), Visibility::Public) + Trait::alloc(db, name.to_string(), Visibility::Public) } pub(crate) fn new_parameter(db: &mut Database, name: &str) -> TypeParameterId {