From 442ecabe2d74ee53af679abc6caaae737b626235 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 17 Sep 2024 20:31:52 +0200 Subject: [PATCH] Reintroduce futures using Future and Promise This commit reintroduces futures, allowing messages to send their results back to the sender of the message, without the need for a channel. Futures are split into two user facing types: Future and Promise. A Future is a proxy for a value to be computed in the future, while a Promise is used for specifying what that value is. Both a Future and Promise can only be used once (e.g. Future.get consumes its receiver). This in turn allows for a simple and efficient implementation, rather than the more complex implementation of the old Channel type. TODO: add deque TODO: replace channels Changelog: changed --- compiler/src/diagnostics.rs | 4 +- compiler/src/hir.rs | 43 ++- compiler/src/llvm/builder.rs | 42 ++- compiler/src/llvm/context.rs | 17 +- compiler/src/llvm/layouts.rs | 8 +- compiler/src/llvm/passes.rs | 238 ++++++++------ compiler/src/mir/mod.rs | 13 +- compiler/src/mir/passes.rs | 36 ++- compiler/src/type_check/expressions.rs | 14 +- docs/source/setup/installation.md | 4 + rt/src/process.rs | 89 +++--- rt/src/runtime/process.rs | 75 +++++ rt/src/scheduler/timeout_worker.rs | 10 +- rt/src/scheduler/timeouts.rs | 6 +- std/src/std/bool.inko | 6 +- std/src/std/sync.inko | 421 +++++++++++++++++++++++++ std/test/compiler/test_ffi.inko | 35 +- std/test/std/test_intrinsics.inko | 16 + std/test/std/test_sync.inko | 97 ++++++ types/src/lib.rs | 328 +++++++++---------- 20 files changed, 1123 insertions(+), 379 deletions(-) create mode 100644 std/src/std/sync.inko create mode 100644 std/test/std/test_intrinsics.inko create mode 100644 std/test/std/test_sync.inko diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index f450f6c90..9d079dd7b 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -444,14 +444,14 @@ impl Diagnostics { ); } - pub(crate) fn builtin_function_not_available( + pub(crate) fn intrinsic_not_available( &mut self, file: PathBuf, location: SourceLocation, ) { self.error( DiagnosticId::InvalidCall, - "builtin functions can only be used in the standard library", + "intrinsics can only be used in the standard library", file, location, ); diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 09c2b826a..a186f6ce3 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -169,12 +169,14 @@ pub(crate) struct Call { /// `Option>` since we don't care about the presence (or lack) /// of parentheses 99% of the time. pub(crate) parens: bool, + /// A flag indicating if the call resides directly in a `mut` expression. + pub(crate) in_mut: bool, pub(crate) location: SourceLocation, } #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct BuiltinCall { - pub(crate) info: Option, + pub(crate) info: Option, pub(crate) name: Identifier, pub(crate) arguments: Vec, pub(crate) location: SourceLocation, @@ -859,6 +861,7 @@ pub(crate) struct FieldRef { pub(crate) name: String, pub(crate) resolved_type: types::TypeRef, pub(crate) location: SourceLocation, + pub(crate) in_mut: bool, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -2065,6 +2068,7 @@ impl<'a> LowerToHir<'a> { location: loc.clone(), }, parens: false, + in_mut: false, arguments: Vec::new(), location: loc, })); @@ -2131,6 +2135,7 @@ impl<'a> LowerToHir<'a> { location: node.location.clone(), }, parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -2165,6 +2170,7 @@ impl<'a> LowerToHir<'a> { location: node.location.clone(), }, parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: arg, @@ -2405,6 +2411,7 @@ impl<'a> LowerToHir<'a> { location: node.operator.location, }, parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: self.expression(node.right), @@ -2420,6 +2427,7 @@ impl<'a> LowerToHir<'a> { field_id: None, name: node.name, resolved_type: types::TypeRef::Unknown, + in_mut: false, location: node.location, }) } @@ -2445,7 +2453,7 @@ impl<'a> LowerToHir<'a> { fn call(&mut self, node: ast::Call) -> Expression { if self.is_builtin_call(&node) { if !self.module.is_std(&self.state.db) { - self.state.diagnostics.builtin_function_not_available( + self.state.diagnostics.intrinsic_not_available( self.file(), node.location.clone(), ); @@ -2471,6 +2479,7 @@ impl<'a> LowerToHir<'a> { receiver: node.receiver.map(|n| self.expression(n)), name: self.identifier(node.name), parens: node.arguments.is_some(), + in_mut: false, arguments: self.optional_call_arguments(node.arguments), location: node.location, })) @@ -2641,6 +2650,7 @@ impl<'a> LowerToHir<'a> { }, receiver: Some(receiver), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: self.expression(node.value), @@ -2664,6 +2674,7 @@ impl<'a> LowerToHir<'a> { field_id: None, name: field.name.clone(), resolved_type: types::TypeRef::Unknown, + in_mut: false, location: field.location.clone(), })); @@ -2678,6 +2689,7 @@ impl<'a> LowerToHir<'a> { }, receiver: Some(receiver), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: self.expression(node.value), @@ -2730,6 +2742,7 @@ impl<'a> LowerToHir<'a> { receiver: Some(setter_rec.clone()), name: name.clone(), parens: false, + in_mut: false, arguments: Vec::new(), location: getter_loc, })); @@ -2742,6 +2755,7 @@ impl<'a> LowerToHir<'a> { kind: types::CallKind::Unknown, receiver: Some(getter_rec), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: self.expression(node.value), @@ -2855,10 +2869,18 @@ impl<'a> LowerToHir<'a> { } fn mut_reference(&mut self, node: ast::Mut) -> Box { + let mut value = self.expression(node.value); + + match &mut value { + Expression::Call(n) => n.in_mut = true, + Expression::FieldRef(n) => n.in_mut = true, + _ => {} + } + Box::new(Mut { pointer_to_method: None, resolved_type: types::TypeRef::Unknown, - value: self.expression(node.value), + value, location: node.location, }) } @@ -5172,6 +5194,7 @@ mod tests { location: cols(12, 13) }, parens: false, + in_mut: false, arguments: Vec::new(), location: cols(12, 13) })), @@ -5220,6 +5243,7 @@ mod tests { location: cols(8, 11), }, parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new( @@ -5251,6 +5275,7 @@ mod tests { location: cols(8, 11), }, parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -5308,6 +5333,7 @@ mod tests { location: cols(8, 8) }))), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -5337,6 +5363,7 @@ mod tests { field_id: None, name: "a".to_string(), resolved_type: types::TypeRef::Unknown, + in_mut: false, location: cols(8, 9) })) ); @@ -5378,6 +5405,7 @@ mod tests { location: cols(10, 10) }, parens: false, + in_mut: false, arguments: Vec::new(), location: cols(8, 10) })) @@ -5412,6 +5440,7 @@ mod tests { location: cols(8, 8) }, parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -5441,6 +5470,7 @@ mod tests { location: cols(8, 8) }, parens: true, + in_mut: false, arguments: vec![Argument::Named(Box::new(NamedArgument { index: 0, name: Identifier { @@ -5480,6 +5510,7 @@ mod tests { location: cols(10, 10) }, parens: false, + in_mut: false, arguments: Vec::new(), location: cols(8, 10) })) @@ -5655,6 +5686,7 @@ mod tests { } ))), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -5738,10 +5770,12 @@ mod tests { location: cols(10, 10) }, parens: false, + in_mut: false, arguments: Vec::new(), location: cols(8, 10) }))), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -5779,9 +5813,11 @@ mod tests { field_id: None, name: "a".to_string(), resolved_type: types::TypeRef::Unknown, + in_mut: false, location: cols(8, 9) }))), parens: true, + in_mut: false, arguments: vec![Argument::Positional(Box::new( PositionalArgument { value: Expression::Int(Box::new(IntLiteral { @@ -6167,6 +6203,7 @@ mod tests { location: cols(12, 12) }, parens: true, + in_mut: false, arguments: Vec::new(), location: cols(12, 14) })), diff --git a/compiler/src/llvm/builder.rs b/compiler/src/llvm/builder.rs index 8a3a135d1..58502ae00 100644 --- a/compiler/src/llvm/builder.rs +++ b/compiler/src/llvm/builder.rs @@ -186,6 +186,16 @@ impl<'ctx> Builder<'ctx> { .into_float_value() } + pub(crate) fn load_bool( + &self, + variable: PointerValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner + .build_load(self.context.bool_type(), variable, "") + .unwrap() + .into_int_value() + } + pub(crate) fn load_pointer( &self, variable: PointerValue<'ctx>, @@ -299,6 +309,26 @@ impl<'ctx> Builder<'ctx> { .unwrap() } + pub(crate) fn atomic_swap>( + &self, + pointer: PointerValue<'ctx>, + old: V, + new: V, + ) -> IntValue<'ctx> { + let res = self + .inner + .build_cmpxchg( + pointer, + old, + new, + AtomicOrdering::AcquireRelease, + AtomicOrdering::Acquire, + ) + .unwrap(); + + self.extract_field(res, 1).into_int_value() + } + pub(crate) fn load_atomic_counter( &self, variable: PointerValue<'ctx>, @@ -484,18 +514,6 @@ impl<'ctx> Builder<'ctx> { self.inner.build_int_cast_sign_flag(value, target, signed, "").unwrap() } - pub(crate) fn bool_to_int(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { - let typ = self.context.i64_type(); - - self.inner.build_int_cast_sign_flag(value, typ, false, "").unwrap() - } - - pub(crate) fn int_to_bool(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { - let typ = self.context.bool_type(); - - self.inner.build_int_cast_sign_flag(value, typ, true, "").unwrap() - } - pub(crate) fn float_to_float( &self, value: FloatValue<'ctx>, diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index 57dc286f1..2ad439d4a 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -155,11 +155,15 @@ impl Context { layouts: &Layouts<'a>, type_ref: TypeRef, ) -> BasicTypeEnum<'a> { + if let TypeRef::Pointer(_) = type_ref { + return self.pointer_type().as_basic_type_enum(); + } + let Ok(id) = type_ref.type_id(db) else { return self.pointer_type().as_basic_type_enum(); }; - let base = match id { + match id { TypeId::Foreign(ForeignType::Int(8, _)) => { self.i8_type().as_basic_type_enum() } @@ -186,21 +190,16 @@ impl Context { .as_basic_type_enum() } else { match cls.0 { - INT_ID | BOOL_ID | NIL_ID => { - self.i64_type().as_basic_type_enum() + BOOL_ID | NIL_ID => { + self.bool_type().as_basic_type_enum() } + INT_ID => self.i64_type().as_basic_type_enum(), FLOAT_ID => self.f64_type().as_basic_type_enum(), _ => self.pointer_type().as_basic_type_enum(), } } } _ => self.pointer_type().as_basic_type_enum(), - }; - - if let TypeRef::Pointer(_) = type_ref { - self.pointer_type().as_basic_type_enum() - } else { - base } } diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs index 0e3a1a01d..35e1dc345 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -125,13 +125,7 @@ impl<'ctx> Layouts<'ctx> { // defined. for &id in mir.classes.keys() { let instance = match id.0 { - INT_ID => { - context.builtin_type(header, context.i64_type().into()) - } - FLOAT_ID => { - context.builtin_type(header, context.f64_type().into()) - } - BOOL_ID | NIL_ID => { + INT_ID | FLOAT_ID | BOOL_ID | NIL_ID => { let typ = context.opaque_struct(""); typ.set_body(&[header.into()], false); diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index d07b0f499..329b30cc5 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -46,10 +46,12 @@ use std::thread::scope; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use types::module_name::ModuleName; use types::{ - BuiltinFunction, ClassId, Database, Module as ModuleType, Shape, TypeRef, + ClassId, Database, Intrinsic, Module as ModuleType, Shape, TypeRef, BYTE_ARRAY_ID, STRING_ID, }; +const NIL_VALUE: bool = false; + fn object_path(directories: &BuildDirectories, name: &ModuleName) -> PathBuf { let hash = hash(name.as_str().as_bytes()).to_string(); @@ -513,9 +515,24 @@ impl<'a> Worker<'a> { let model = CodeModel::Default; let triple_name = shared.state.config.target.llvm_triple(); let triple = TargetTriple::create(&triple_name); + + // We require SSE2 for the pause() instruction, and also require it and + // both Neon on ARM64 to allow generated code to take advantage of their + // instructions. + let features = match shared.state.config.target.arch { + Architecture::Amd64 => "+sse2", + Architecture::Arm64 => "+neon", + }; let machine = Target::from_triple(&triple) .unwrap() - .create_target_machine(&triple, "", "", shared.level, reloc, model) + .create_target_machine( + &triple, + "", + features, + shared.level, + reloc, + model, + ) .unwrap(); Worker { shared, main, machine, timings: HashMap::new() } @@ -894,12 +911,7 @@ impl<'shared, 'module, 'ctx> LowerModule<'shared, 'module, 'ctx> { builder.f64_literal(*val).as_basic_value_enum() } Constant::String(val) => self.new_string(builder, state, val), - Constant::Bool(true) => { - builder.i64_literal(1).as_basic_value_enum() - } - Constant::Bool(false) => { - builder.i64_literal(0).as_basic_value_enum() - } + Constant::Bool(v) => builder.bool_literal(*v).as_basic_value_enum(), Constant::Array(values) => { let (shape, val_typ) = match values.first() { Some(Constant::Int(_)) => ( @@ -908,7 +920,7 @@ impl<'shared, 'module, 'ctx> LowerModule<'shared, 'module, 'ctx> { ), Some(Constant::Bool(_)) => ( Shape::Boolean, - builder.context.i64_type().as_basic_type_enum(), + builder.context.bool_type().as_basic_type_enum(), ), Some(Constant::Float(_)) => ( Shape::float(), @@ -1172,7 +1184,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.set_debug_location(ins.location); match ins.name { - BuiltinFunction::IntDiv => { + Intrinsic::IntDiv => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1182,7 +1194,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntRem => { + Intrinsic::IntRem => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1192,7 +1204,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntBitAnd => { + Intrinsic::IntBitAnd => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1202,7 +1214,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntBitOr => { + Intrinsic::IntBitOr => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1212,7 +1224,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntBitNot => { + Intrinsic::IntBitNot => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_int(val_var); @@ -1220,7 +1232,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntBitXor => { + Intrinsic::IntBitXor => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1230,62 +1242,57 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntEq => { + Intrinsic::IntEq => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_int(lhs_var); let rhs = self.builder.load_int(rhs_var); - let raw = self.builder.int_eq(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.int_eq(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::IntGt => { + Intrinsic::IntGt => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_int(lhs_var); let rhs = self.builder.load_int(rhs_var); - let raw = self.builder.int_gt(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.int_gt(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::IntGe => { + Intrinsic::IntGe => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_int(lhs_var); let rhs = self.builder.load_int(rhs_var); - let raw = self.builder.int_ge(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.int_ge(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::IntLe => { + Intrinsic::IntLe => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_int(lhs_var); let rhs = self.builder.load_int(rhs_var); - let raw = self.builder.int_le(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.int_le(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::IntLt => { + Intrinsic::IntLt => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_int(lhs_var); let rhs = self.builder.load_int(rhs_var); - let raw = self.builder.int_lt(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.int_lt(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::FloatAdd => { + Intrinsic::FloatAdd => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1295,7 +1302,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatSub => { + Intrinsic::FloatSub => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1305,7 +1312,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatDiv => { + Intrinsic::FloatDiv => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1315,7 +1322,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatMul => { + Intrinsic::FloatMul => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1325,7 +1332,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatMod => { + Intrinsic::FloatMod => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1341,7 +1348,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatCeil => { + Intrinsic::FloatCeil => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_float(val_var); @@ -1357,7 +1364,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatFloor => { + Intrinsic::FloatFloor => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_float(val_var); @@ -1373,18 +1380,17 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatEq => { + Intrinsic::FloatEq => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_float(lhs_var); let rhs = self.builder.load_float(rhs_var); - let raw = self.builder.float_eq(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.float_eq(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::FloatToBits => { + Intrinsic::FloatToBits => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_float(val_var); @@ -1395,7 +1401,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatFromBits => { + Intrinsic::FloatFromBits => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_int(val_var); @@ -1406,51 +1412,47 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatGt => { + Intrinsic::FloatGt => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_float(lhs_var); let rhs = self.builder.load_float(rhs_var); - let raw = self.builder.float_gt(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.float_gt(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::FloatGe => { + Intrinsic::FloatGe => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_float(lhs_var); let rhs = self.builder.load_float(rhs_var); - let raw = self.builder.float_ge(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.float_ge(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::FloatLt => { + Intrinsic::FloatLt => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_float(lhs_var); let rhs = self.builder.load_float(rhs_var); - let raw = self.builder.float_lt(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.float_lt(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::FloatLe => { + Intrinsic::FloatLe => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; let lhs = self.builder.load_float(lhs_var); let rhs = self.builder.load_float(rhs_var); - let raw = self.builder.float_le(lhs, rhs); - let res = self.builder.bool_to_int(raw); + let res = self.builder.float_le(lhs, rhs); self.builder.store(reg_var, res); } - BuiltinFunction::FloatIsInf => { + Intrinsic::FloatIsInf => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_float(val_var); @@ -1465,21 +1467,19 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .into_float_value(); let pos_inf = self.builder.f64_literal(f64::INFINITY); - let cond = self.builder.float_eq(pos_val, pos_inf); - let res = self.builder.bool_to_int(cond); + let res = self.builder.float_eq(pos_val, pos_inf); self.builder.store(reg_var, res); } - BuiltinFunction::FloatIsNan => { + Intrinsic::FloatIsNan => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_float(val_var); - let raw = self.builder.float_is_nan(val); - let res = self.builder.bool_to_int(raw); + let res = self.builder.float_is_nan(val); self.builder.store(reg_var, res); } - BuiltinFunction::FloatRound => { + Intrinsic::FloatRound => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_float(val_var); @@ -1495,7 +1495,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::FloatPowi => { + Intrinsic::FloatPowi => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1517,7 +1517,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntRotateLeft => { + Intrinsic::IntRotateLeft => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1534,7 +1534,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntRotateRight => { + Intrinsic::IntRotateRight => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1551,7 +1551,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntShl => { + Intrinsic::IntShl => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1561,7 +1561,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntShr => { + Intrinsic::IntShr => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1571,7 +1571,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntUnsignedShr => { + Intrinsic::IntUnsignedShr => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1581,7 +1581,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntWrappingAdd => { + Intrinsic::IntWrappingAdd => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1591,7 +1591,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntWrappingMul => { + Intrinsic::IntWrappingMul => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1601,7 +1601,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntWrappingSub => { + Intrinsic::IntWrappingSub => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1611,7 +1611,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntCheckedAdd => { + Intrinsic::IntCheckedAdd => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1629,7 +1629,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntCheckedMul => { + Intrinsic::IntCheckedMul => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1647,7 +1647,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntCheckedSub => { + Intrinsic::IntCheckedSub => { let reg_var = self.variables[&ins.register]; let lhs_var = self.variables[&ins.arguments[0]]; let rhs_var = self.variables[&ins.arguments[1]]; @@ -1665,7 +1665,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntSwapBytes => { + Intrinsic::IntSwapBytes => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_int(val_var); @@ -1680,7 +1680,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::IntAbsolute => { + Intrinsic::IntAbsolute => { let reg_var = self.variables[&ins.register]; let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_int(val_var); @@ -1696,7 +1696,21 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::Panic => { + Intrinsic::IntCompareSwap => { + let reg_var = self.variables[&ins.register]; + let ptr_var = self.variables[&ins.arguments[0]]; + let old_var = self.variables[&ins.arguments[1]]; + let old_typ = self.variable_types[&ins.arguments[1]]; + let new_var = self.variables[&ins.arguments[2]]; + let new_typ = self.variable_types[&ins.arguments[2]]; + let ptr = self.builder.load_pointer(ptr_var); + let old = self.builder.load(old_typ, old_var); + let new = self.builder.load(new_typ, new_var); + let res = self.builder.atomic_swap(ptr, old, new); + + self.builder.store(reg_var, res); + } + Intrinsic::Panic => { let val_var = self.variables[&ins.arguments[0]]; let val = self.builder.load_pointer(val_var); let func_name = RuntimeFunction::ProcessPanic; @@ -1706,7 +1720,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.call_void(func, &[proc, val.into()]); self.builder.unreachable(); } - BuiltinFunction::StringConcat => { + Intrinsic::StringConcat => { let reg_var = self.variables[&ins.register]; let len = self.builder.i64_literal(ins.arguments.len() as _); @@ -1737,19 +1751,55 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, res); } - BuiltinFunction::State => { + Intrinsic::State => { let reg_var = self.variables[&ins.register]; let state = self.load_state(); self.builder.store(reg_var, state); } - BuiltinFunction::Process => { + Intrinsic::Process => { let reg_var = self.variables[&ins.register]; let proc = self.load_process(); self.builder.store(reg_var, proc); } - BuiltinFunction::Moved => unreachable!(), + Intrinsic::SpinLoopHint => { + let reg_var = self.variables[&ins.register]; + let nil = self.builder.bool_literal(NIL_VALUE); + + match self.shared.state.config.target.arch { + Architecture::Amd64 => { + let func = self + .module + .intrinsic("llvm.x86.sse2.pause", &[]); + self.builder.call_void(func, &[]); + } + Architecture::Arm64 => { + // For ARM64 we use the same approach as Rust by + // using the ISB SY instruction. + let sy = self.builder.u32_literal(15); + let func = self.module.intrinsic( + "llvm.aarch64.isb", + &[self.builder.context.i32_type().into()], + ); + + self.builder.call_void(func, &[sy.into()]); + } + }; + + self.builder.store(reg_var, nil) + } + Intrinsic::BoolEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.builder.load_bool(lhs_var); + let rhs = self.builder.load_bool(rhs_var); + let res = self.builder.int_eq(lhs, rhs); + + self.builder.store(reg_var, res); + } + Intrinsic::Moved => unreachable!(), } } Instruction::Goto(ins) => { @@ -1764,11 +1814,10 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { } Instruction::Branch(ins) => { let var = self.variables[&ins.condition]; - let val = self.builder.load_int(var); - let status = self.builder.int_to_bool(val); + let val = self.builder.load_bool(var); self.builder.branch( - status, + val, all_blocks[ins.if_true.0], all_blocks[ins.if_false.0], ); @@ -1789,19 +1838,19 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { } Instruction::Nil(ins) => { let var = self.variables[&ins.register]; - let val = self.builder.i64_literal(0); + let val = self.builder.bool_literal(NIL_VALUE); self.builder.store(var, val); } Instruction::True(ins) => { let var = self.variables[&ins.register]; - let val = self.builder.i64_literal(1); + let val = self.builder.bool_literal(true); self.builder.store(var, val); } Instruction::False(ins) => { let var = self.variables[&ins.register]; - let val = self.builder.i64_literal(0); + let val = self.builder.bool_literal(false); self.builder.store(var, val); } @@ -2091,10 +2140,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .extract_field(method, METHOD_FUNCTION_INDEX) .into_pointer_value(); - let func_type = self - .builder - .context - .pointer_type() + let func_type = self.variable_types[&ins.register] .fn_type(&sig_args, false); self.indirect_call(ins.register, func_type, func_val, &args); @@ -2138,10 +2184,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .extract_field(method, METHOD_FUNCTION_INDEX) .into_pointer_value(); - let func_type = self - .builder - .context - .pointer_type() + let func_type = self.variable_types[&ins.register] .fn_type(&sig_args, false); self.indirect_call(ins.register, func_type, func_val, &args); @@ -2511,12 +2554,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { } Instruction::Finish(ins) => { let proc = self.load_process().into(); - let terminate = self - .builder - .context - .bool_type() - .const_int(ins.terminate as _, false) - .into(); + let terminate = self.builder.bool_literal(ins.terminate).into(); let func = self .module .runtime_function(RuntimeFunction::ProcessFinishMessage); diff --git a/compiler/src/mir/mod.rs b/compiler/src/mir/mod.rs index b9a49081c..a77f8bc44 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -13,8 +13,8 @@ use std::fmt; use std::hash::{Hash, Hasher}; use types::collections::IndexMap; use types::{ - BuiltinFunction, Database, ForeignType, MethodId, Shape, Sign, - TypeArguments, TypeId, TypeRef, BOOL_ID, FLOAT_ID, INT_ID, NIL_ID, + Database, ForeignType, Intrinsic, MethodId, Shape, Sign, TypeArguments, + TypeId, TypeRef, BOOL_ID, FLOAT_ID, INT_ID, NIL_ID, }; /// The register ID of the register that stores `self`. @@ -496,7 +496,7 @@ impl Block { pub(crate) fn call_builtin( &mut self, register: RegisterId, - name: BuiltinFunction, + name: Intrinsic, arguments: Vec, location: LocationId, ) { @@ -942,7 +942,7 @@ pub(crate) struct CallClosure { #[derive(Clone)] pub(crate) struct CallBuiltin { pub(crate) register: RegisterId, - pub(crate) name: BuiltinFunction, + pub(crate) name: Intrinsic, pub(crate) arguments: Vec, pub(crate) location: LocationId, } @@ -1048,9 +1048,8 @@ impl CastType { CastType::Float(64) } Ok(TypeId::ClassInstance(ins)) => match ins.instance_of().0 { - INT_ID | NIL_ID | BOOL_ID => { - CastType::Int(64, Sign::Signed) - } + BOOL_ID | NIL_ID => CastType::Int(1, Sign::Unsigned), + INT_ID => CastType::Int(64, Sign::Signed), FLOAT_ID => CastType::Float(64), _ => CastType::Object, }, diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index a0af7d637..6701131ab 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -1787,7 +1787,7 @@ impl<'a> LowerMethod<'a> { self.current_block_mut().call_builtin( reg, - types::BuiltinFunction::StringConcat, + types::Intrinsic::StringConcat, vals, loc, ); @@ -2323,7 +2323,7 @@ impl<'a> LowerMethod<'a> { node.arguments.into_iter().map(|n| self.expression(n)).collect(); match info.id { - types::BuiltinFunction::Moved => { + types::Intrinsic::Moved => { self.mark_register_as_moved(args[0]); self.get_nil(loc) } @@ -2565,8 +2565,24 @@ impl<'a> LowerMethod<'a> { self.current_block_mut().method_pointer(reg, id, loc); reg } else if node.resolved_type.is_pointer(self.db()) { + let expr = match node.value { + // For expressions `mut value.field` we defer to the call() + // method since that already has the necessary logic in place. + hir::Expression::Call(mut n) => { + if let types::CallKind::GetField(_) = &mut n.kind { + return self.call(*n); + } + + hir::Expression::Call(n) + } + // For `mut @field` the same is true: field() takes care of + // things. + hir::Expression::FieldRef(n) => return self.field(*n), + expr => expr, + }; + let loc = self.add_location(node.location); - let val = self.expression(node.value); + let val = self.expression(expr); let reg = self.new_register(node.resolved_type); self.current_block_mut().pointer(reg, val, loc); @@ -3205,7 +3221,7 @@ impl<'a> LowerMethod<'a> { self.block_mut(test_block).int_literal(val_reg, val, loc); self.block_mut(test_block).call_builtin( res_reg, - types::BuiltinFunction::IntEq, + types::Intrinsic::IntEq, vec![test_reg, val_reg], loc, ); @@ -3385,15 +3401,19 @@ impl<'a> LowerMethod<'a> { } } - if id.value_type(self.db()).is_stack_class_instance(self.db()) - && self.register_type(reg).is_pointer(self.db()) + let typ = id.value_type(self.db()); + + if (node.in_mut && typ.is_foreign_type(self.db())) + || typ.is_extern_instance(self.db()) { + let reg = self.new_register(typ.as_pointer(self.db())); + self.current_block_mut().field_pointer(reg, rec, class, id, loc); + reg } else { self.current_block_mut().get_field(reg, rec, class, id, loc); + reg } - - reg } fn constant(&mut self, node: hir::ConstantRef) -> RegisterId { diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs index 6796ca4f1..c9b7713d2 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -12,9 +12,9 @@ use types::check::{Environment, TypeChecker}; use types::format::{format_type, format_type_with_arguments}; use types::resolve::TypeResolver; use types::{ - Block, BuiltinCallInfo, CallInfo, CallKind, ClassId, ClassInstance, - Closure, ClosureCallInfo, ClosureId, ConstantKind, ConstantPatternKind, - Database, FieldId, FieldInfo, IdentifierKind, MethodId, MethodLookup, + Block, CallInfo, CallKind, ClassId, ClassInstance, Closure, + ClosureCallInfo, ClosureId, ConstantKind, ConstantPatternKind, Database, + FieldId, FieldInfo, IdentifierKind, IntrinsicCall, MethodId, MethodLookup, ModuleId, Receiver, Sign, Symbol, ThrowKind, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, TypeRef, Variable, VariableId, VariableLocation, CALL_METHOD, DEREF_POINTER_FIELD, @@ -4034,8 +4034,8 @@ impl<'a> CheckMethodBody<'a> { let mut returns = TypeResolver::new(self.db_mut(), &args, bounds) .with_immutable(immutable) .resolve(raw_type); - - let as_pointer = returns.is_stack_class_instance(self.db()); + let as_pointer = returns.is_extern_instance(self.db()) + || (node.in_mut && returns.is_foreign_type(self.db())); if returns.is_value_type(self.db()) { returns = if as_pointer { @@ -4070,7 +4070,7 @@ impl<'a> CheckMethodBody<'a> { self.expression(n, scope); } - let id = if let Some(id) = self.db().builtin_function(&node.name.name) { + let id = if let Some(id) = self.db().intrinsic(&node.name.name) { id } else { self.state.diagnostics.undefined_symbol( @@ -4084,7 +4084,7 @@ impl<'a> CheckMethodBody<'a> { let returns = id.return_type(); - node.info = Some(BuiltinCallInfo { id, returns }); + node.info = Some(IntrinsicCall { id, returns }); returns } diff --git a/docs/source/setup/installation.md b/docs/source/setup/installation.md index 753a693e1..2ff9f19a8 100644 --- a/docs/source/setup/installation.md +++ b/docs/source/setup/installation.md @@ -25,6 +25,10 @@ for the listed platforms. Windows isn't supported. - A C compiler such as [GCC](https://gcc.gnu.org/) or [clang](https://clang.llvm.org/) - Git, for managing packages using the `inko pkg` command +- An AMD64 CPU with [SSE2](https://en.wikipedia.org/wiki/SSE2) support or an + ARM64 CPU with + [Neon](https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)) + support (only for the generated code) ::: note While using newer versions of LLVM may work, it's possible for a newer version diff --git a/rt/src/process.rs b/rt/src/process.rs index 6340bd6c7..028a4ee92 100644 --- a/rt/src/process.rs +++ b/rt/src/process.rs @@ -95,8 +95,8 @@ impl ProcessStatus { /// The process is waiting for a message. const WAITING_FOR_MESSAGE: u8 = 0b00_0010; - /// The process is waiting for a channel. - const WAITING_FOR_CHANNEL: u8 = 0b00_0100; + /// The process is waiting for a value to be sent over some data structure. + const WAITING_FOR_VALUE: u8 = 0b00_0100; /// The process is waiting for an IO operation to complete. const WAITING_FOR_IO: u8 = 0b00_1000; @@ -112,7 +112,7 @@ impl ProcessStatus { /// The process is waiting for something, or suspended for a period of time. const WAITING: u8 = - Self::WAITING_FOR_CHANNEL | Self::SLEEPING | Self::WAITING_FOR_IO; + Self::WAITING_FOR_VALUE | Self::SLEEPING | Self::WAITING_FOR_IO; pub(crate) fn new() -> Self { Self { bits: Self::NORMAL } @@ -142,8 +142,8 @@ impl ProcessStatus { self.bit_is_set(Self::WAITING_FOR_MESSAGE) } - fn set_waiting_for_channel(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_CHANNEL, enable); + fn set_waiting_for_value(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_VALUE, enable); } fn set_waiting_for_io(&mut self, enable: bool) { @@ -154,8 +154,8 @@ impl ProcessStatus { self.bit_is_set(Self::WAITING_FOR_IO) } - fn is_waiting_for_channel(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_CHANNEL) + fn is_waiting_for_value(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_VALUE) } fn is_waiting(&self) -> bool { @@ -254,8 +254,7 @@ impl ProcessState { return RescheduleRights::Failed; } - if self.status.is_waiting_for_channel() - || self.status.is_waiting_for_io() + if self.status.is_waiting_for_value() || self.status.is_waiting_for_io() { // We may be suspended for some time without actually waiting for // anything, in that case we don't want to update the process @@ -272,13 +271,12 @@ impl ProcessState { } } - pub(crate) fn waiting_for_channel( + pub(crate) fn waiting_for_value( &mut self, timeout: Option>, ) { self.timeout = timeout; - - self.status.set_waiting_for_channel(true); + self.status.set_waiting_for_value(true); } pub(crate) fn waiting_for_io( @@ -286,7 +284,6 @@ impl ProcessState { timeout: Option>, ) { self.timeout = timeout; - self.status.set_waiting_for_io(true); } @@ -299,12 +296,12 @@ impl ProcessState { RescheduleRights::Acquired } - fn try_reschedule_for_channel(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_channel() { + pub(crate) fn try_reschedule_for_value(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_value() { return RescheduleRights::Failed; } - self.status.set_waiting_for_channel(false); + self.status.set_waiting_for_value(false); if self.timeout.take().is_some() { RescheduleRights::AcquiredWithTimeout @@ -855,7 +852,7 @@ impl Channel { // message. In this case it's possible that multiple different // processes try to reschedule the same waiting process, so we have // to acquire the rescheduling rights first. - match receiver.state().try_reschedule_for_channel() { + match receiver.state().try_reschedule_for_value() { RescheduleRights::Failed => SendResult::Sent, RescheduleRights::Acquired => SendResult::Reschedule(receiver), RescheduleRights::AcquiredWithTimeout => { @@ -881,7 +878,7 @@ impl Channel { ReceiveResult::Some(msg) } } else { - receiver.state().waiting_for_channel(timeout); + receiver.state().waiting_for_value(timeout); state.waiting_for_message.push(receiver); ReceiveResult::None } @@ -987,14 +984,14 @@ mod tests { } #[test] - fn test_process_status_set_waiting_for_channel() { + fn test_process_status_set_waiting_for_value() { let mut status = ProcessStatus::new(); - status.set_waiting_for_channel(true); - assert!(status.is_waiting_for_channel()); + status.set_waiting_for_value(true); + assert!(status.is_waiting_for_value()); - status.set_waiting_for_channel(false); - assert!(!status.is_waiting_for_channel()); + status.set_waiting_for_value(false); + assert!(!status.is_waiting_for_value()); } #[test] @@ -1002,13 +999,13 @@ mod tests { let mut status = ProcessStatus::new(); status.set_running(true); - status.set_waiting_for_channel(true); + status.set_waiting_for_value(true); status.set_waiting_for_io(true); status.set_sleeping(true); status.no_longer_waiting(); assert!(status.is_running()); - assert!(!status.is_waiting_for_channel()); + assert!(!status.is_waiting_for_value()); assert!(!status.is_waiting_for_io()); assert!(!status.bit_is_set(ProcessStatus::SLEEPING)); assert!(!status.is_waiting()); @@ -1022,12 +1019,12 @@ mod tests { assert!(status.is_waiting()); status.set_sleeping(false); - status.set_waiting_for_channel(true); + status.set_waiting_for_value(true); assert!(status.is_waiting()); status.no_longer_waiting(); - assert!(!status.is_waiting_for_channel()); + assert!(!status.is_waiting_for_value()); assert!(!status.is_waiting()); } @@ -1072,43 +1069,43 @@ mod tests { RescheduleRights::Failed ); - proc_state.waiting_for_channel(None); + proc_state.waiting_for_value(None); assert_eq!( proc_state.try_reschedule_after_timeout(), RescheduleRights::Acquired ); - assert!(!proc_state.status.is_waiting_for_channel()); + assert!(!proc_state.status.is_waiting_for_value()); assert!(!proc_state.status.is_waiting()); let timeout = Timeout::duration(&state, Duration::from_secs(0)); - proc_state.waiting_for_channel(Some(timeout)); + proc_state.waiting_for_value(Some(timeout)); assert_eq!( proc_state.try_reschedule_after_timeout(), RescheduleRights::AcquiredWithTimeout ); - assert!(!proc_state.status.is_waiting_for_channel()); + assert!(!proc_state.status.is_waiting_for_value()); assert!(!proc_state.status.is_waiting()); } #[test] - fn test_process_state_waiting_for_channel() { + fn test_process_state_waiting_for_value() { let state = setup(); let mut proc_state = ProcessState::new(); let timeout = Timeout::duration(&state, Duration::from_secs(0)); - proc_state.waiting_for_channel(None); + proc_state.waiting_for_value(None); - assert!(proc_state.status.is_waiting_for_channel()); + assert!(proc_state.status.is_waiting_for_value()); assert!(proc_state.timeout.is_none()); - proc_state.waiting_for_channel(Some(timeout)); + proc_state.waiting_for_value(Some(timeout)); - assert!(proc_state.status.is_waiting_for_channel()); + assert!(proc_state.status.is_waiting_for_value()); assert!(proc_state.timeout.is_some()); } @@ -1131,31 +1128,31 @@ mod tests { } #[test] - fn test_process_state_try_reschedule_for_channel() { + fn test_process_state_try_reschedule_for_value() { let state = setup(); let mut proc_state = ProcessState::new(); assert_eq!( - proc_state.try_reschedule_for_channel(), + proc_state.try_reschedule_for_value(), RescheduleRights::Failed ); - proc_state.status.set_waiting_for_channel(true); + proc_state.status.set_waiting_for_value(true); assert_eq!( - proc_state.try_reschedule_for_channel(), + proc_state.try_reschedule_for_value(), RescheduleRights::Acquired ); - assert!(!proc_state.status.is_waiting_for_channel()); + assert!(!proc_state.status.is_waiting_for_value()); - proc_state.status.set_waiting_for_channel(true); + proc_state.status.set_waiting_for_value(true); proc_state.timeout = Some(Timeout::duration(&state, Duration::from_secs(0))); assert_eq!( - proc_state.try_reschedule_for_channel(), + proc_state.try_reschedule_for_value(), RescheduleRights::AcquiredWithTimeout ); - assert!(!proc_state.status.is_waiting_for_channel()); + assert!(!proc_state.status.is_waiting_for_value()); } #[test] @@ -1313,7 +1310,7 @@ mod tests { let chan = Channel::new(1); assert_eq!(chan.receive(*process, None), ReceiveResult::None); - assert!(process.state().status.is_waiting_for_channel()); + assert!(process.state().status.is_waiting_for_value()); } #[test] @@ -1326,7 +1323,7 @@ mod tests { let chan = Channel::new(1); assert_eq!(chan.try_receive(), ReceiveResult::None); - assert!(!process.state().status.is_waiting_for_channel()); + assert!(!process.state().status.is_waiting_for_value()); } #[test] diff --git a/rt/src/runtime/process.rs b/rt/src/runtime/process.rs index 683fb070e..5085bb308 100644 --- a/rt/src/runtime/process.rs +++ b/rt/src/runtime/process.rs @@ -12,6 +12,7 @@ use std::cmp::max; use std::fmt::Write as _; use std::process::exit; use std::str; +use std::sync::atomic::{AtomicU8, Ordering}; use std::time::Duration; /// There's no real standard across programs for exit codes. Rust uses 101 so @@ -212,6 +213,80 @@ pub unsafe extern "system" fn inko_process_stop_blocking( process.stop_blocking(); } +#[no_mangle] +pub unsafe extern "system" fn inko_process_wait_for_value( + process: ProcessPointer, + lock: *const AtomicU8, + current: u8, + new: u8, +) { + let mut state = process.state(); + + state.waiting_for_value(None); + + let _ = (&*lock).compare_exchange( + current, + new, + Ordering::AcqRel, + Ordering::Acquire, + ); + + drop(state); + context::switch(process); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_wait_for_value_until( + state: *const State, + process: ProcessPointer, + lock: *const AtomicU8, + current: u8, + new: u8, + nanos: u64, +) -> bool { + let state = &*state; + let deadline = Timeout::until(nanos); + let mut proc_state = process.state(); + + proc_state.waiting_for_value(Some(deadline.clone())); + + let _ = (&*lock).compare_exchange( + current, + new, + Ordering::AcqRel, + Ordering::Acquire, + ); + + drop(proc_state); + + // Safety: the current thread is holding on to the run lock + state.timeout_worker.suspend(process, deadline); + context::switch(process); + process.timeout_expired() +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_reschedule_for_value( + state: *const State, + mut process: ProcessPointer, + waiter: ProcessPointer, +) { + let state = &*state; + let mut waiter_state = waiter.state(); + let reschedule = match waiter_state.try_reschedule_for_value() { + RescheduleRights::Failed => false, + RescheduleRights::Acquired => true, + RescheduleRights::AcquiredWithTimeout => { + state.timeout_worker.increase_expired_timeouts(); + true + } + }; + + if reschedule { + process.thread().schedule(waiter); + } +} + #[no_mangle] pub unsafe extern "system" fn inko_channel_new(capacity: i64) -> *mut Channel { Box::into_raw(Box::new(Channel::new(max(capacity, 1) as usize))) diff --git a/rt/src/scheduler/timeout_worker.rs b/rt/src/scheduler/timeout_worker.rs index 025fb36b1..401cd0394 100644 --- a/rt/src/scheduler/timeout_worker.rs +++ b/rt/src/scheduler/timeout_worker.rs @@ -234,7 +234,7 @@ mod tests { for time in &[10_u64, 5_u64] { let timeout = Timeout::duration(&state, Duration::from_secs(*time)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); worker.suspend(process, timeout); } @@ -258,7 +258,7 @@ mod tests { let worker = TimeoutWorker::new(); let timeout = Timeout::duration(&state, Duration::from_secs(10)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); worker.suspend(process, timeout); worker.run_iteration(&state); @@ -273,7 +273,7 @@ mod tests { let worker = TimeoutWorker::new(); let timeout = Timeout::duration(&state, Duration::from_secs(0)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); worker.suspend(process, timeout); worker.run_iteration(&state); @@ -288,7 +288,7 @@ mod tests { let worker = TimeoutWorker::new(); let timeout = Timeout::duration(&state, Duration::from_secs(1)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); worker.suspend(process, timeout); worker.move_messages(); worker.handle_pending_messages(); @@ -308,7 +308,7 @@ mod tests { for time in &[1_u64, 1_u64] { let timeout = Timeout::duration(&state, Duration::from_secs(*time)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); worker.suspend(process, timeout); } diff --git a/rt/src/scheduler/timeouts.rs b/rt/src/scheduler/timeouts.rs index 91c763148..6e6bc5ab7 100644 --- a/rt/src/scheduler/timeouts.rs +++ b/rt/src/scheduler/timeouts.rs @@ -323,7 +323,7 @@ mod tests { let mut timeouts = Timeouts::new(); let timeout = Timeout::duration(&state, Duration::from_secs(10)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); timeouts.insert(process, timeout); assert_eq!(timeouts.remove_invalid_entries(), 0); @@ -369,7 +369,7 @@ mod tests { let mut timeouts = Timeouts::new(); let timeout = Timeout::duration(&state, Duration::from_secs(10)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); timeouts.insert(process, timeout); let (reschedule, expiration) = @@ -388,7 +388,7 @@ mod tests { let mut timeouts = Timeouts::new(); let timeout = Timeout::duration(&state, Duration::from_secs(0)); - process.state().waiting_for_channel(Some(timeout.clone())); + process.state().waiting_for_value(Some(timeout.clone())); timeouts.insert(*process, timeout); let (reschedule, expiration) = diff --git a/std/src/std/bool.inko b/std/src/std/bool.inko index 1c5366225..b14fa8a31 100644 --- a/std/src/std/bool.inko +++ b/std/src/std/bool.inko @@ -38,8 +38,8 @@ class builtin Bool { # # Examples # # ```inko - # true.then fn { 10 } # => Option.Some(10) - # false.then fn { 10 } # => Option.None + # true.then(fn { 10 }) # => Option.Some(10) + # false.then(fn { 10 }) # => Option.None # ``` fn pub then[T](func: fn -> T) -> Option[T] { if self { Option.Some(func.call) } else { Option.None } @@ -66,7 +66,7 @@ impl Clone[Bool] for Bool { impl Equal[ref Bool] for Bool { fn pub ==(other: ref Bool) -> Bool { - _INKO.int_eq(self, other) + _INKO.bool_eq(self, other) } } diff --git a/std/src/std/sync.inko b/std/src/std/sync.inko new file mode 100644 index 000000000..10ad48b7f --- /dev/null +++ b/std/src/std/sync.inko @@ -0,0 +1,421 @@ +# Types for synchronizing operations. +# +# # Futures and promises +# +# Two important types for synchronizing operations are `Future` and `Promise`. A +# future is a proxy value to be resolved into a final value using a `Promise`. A +# `Future` and its corresponding `Promise` are created using the `Future.new` +# method. For example: +# +# ```inko +# import std.sync (Future, Promise) +# +# class async Example { +# fn async write(promise: uni Promise[Int]) { +# promise.set(42) +# } +# } +# +# class async Main { +# fn async main { +# match Future.new { +# case (future, promise) -> { +# Example().write(promise) +# future.get # => 42 +# } +# } +# } +# } +# ``` +import std.clone (Clone) +import std.drop (Drop, drop as drop_value) +import std.time (ToInstant) + +fn extern inko_process_wait_for_value( + process: Pointer[UInt8], + lock: Pointer[UInt8], + current: UInt8, + new: UInt8, +) + +fn extern inko_process_wait_for_value_until( + state: Pointer[UInt8], + process: Pointer[UInt8], + lock: Pointer[UInt8], + current: UInt8, + new: UInt8, + nanos: UInt64, +) -> Bool + +fn extern inko_process_reschedule_for_value( + state: Pointer[UInt8], + process: Pointer[UInt8], + waiter: Pointer[UInt8], +) + +let NO_WAITER = 0 +let UNLOCKED = 0 +let LOCKED = 1 + +# The state shared between a `Future` and a `Promise`. +class FutureState[T] { + # A spinlock used to restrict access to the state to a single thread/process + # at a time. + # + # The lock can be in one of two states: + # + # - `0`: the lock is unlocked + # - `1`: the lock is locked + # + # This field is of type `UInt8` such that we can take a pointer to it, which + # is only supported for foreign types. + # + # The reason for using a spinlock is to avoid the complexity and platform + # differences of pthread mutexes, and because it's unlikely we actually need + # them to begin with for this particular workload. + let @locked: UInt8 + + # A flag indicating if both the `Future` and `Promise` still exist. + # + # When either the `Future` or `Promise` is dropped, it sets this flag to + # `false` and the other half is responsible for cleaning up the shared state. + let @connected: Bool + + # The process waiting for a value to be written to the future. + # + # A value of NULL means no process is waiting. + let @waiter: Pointer[UInt8] + + # The value the future resolves to, if any. + let @value: Option[T] + + fn mut lock { + while + _INKO + .int_compare_swap(mut @locked, UNLOCKED as UInt8, LOCKED as UInt8) + .false? + { + # Since a future can only have a single reader and a single writer, + # contention is limited to at most two OS threads. Combined with the + # locked workload being fast (in the order of a few hundred nanoseconds at + # most), it's unlikely we'll ever reach this point. + # + # If we do, the spin loop hint ensures we don't set the CPU on fire. In + # addition, Inko's scheduler takes care of rescheduling the process + # automatically if we spin for too long. + _INKO.spin_loop_hint + } + } + + fn mut unlock { + _INKO.int_compare_swap(mut @locked, LOCKED as UInt8, UNLOCKED as UInt8) + } +} + +# A proxy value to resolve into the result of some asynchronous operation. +# +# The value of a `Future` is set by its corresponding `Promise`. +# +# A `Future[T]` is resolved into its `T` using one of the following methods: +# +# - `Future.get` +# - `Future.try_get` +# - `Future.get_until` +class pub Future[T] { + let @state: UInt64 + + # Returns a new `Future` along with its corresponding `Promise`. + # + # The `Future` and `Promise` are returned as unique references, allowing them + # to be moved between processes. + # + # # Examples + # + # ```inko + # import std.sync (Future) + # + # match Future.new { + # case (future, promise) -> { + # promise.set(42) + # future.get # => 42 + # } + # } + # ``` + fn pub static new -> (uni Future[uni T], uni Promise[uni T]) { + let fut: FutureState[uni T] = FutureState( + waiter: NO_WAITER as Pointer[UInt8], + locked: UNLOCKED as UInt8, + connected: true, + value: Option.None, + ) + + # The `Future` and `Promise` need shared access of the underlying data. This + # technically violates Inko's single-ownership rules, so to allow that we + # cast the state reference to an address, then cast that back where + # necessary. + # + # This is of course highly unsafe, but it's how this particular sausage is + # made. + let fut = fut as UInt64 + + (recover Future(fut), recover Promise(fut)) + } + + # Returns the value of the `Future`, blocking the calling process until a + # value is available. + # + # This method consumes the `Future`, ensuring a value can only be resolved + # once. + # + # # Deadlocks + # + # If a `Promise` is dropped before a call to `Future.get` or while the + # `Future` waits for a value to be written, the calling process of + # `Future.get` will deadlock. This method makes no attempt at detecting such + # cases as doing so is notoriously difficult. + # + # To avoid a deadlock, make sure to always write a value to a `Promise` + # _before_ discarding it. + # + # # Examples + # + # ```inko + # import std.sync (Future) + # + # match Future.new { + # case (future, promise) -> { + # promise.set(42) + # future.get # => 42 + # } + # } + # ``` + fn pub move get -> uni T { + loop { + let fut = lock + + match fut.value := Option.None { + case Some(val) -> { + fut.unlock + + # Ensure the shared state isn't dropped. + _INKO.moved(fut) + return val + } + case _ -> { + fut.waiter = _INKO.process + + # This atomically changes the process status, unlocks the future lock + # and yields back to the scheduler. + inko_process_wait_for_value( + _INKO.process, + mut fut.locked, + 1 as UInt8, + 0 as UInt8, + ) + + # Ensure the shared state isn't dropped. + _INKO.moved(fut) + } + } + } + } + + # Returns the value of the future, blocking the calling process until a value + # is available or the given deadline is exceeded. + # + # If a value is resolved within the deadline, a `Result.Ok` containing the + # value is returned. If the timeout expired, a `Result.Error` is returned + # containing a new `Future` to use for resolving the value. + # + # # Deadlocks + # + # Unlike `Future.get`, this method can't deadlock a calling process forever + # due to the use of a deadline. However, if the `Promise` is dropped before or + # during a call to `Future.get_until`, the calling process will be suspended + # until the deadline expires. + # + # # Examples + # + # ```inko + # import std.sync (Future) + # import std.time (Duration) + # + # match Future.new { + # case (future, promise) -> { + # promise.set(42) + # future.get_until(Duration.from_secs(1)) # => Result.Ok(42) + # } + # } + # ``` + fn pub move get_until[D: ToInstant]( + deadline: ref D, + ) -> Result[uni T, Future[T]] { + let nanos = deadline.to_instant.to_int as UInt64 + + loop { + let fut = lock + + match fut.value := Option.None { + case Some(val) -> { + fut.unlock + + # Ensure the shared state isn't dropped. + _INKO.moved(fut) + return Result.Ok(val) + } + case _ -> { + fut.waiter = _INKO.process + + # This atomically changes the process status, unlocks the future lock + # and yields back to the scheduler. + let timed_out = inko_process_wait_for_value_until( + _INKO.state, + _INKO.process, + mut fut.locked, + 1 as UInt8, + 0 as UInt8, + nanos, + ) + + # Ensure the shared state isn't dropped. + _INKO.moved(fut) + + if timed_out { return Result.Error(self) } + } + } + } + } + + # Returns the value of the future if one is present, without blocking the + # calling process. + # + # If a value is present, a `Result.Ok` is returned containing the value. If no + # value is present, a `Result.Error` is returned containing a new `Future` to + # use for resolving the value. + # + # # Deadlocks + # + # This method never deadlocks. + # + # # Examples + # + # ```inko + # import std.sync (Future) + # + # match Future.new { + # case (future, promise) -> { + # promise.set(42) + # future.try_get # => Result.Ok(42) + # } + # } + # ``` + fn pub move try_get -> Result[uni T, Future[T]] { + let fut = lock + let val = fut.value := Option.None + + fut.unlock + + # Ensure the shared state isn't dropped. + _INKO.moved(fut) + + match val { + case Some(v) -> Result.Ok(v) + case _ -> Result.Error(self) + } + } + + fn lock -> FutureState[uni T] { + let fut = @state as FutureState[uni T] + + fut.lock + fut + } +} + +impl Drop for Future { + fn mut drop { + let fut = lock + + if fut.connected { + # The `Promise` is still present, so it will be tasked with cleaning up + # the shared state. + fut.connected = false + fut.unlock + + # Ensure the shared state isn't dropped. + _INKO.moved(fut) + } else { + # The `Promise` is already dropped, so it's our job to clean up the shared + # state. + drop_value(fut) + } + } +} + +# The writing half of a future. +# +# A `Promise` is used to write a value to a future, such that a corresponding +# `Future` can be resolved into this value. +class pub Promise[T] { + let @state: UInt64 + + # Sets the value of the `Future` that belongs to this `Promise` to the given + # value. + # + # This method consumes `self` as to ensure a value can only be written once. + # + # This method never blocks the calling process. + # + # # Examples + # + # ```inko + # import std.sync (Future) + # + # match Future.new { + # case (future, promise) -> { + # promise.set(42) + # future.get # => 42 + # } + # } + # ``` + fn pub move set(value: uni T) { + let fut = lock + let waiter = fut.waiter := NO_WAITER as Pointer[UInt8] + + fut.value = Option.Some(value) + fut.unlock + + # Ensure we don't drop the shared state. + _INKO.moved(fut) + + # If the waiter is waiting for a value, we have to reschedule it. + if waiter as Int != NO_WAITER { + inko_process_reschedule_for_value(_INKO.state, _INKO.process, waiter) + } + } + + fn lock -> FutureState[uni T] { + let fut = @state as FutureState[uni T] + + fut.lock + fut + } +} + +impl Drop for Promise { + fn mut drop { + let fut = lock + + if fut.connected { + # The `Future` is still present, so it will be tasked with cleaning up the + # shared state. + fut.connected = false + fut.unlock + _INKO.moved(fut) + } else { + # The `Future` is already dropped, so it's our job to clean up the shared + # state. + drop_value(fut) + } + } +} diff --git a/std/test/compiler/test_ffi.inko b/std/test/compiler/test_ffi.inko index cbb0b36e2..b3a175a04 100644 --- a/std/test/compiler/test_ffi.inko +++ b/std/test/compiler/test_ffi.inko @@ -1,19 +1,40 @@ import std.test (Tests) -class extern Example { +class extern Foo { let @foo: Int32 let @bar: Int32 - let @baz: Pointer[Example] + let @baz: Pointer[Foo] +} + +class Bar { + let @value: Int64 + + fn mut ptr1 -> Pointer[Int64] { + mut @value + } + + fn mut ptr2 -> Pointer[Int64] { + mut self.value + } } fn pub tests(t: mut Tests) { t.test('Reading a field storing a pointer through another pointer', fn (t) { - let ex = Example( - foo: 1 as Int32, - bar: 2 as Int32, - baz: 0x42 as Pointer[Example], - ) + let ex = Foo(foo: 1 as Int32, bar: 2 as Int32, baz: 0x42 as Pointer[Foo]) t.equal(ex.baz as Int, 0x42) }) + + t.test('Creating pointers using the mut expression', fn (t) { + let bar = Bar(value: 42 as Int64) + + bar.ptr1.0 = 100 as Int64 + t.equal(bar.value as Int, 100) + + bar.ptr2.0 = 200 as Int64 + t.equal(bar.value as Int, 200) + + (mut bar.value).0 = 300 as Int64 + t.equal(bar.value as Int, 300) + }) } diff --git a/std/test/std/test_intrinsics.inko b/std/test/std/test_intrinsics.inko new file mode 100644 index 000000000..92b72a3ee --- /dev/null +++ b/std/test/std/test_intrinsics.inko @@ -0,0 +1,16 @@ +# Intrinsics technically aren't provided by the standard library, but we test +# them here since they can only be used in modules that reside in the `std` +# namespace. +import std.test (Tests) + +fn pub tests(t: mut Tests) { + t.test('int_compare_swap', fn (t) { + let val = 0 as Int64 + + t.true(_INKO.int_compare_swap(mut val, 0, 1)) + t.equal(val as Int, 1) + + t.false(_INKO.int_compare_swap(mut val, 0, 2)) + t.equal(val as Int, 1) + }) +} diff --git a/std/test/std/test_sync.inko b/std/test/std/test_sync.inko new file mode 100644 index 000000000..3636260eb --- /dev/null +++ b/std/test/std/test_sync.inko @@ -0,0 +1,97 @@ +import std.drop (drop) +import std.process (sleep) +import std.sync (Future, Promise) +import std.test (Tests) +import std.time (Duration) + +class async AsyncWriter { + fn async write(writer: uni Promise[Int]) { + # This doesn't strictly guarantee the reader is in fact waiting, but it's + # the closest we can get to that. + sleep(Duration.from_millis(10)) + writer.set(42) + } +} + +fn int_future -> (uni Future[Int], uni Promise[Int]) { + Future.new +} + +fn pub tests(t: mut Tests) { + t.test('Future.get', fn (t) { + match Future.new { + case (r, w) -> { + w.set(42) + t.equal(r.get, 42) + } + } + }) + + t.ok('Future.get_until', fn (t) { + match int_future { + case (r, w) -> { + let r = match r.get_until(Duration.from_millis(1)) { + case Ok(_) -> throw 'expected an Error' + case Error(r) -> r + } + + w.set(42) + t.equal(r.get_until(Duration.from_millis(1)).ok, Option.Some(42)) + } + } + + Result.Ok(nil) + }) + + t.ok('Future.try_get', fn (t) { + match int_future { + case (r, w) -> { + let r = match r.try_get { + case Ok(_) -> throw 'expected an Error' + case Error(r) -> r + } + + w.set(42) + t.equal(r.try_get.ok, Option.Some(42)) + } + } + + Result.Ok(nil) + }) + + t.test('Future.get_until with a dropped Promise', fn (t) { + match int_future { + case (r, w) -> { + drop(w) + t.true(r.get_until(Duration.from_millis(1)).error?) + } + } + }) + + t.test('Future.try_get with a dropped Promise', fn (t) { + match int_future { + case (r, w) -> { + drop(w) + t.true(r.try_get.error?) + } + } + }) + + t.test('Promise.set with a dropped Future', fn (t) { + match int_future { + case (r, w) -> { + drop(r) + w.set(42) + } + } + }) + + t.test('Promise.set wakes up a Future', fn (t) { + match int_future { + case (r, w) -> { + AsyncWriter().write(w) + t.equal(r.get, 42) + } + } + }) +} diff --git a/types/src/lib.rs b/types/src/lib.rs index 107e71f10..876c06c65 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1912,7 +1912,7 @@ impl Visibility { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum BuiltinFunction { +pub enum Intrinsic { FloatAdd, FloatCeil, FloatDiv, @@ -1960,58 +1960,64 @@ pub enum BuiltinFunction { IntCheckedSub, IntSwapBytes, IntAbsolute, + IntCompareSwap, + SpinLoopHint, + BoolEq, } -impl BuiltinFunction { +impl Intrinsic { pub fn mapping() -> HashMap { vec![ - BuiltinFunction::FloatAdd, - BuiltinFunction::FloatCeil, - BuiltinFunction::FloatDiv, - BuiltinFunction::FloatEq, - BuiltinFunction::FloatFloor, - BuiltinFunction::FloatFromBits, - BuiltinFunction::FloatGe, - BuiltinFunction::FloatGt, - BuiltinFunction::FloatIsInf, - BuiltinFunction::FloatIsNan, - BuiltinFunction::FloatLe, - BuiltinFunction::FloatLt, - BuiltinFunction::FloatMod, - BuiltinFunction::FloatMul, - BuiltinFunction::FloatSub, - BuiltinFunction::FloatToBits, - BuiltinFunction::IntBitAnd, - BuiltinFunction::IntBitNot, - BuiltinFunction::IntBitOr, - BuiltinFunction::IntBitXor, - BuiltinFunction::IntDiv, - BuiltinFunction::IntEq, - BuiltinFunction::IntGe, - BuiltinFunction::IntGt, - BuiltinFunction::IntLe, - BuiltinFunction::IntLt, - BuiltinFunction::IntRem, - BuiltinFunction::IntRotateLeft, - BuiltinFunction::IntRotateRight, - BuiltinFunction::IntShl, - BuiltinFunction::IntShr, - BuiltinFunction::IntUnsignedShr, - BuiltinFunction::IntWrappingAdd, - BuiltinFunction::IntWrappingMul, - BuiltinFunction::IntWrappingSub, - BuiltinFunction::IntCheckedAdd, - BuiltinFunction::IntCheckedMul, - BuiltinFunction::IntCheckedSub, - BuiltinFunction::Moved, - BuiltinFunction::Panic, - BuiltinFunction::StringConcat, - BuiltinFunction::State, - BuiltinFunction::Process, - BuiltinFunction::FloatRound, - BuiltinFunction::FloatPowi, - BuiltinFunction::IntSwapBytes, - BuiltinFunction::IntAbsolute, + Intrinsic::FloatAdd, + Intrinsic::FloatCeil, + Intrinsic::FloatDiv, + Intrinsic::FloatEq, + Intrinsic::FloatFloor, + Intrinsic::FloatFromBits, + Intrinsic::FloatGe, + Intrinsic::FloatGt, + Intrinsic::FloatIsInf, + Intrinsic::FloatIsNan, + Intrinsic::FloatLe, + Intrinsic::FloatLt, + Intrinsic::FloatMod, + Intrinsic::FloatMul, + Intrinsic::FloatSub, + Intrinsic::FloatToBits, + Intrinsic::IntBitAnd, + Intrinsic::IntBitNot, + Intrinsic::IntBitOr, + Intrinsic::IntBitXor, + Intrinsic::IntDiv, + Intrinsic::IntEq, + Intrinsic::IntGe, + Intrinsic::IntGt, + Intrinsic::IntLe, + Intrinsic::IntLt, + Intrinsic::IntRem, + Intrinsic::IntRotateLeft, + Intrinsic::IntRotateRight, + Intrinsic::IntShl, + Intrinsic::IntShr, + Intrinsic::IntUnsignedShr, + Intrinsic::IntWrappingAdd, + Intrinsic::IntWrappingMul, + Intrinsic::IntWrappingSub, + Intrinsic::IntCheckedAdd, + Intrinsic::IntCheckedMul, + Intrinsic::IntCheckedSub, + Intrinsic::Moved, + Intrinsic::Panic, + Intrinsic::StringConcat, + Intrinsic::State, + Intrinsic::Process, + Intrinsic::FloatRound, + Intrinsic::FloatPowi, + Intrinsic::IntSwapBytes, + Intrinsic::IntAbsolute, + Intrinsic::IntCompareSwap, + Intrinsic::SpinLoopHint, + Intrinsic::BoolEq, ] .into_iter() .fold(HashMap::new(), |mut map, func| { @@ -2022,53 +2028,56 @@ impl BuiltinFunction { pub fn name(self) -> &'static str { match self { - BuiltinFunction::FloatAdd => "float_add", - BuiltinFunction::FloatCeil => "float_ceil", - BuiltinFunction::FloatDiv => "float_div", - BuiltinFunction::FloatEq => "float_eq", - BuiltinFunction::FloatFloor => "float_floor", - BuiltinFunction::FloatFromBits => "float_from_bits", - BuiltinFunction::FloatGe => "float_ge", - BuiltinFunction::FloatGt => "float_gt", - BuiltinFunction::FloatIsInf => "float_is_inf", - BuiltinFunction::FloatIsNan => "float_is_nan", - BuiltinFunction::FloatLe => "float_le", - BuiltinFunction::FloatLt => "float_lt", - BuiltinFunction::FloatMod => "float_mod", - BuiltinFunction::FloatMul => "float_mul", - BuiltinFunction::FloatSub => "float_sub", - BuiltinFunction::FloatToBits => "float_to_bits", - BuiltinFunction::IntBitAnd => "int_bit_and", - BuiltinFunction::IntBitNot => "int_bit_not", - BuiltinFunction::IntBitOr => "int_bit_or", - BuiltinFunction::IntBitXor => "int_bit_xor", - BuiltinFunction::IntDiv => "int_div", - BuiltinFunction::IntEq => "int_eq", - BuiltinFunction::IntGe => "int_ge", - BuiltinFunction::IntGt => "int_gt", - BuiltinFunction::IntLe => "int_le", - BuiltinFunction::IntLt => "int_lt", - BuiltinFunction::IntRem => "int_rem", - BuiltinFunction::IntRotateLeft => "int_rotate_left", - BuiltinFunction::IntRotateRight => "int_rotate_right", - BuiltinFunction::IntShl => "int_shl", - BuiltinFunction::IntShr => "int_shr", - BuiltinFunction::IntUnsignedShr => "int_unsigned_shr", - BuiltinFunction::IntWrappingAdd => "int_wrapping_add", - BuiltinFunction::IntWrappingMul => "int_wrapping_mul", - BuiltinFunction::IntWrappingSub => "int_wrapping_sub", - BuiltinFunction::IntCheckedAdd => "int_checked_add", - BuiltinFunction::IntCheckedMul => "int_checked_mul", - BuiltinFunction::IntCheckedSub => "int_checked_sub", - BuiltinFunction::Moved => "moved", - BuiltinFunction::Panic => "panic", - BuiltinFunction::StringConcat => "string_concat", - BuiltinFunction::State => "state", - BuiltinFunction::Process => "process", - BuiltinFunction::FloatRound => "float_round", - BuiltinFunction::FloatPowi => "float_powi", - BuiltinFunction::IntSwapBytes => "int_swap_bytes", - BuiltinFunction::IntAbsolute => "int_absolute", + Intrinsic::FloatAdd => "float_add", + Intrinsic::FloatCeil => "float_ceil", + Intrinsic::FloatDiv => "float_div", + Intrinsic::FloatEq => "float_eq", + Intrinsic::FloatFloor => "float_floor", + Intrinsic::FloatFromBits => "float_from_bits", + Intrinsic::FloatGe => "float_ge", + Intrinsic::FloatGt => "float_gt", + Intrinsic::FloatIsInf => "float_is_inf", + Intrinsic::FloatIsNan => "float_is_nan", + Intrinsic::FloatLe => "float_le", + Intrinsic::FloatLt => "float_lt", + Intrinsic::FloatMod => "float_mod", + Intrinsic::FloatMul => "float_mul", + Intrinsic::FloatSub => "float_sub", + Intrinsic::FloatToBits => "float_to_bits", + Intrinsic::IntBitAnd => "int_bit_and", + Intrinsic::IntBitNot => "int_bit_not", + Intrinsic::IntBitOr => "int_bit_or", + Intrinsic::IntBitXor => "int_bit_xor", + Intrinsic::IntDiv => "int_div", + Intrinsic::IntEq => "int_eq", + Intrinsic::IntGe => "int_ge", + Intrinsic::IntGt => "int_gt", + Intrinsic::IntLe => "int_le", + Intrinsic::IntLt => "int_lt", + Intrinsic::IntRem => "int_rem", + Intrinsic::IntRotateLeft => "int_rotate_left", + Intrinsic::IntRotateRight => "int_rotate_right", + Intrinsic::IntShl => "int_shl", + Intrinsic::IntShr => "int_shr", + Intrinsic::IntUnsignedShr => "int_unsigned_shr", + Intrinsic::IntWrappingAdd => "int_wrapping_add", + Intrinsic::IntWrappingMul => "int_wrapping_mul", + Intrinsic::IntWrappingSub => "int_wrapping_sub", + Intrinsic::IntCheckedAdd => "int_checked_add", + Intrinsic::IntCheckedMul => "int_checked_mul", + Intrinsic::IntCheckedSub => "int_checked_sub", + Intrinsic::Moved => "moved", + Intrinsic::Panic => "panic", + Intrinsic::StringConcat => "string_concat", + Intrinsic::State => "state", + Intrinsic::Process => "process", + Intrinsic::FloatRound => "float_round", + Intrinsic::FloatPowi => "float_powi", + Intrinsic::IntSwapBytes => "int_swap_bytes", + Intrinsic::IntAbsolute => "int_absolute", + Intrinsic::IntCompareSwap => "int_compare_swap", + Intrinsic::SpinLoopHint => "spin_loop_hint", + Intrinsic::BoolEq => "bool_eq", } } @@ -2078,57 +2087,60 @@ impl BuiltinFunction { )); match self { - BuiltinFunction::FloatAdd => TypeRef::float(), - BuiltinFunction::FloatCeil => TypeRef::float(), - BuiltinFunction::FloatDiv => TypeRef::float(), - BuiltinFunction::FloatEq => TypeRef::boolean(), - BuiltinFunction::FloatFloor => TypeRef::float(), - BuiltinFunction::FloatFromBits => TypeRef::float(), - BuiltinFunction::FloatGe => TypeRef::boolean(), - BuiltinFunction::FloatGt => TypeRef::boolean(), - BuiltinFunction::FloatIsInf => TypeRef::boolean(), - BuiltinFunction::FloatIsNan => TypeRef::boolean(), - BuiltinFunction::FloatLe => TypeRef::boolean(), - BuiltinFunction::FloatLt => TypeRef::boolean(), - BuiltinFunction::FloatMod => TypeRef::float(), - BuiltinFunction::FloatMul => TypeRef::float(), - BuiltinFunction::FloatSub => TypeRef::float(), - BuiltinFunction::FloatToBits => TypeRef::int(), - BuiltinFunction::IntBitAnd => TypeRef::int(), - BuiltinFunction::IntBitNot => TypeRef::int(), - BuiltinFunction::IntBitOr => TypeRef::int(), - BuiltinFunction::IntBitXor => TypeRef::int(), - BuiltinFunction::IntDiv => TypeRef::int(), - BuiltinFunction::IntEq => TypeRef::boolean(), - BuiltinFunction::IntGe => TypeRef::boolean(), - BuiltinFunction::IntGt => TypeRef::boolean(), - BuiltinFunction::IntLe => TypeRef::boolean(), - BuiltinFunction::IntLt => TypeRef::boolean(), - BuiltinFunction::IntRem => TypeRef::int(), - BuiltinFunction::IntRotateLeft => TypeRef::int(), - BuiltinFunction::IntRotateRight => TypeRef::int(), - BuiltinFunction::IntShl => TypeRef::int(), - BuiltinFunction::IntShr => TypeRef::int(), - BuiltinFunction::IntUnsignedShr => TypeRef::int(), - BuiltinFunction::IntWrappingAdd => TypeRef::int(), - BuiltinFunction::IntWrappingMul => TypeRef::int(), - BuiltinFunction::IntWrappingSub => TypeRef::int(), - BuiltinFunction::IntCheckedAdd => checked_result, - BuiltinFunction::IntCheckedMul => checked_result, - BuiltinFunction::IntCheckedSub => checked_result, - BuiltinFunction::Moved => TypeRef::nil(), - BuiltinFunction::Panic => TypeRef::Never, - BuiltinFunction::StringConcat => TypeRef::string(), - BuiltinFunction::State => TypeRef::pointer(TypeId::Foreign( + Intrinsic::FloatAdd => TypeRef::float(), + Intrinsic::FloatCeil => TypeRef::float(), + Intrinsic::FloatDiv => TypeRef::float(), + Intrinsic::FloatEq => TypeRef::boolean(), + Intrinsic::FloatFloor => TypeRef::float(), + Intrinsic::FloatFromBits => TypeRef::float(), + Intrinsic::FloatGe => TypeRef::boolean(), + Intrinsic::FloatGt => TypeRef::boolean(), + Intrinsic::FloatIsInf => TypeRef::boolean(), + Intrinsic::FloatIsNan => TypeRef::boolean(), + Intrinsic::FloatLe => TypeRef::boolean(), + Intrinsic::FloatLt => TypeRef::boolean(), + Intrinsic::FloatMod => TypeRef::float(), + Intrinsic::FloatMul => TypeRef::float(), + Intrinsic::FloatSub => TypeRef::float(), + Intrinsic::FloatToBits => TypeRef::int(), + Intrinsic::IntBitAnd => TypeRef::int(), + Intrinsic::IntBitNot => TypeRef::int(), + Intrinsic::IntBitOr => TypeRef::int(), + Intrinsic::IntBitXor => TypeRef::int(), + Intrinsic::IntDiv => TypeRef::int(), + Intrinsic::IntEq => TypeRef::boolean(), + Intrinsic::IntGe => TypeRef::boolean(), + Intrinsic::IntGt => TypeRef::boolean(), + Intrinsic::IntLe => TypeRef::boolean(), + Intrinsic::IntLt => TypeRef::boolean(), + Intrinsic::IntRem => TypeRef::int(), + Intrinsic::IntRotateLeft => TypeRef::int(), + Intrinsic::IntRotateRight => TypeRef::int(), + Intrinsic::IntShl => TypeRef::int(), + Intrinsic::IntShr => TypeRef::int(), + Intrinsic::IntUnsignedShr => TypeRef::int(), + Intrinsic::IntWrappingAdd => TypeRef::int(), + Intrinsic::IntWrappingMul => TypeRef::int(), + Intrinsic::IntWrappingSub => TypeRef::int(), + Intrinsic::IntCheckedAdd => checked_result, + Intrinsic::IntCheckedMul => checked_result, + Intrinsic::IntCheckedSub => checked_result, + Intrinsic::Moved => TypeRef::nil(), + Intrinsic::Panic => TypeRef::Never, + Intrinsic::StringConcat => TypeRef::string(), + Intrinsic::State => TypeRef::pointer(TypeId::Foreign( ForeignType::Int(8, Sign::Unsigned), )), - BuiltinFunction::Process => TypeRef::pointer(TypeId::Foreign( + Intrinsic::Process => TypeRef::pointer(TypeId::Foreign( ForeignType::Int(8, Sign::Unsigned), )), - BuiltinFunction::FloatRound => TypeRef::float(), - BuiltinFunction::FloatPowi => TypeRef::float(), - BuiltinFunction::IntSwapBytes => TypeRef::int(), - BuiltinFunction::IntAbsolute => TypeRef::int(), + Intrinsic::FloatRound => TypeRef::float(), + Intrinsic::FloatPowi => TypeRef::float(), + Intrinsic::IntSwapBytes => TypeRef::int(), + Intrinsic::IntAbsolute => TypeRef::int(), + Intrinsic::IntCompareSwap => TypeRef::boolean(), + Intrinsic::SpinLoopHint => TypeRef::nil(), + Intrinsic::BoolEq => TypeRef::boolean(), } } } @@ -2820,8 +2832,8 @@ pub struct ClosureCallInfo { } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct BuiltinCallInfo { - pub id: BuiltinFunction, +pub struct IntrinsicCall { + pub id: Intrinsic, pub returns: TypeRef, } @@ -3799,9 +3811,7 @@ impl TypeRef { } } - /// Returns `true` if `self` is an instance of a class that's allocated on - /// and passed around using the stack. - pub fn is_stack_class_instance(self, db: &Database) -> bool { + pub fn is_extern_instance(self, db: &Database) -> bool { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { @@ -4173,9 +4183,7 @@ impl TypeRef { if self.is_value_type(db) { return if other.is_uni(db) { self.as_uni(db) - } else if other.is_ref_or_mut(db) - && self.is_stack_class_instance(db) - { + } else if other.is_ref_or_mut(db) && self.is_extern_instance(db) { self.as_pointer(db) } else { self.as_owned(db) @@ -4909,7 +4917,7 @@ pub struct Database { closures: Vec, variables: Vec, constants: Vec, - builtin_functions: HashMap, + intrinsics: HashMap, type_placeholders: Vec, constructors: Vec, @@ -4960,7 +4968,7 @@ impl Database { closures: Vec::new(), variables: Vec::new(), constants: Vec::new(), - builtin_functions: BuiltinFunction::mapping(), + intrinsics: Intrinsic::mapping(), type_placeholders: Vec::new(), constructors: Vec::new(), main_module: None, @@ -4992,8 +5000,8 @@ impl Database { } } - pub fn builtin_function(&self, name: &str) -> Option { - self.builtin_functions.get(name).cloned() + pub fn intrinsic(&self, name: &str) -> Option { + self.intrinsics.get(name).cloned() } pub fn module(&self, name: &str) -> ModuleId { @@ -6312,10 +6320,10 @@ mod tests { let mut db = Database::new(); let ext = new_extern_class(&mut db, "A"); - assert!(owned(instance(ext)).is_stack_class_instance(&db)); - assert!(uni(instance(ext)).is_stack_class_instance(&db)); - assert!(!immutable(instance(ext)).is_stack_class_instance(&db)); - assert!(!mutable(instance(ext)).is_stack_class_instance(&db)); - assert!(!pointer(instance(ext)).is_stack_class_instance(&db)); + assert!(owned(instance(ext)).is_extern_instance(&db)); + assert!(uni(instance(ext)).is_extern_instance(&db)); + assert!(!immutable(instance(ext)).is_extern_instance(&db)); + assert!(!mutable(instance(ext)).is_extern_instance(&db)); + assert!(!pointer(instance(ext)).is_extern_instance(&db)); } }